"Vou falar sobre « modificadores de acesso ». Já falei sobre eles uma vez, mas a repetição é um pilar do aprendizado."
Você pode controlar o acesso (visibilidade) que outras classes têm aos métodos e variáveis de sua classe. Um modificador de acesso responde à pergunta «Quem pode acessar este método/variável?». Você pode especificar apenas um modificador para cada método ou variável.
1) modificador « público ».
Uma variável, método ou classe marcada com o modificador público pode ser acessada de qualquer lugar no programa. Este é o mais alto grau de abertura: não há restrições.
2) modificador « privado ».
Uma variável, método ou classe marcada com o modificador private só pode ser acessada na classe em que foi declarada. O método ou variável marcado está oculto de todas as outras classes. Este é o mais alto grau de privacidade: acessível apenas por sua classe. Esses métodos não são herdados e não podem ser substituídos. Além disso, eles não podem ser acessados em uma classe descendente.
3) « Modificador padrão ».
Se uma variável ou método não estiver marcado com nenhum modificador, será considerado marcado com o modificador "padrão". Variáveis e métodos com este modificador são visíveis para todas as classes no pacote onde são declaradas e somente para essas classes. Esse modificador também é chamado de acesso " pacote " ou " pacote privado ", sugerindo que o acesso a variáveis e métodos é aberto a todo o pacote que contém a classe.
4) modificador « protegido ».
Este nível de acesso é um pouco mais amplo do que o pacote . Uma variável, método ou classe marcada com o modificador protegido pode ser acessada de seu pacote (como "pacote") e de todas as classes herdadas.
Esta tabela explica tudo:
| Tipo de visibilidade | Palavra-chave | Acesso | |||
|---|---|---|---|---|---|
| Sua classe | Seu pacote | Descendente | todas as classes | ||
| Privado | privado | Sim | Não | Não | Não |
| Pacote | (sem modificador) | Sim | Sim | Não | Não |
| Protegido | protegido | Sim | Sim | Sim | Não |
| Público | público | Sim | Sim | Sim | Sim |
Existe uma maneira de lembrar facilmente esta tabela. Imagine que você está escrevendo um testamento. Você está dividindo todas as suas coisas em quatro categorias. Quem pode usar suas coisas?
| Quem tem acesso | modificador | Exemplo |
|---|---|---|
| só eu | privado | diário pessoal |
| Família | (sem modificador) | Fotos de família |
| Família e herdeiros | protegido | propriedade familiar |
| Todo mundo | público | Memórias |
"É como imaginar que as aulas de um mesmo pacote fazem parte de uma família."
"Também quero contar algumas nuances interessantes sobre a substituição de métodos."
1) Implementação implícita de um método abstrato.
Digamos que você tenha o seguinte código:
class Cat
{
public String getName()
{
return "Oscar";
}
}
E você decidiu criar uma classe Tiger que herda esta classe e adicionar uma interface à nova classe
class Cat
{
public String getName()
{
return "Oscar";
}
}
interface HasName
{
String getName();
int getWeight();
}
class Tiger extends Cat implements HasName
{
public int getWeight()
{
return 115;
}
}
Se você apenas implementar todos os métodos ausentes que o IntelliJ IDEA diz para você implementar, mais tarde você pode acabar gastando muito tempo procurando por um bug.
Acontece que a classe Tiger tem um método getName herdado de Cat, que será considerado a implementação do método getName para a interface HasName.
"Não vejo nada de terrível nisso."
"Não é tão ruim, é um lugar provável para erros se infiltrarem."
Mas pode ser ainda pior:
interface HasWeight
{
int getValue();
}
interface HasSize
{
int getValue();
}
class Tiger extends Cat implements HasWeight, HasSize
{
public int getValue()
{
return 115;
}
}
Acontece que você nem sempre pode herdar de várias interfaces. Mais precisamente, você pode herdá-los, mas não pode implementá-los corretamente. Olhe para o exemplo. Ambas as interfaces exigem que você implemente o método getValue(), mas não está claro o que ele deve retornar: o peso ou o tamanho? Isso é bastante desagradável de se lidar.
"Concordo. Você quer implementar um método, mas não pode. Você já herdou um método com o mesmo nome da classe base. Está quebrado."
"Mas há boas notícias."
2) Expandir a visibilidade. Ao herdar um tipo, você pode expandir a visibilidade de um método. Isto é o que parece:
| código Java | Descrição |
|---|---|
|
|
|
Expandimos a visibilidade do método de protectedpara public. |
| Código | Por que isso é «legal» |
|---|---|
|
Está tudo ótimo. Aqui nem sabemos que a visibilidade foi estendida em uma classe descendente. |
|
Aqui chamamos o método cuja visibilidade foi estendida. Se isso não fosse possível, sempre poderíamos declarar um método no Tiger: Em outras palavras, não estamos falando de nenhuma violação de segurança. |
|
Se todas as condições necessárias para chamar um método em uma classe base ( Cat ) forem satisfeitas, então elas certamente serão satisfeitas para chamar o método no tipo descendente ( Tigre ). Porque as restrições na chamada do método eram fracas, não fortes. |
"Não tenho certeza se entendi completamente, mas vou lembrar que isso é possível."
3) Restringindo o tipo de retorno.
Em um método substituído, podemos alterar o tipo de retorno para um tipo de referência restrito.
| código Java | Descrição |
|---|---|
|
|
|
Substituímos o método getMyParente agora ele retorna um Tigerobjeto. |
| Código | Por que isso é «legal» |
|---|---|
|
Está tudo ótimo. Aqui nem sabemos que o tipo de retorno do método getMyParent foi ampliado na classe descendente. Como o «código antigo» funcionava e funciona. |
|
Aqui chamamos o método cujo tipo de retorno foi reduzido. Se isso não fosse possível, poderíamos sempre declarar um método em Tiger: Em outras palavras, não há violações de segurança e/ou violações de conversão de tipo. |
|
E tudo funciona bem aqui, embora tenhamos ampliado o tipo das variáveis para a classe base (Cat). Devido à substituição, o método setMyParent correto é chamado. E não há com o que se preocupar ao chamar o método getMyParent , pois o valor de retorno, embora seja da classe Tiger, ainda pode ser atribuído à variável myParent da classe base (Cat) sem problemas. Os objetos Tiger podem ser armazenados com segurança nas variáveis Tiger e Cat. |
"Sim. Entendi. Ao substituir métodos, você deve estar ciente de como tudo isso funciona se passarmos nossos objetos para um código que só pode manipular a classe base e não sabe nada sobre nossa classe. "
"Exatamente! Então a grande questão é por que não podemos restringir o tipo do valor de retorno ao substituir um método?"
"É óbvio que neste caso o código da classe base pararia de funcionar:"
| código Java | Explicação do problema |
|---|---|
|
|
|
Sobrecarregamos o método getMyParent e reduzimos o tipo de seu valor de retorno. Está tudo bem aqui. |
|
Então este código vai parar de funcionar. O método getMyParent pode retornar qualquer instância de um Object, porque na verdade ele é chamado em um objeto Tiger. E não temos um cheque antes da missão. Portanto, é totalmente possível que a variável myParent do tipo Cat armazene uma referência String. |
"Maravilhoso exemplo, Amigo!"
Em Java, antes de um método ser chamado, não há verificação se o objeto possui tal método. Todas as verificações ocorrem em tempo de execução. E uma chamada [hipotética] para um método ausente provavelmente faria com que o programa tentasse executar um bytecode inexistente. Isso levaria a um erro fatal e o sistema operacional fecharia o programa à força.
"Uau. Agora eu sei."
GO TO FULL VERSION