1. Visão geral dos modificadores de acesso
Em Java há quatro níveis de acesso para classes, campos, métodos e construtores:
| Modificador | Onde é acessível? |
|---|---|
|
Em qualquer lugar: dentro da classe, em outras classes, em outros pacotes |
|
Dentro da classe, em subclasses (herdeiras), em outras classes do mesmo pacote |
|
Somente dentro do pacote (quando o modificador não é especificado explicitamente) |
|
Somente dentro da classe atual |
Vamos analisar cada um em mais detalhes — com exemplos, piadas e reviravoltas inesperadas.
public — acesso público
public — é como um aviso para o mundo inteiro: “Entrada liberada para todos!”. Se uma classe, campo, método ou construtor é declarado como public, ele pode ser acessado a partir de qualquer outra classe, até mesmo de outro pacote.
Exemplo:
public class Cat {
public String name;
public void sayMeow() {
System.out.println("Miau!");
}
}
Esta classe e seus campos/métodos podem ser acessados de qualquer lugar. Isso é conveniente se você quer que sua classe esteja disponível para todos: por exemplo, se estiver escrevendo uma biblioteca.
Mas! Campos públicos nem sempre são uma boa ideia (veja a aula anterior). Normalmente, apenas os métodos que devem ser acessíveis de fora são public, enquanto os campos quase sempre permanecem private.
private — acesso apenas dentro da classe
private — é como um cofre com combinação: ninguém além da própria classe pode acessar esses membros. Nem mesmo as subclasses veem campos e métodos privados!
Exemplo:
public class Cat {
private String secretName;
public void setSecretName(String name) {
secretName = name;
}
public String getSecretName() {
return secretName;
}
}
Aqui, secretName não pode ser lido ou alterado diretamente a partir de outra classe. Apenas a própria Cat pode fazer isso (ou seus métodos). Isso é a base do encapsulamento: escondemos os detalhes internos e fornecemos acesso a eles apenas por meio de métodos.
protected — acesso protegido
protected — é como um passe VIP: o acesso é permitido à classe, às suas subclasses (mesmo se estiverem em outros pacotes) e a todas as classes dentro do pacote atual.
Exemplo:
public class Animal {
protected int age;
protected void growOlder() {
age++;
}
}
Agora qualquer classe que herde de Animal poderá acessar o campo age e o método growOlder().
public class Cat extends Animal {
public void haveBirthday() {
growOlder();
System.out.println("O gato completou " + age + " anos!");
}
}
Além disso, todas as classes dentro do mesmo pacote têm acesso aos membros protected.
(package-private) — acesso dentro do pacote
Se você não especificar nenhum modificador de acesso, o membro da classe é considerado package-private (ou “acesso padrão”). É como uma porta sem tranca, mas apenas para os de casa: o acesso é permitido apenas às classes do mesmo pacote.
Exemplo:
class Dog {
String name; // package-private
void bark() { // package-private
System.out.println("Au!");
}
}
A classe Dog, seu campo name e o método bark() estão acessíveis apenas dentro do mesmo pacote. Tentar acessá-los de outro pacote causará um erro de compilação.
2. Aplicando modificadores a campos e métodos
Por que os campos quase sempre são private
Os campos de uma classe representam seu estado interno. Se você os deixar públicos, qualquer código externo poderá modificá-los a qualquer momento. É como permitir que crianças desconhecidas brinquem com seus móveis como se fossem Lego: um dia você acorda e a geladeira está virada de ponta-cabeça no banheiro.
Exemplo de encapsulamento ruim:
public class Person {
public String name;
public int age;
}
Exemplo de bom encapsulamento:
public class Person {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Métodos (getters e setters) permitem controlar como as classes externas podem alterar os campos. Por exemplo, você pode proibir idade negativa.
Quando tornar métodos public, protected ou package-private
- public — quando o método deve estar acessível a todos. Geralmente é a funcionalidade principal da classe.
- protected — quando o método é necessário apenas para subclasses ou dentro do pacote (por exemplo, métodos auxiliares que podem ser úteis em classes filhas).
- package-private — quando o método é necessário apenas dentro do pacote e não deve ficar acessível externamente (por exemplo, detalhes internos de implementação).
- private — quando o método é usado apenas dentro da própria classe (por exemplo, métodos auxiliares para a lógica interna).
Exemplo:
public class BankAccount {
private double balance;
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
protected void applyInterest() {
balance *= 1.05;
}
void internalAudit() {
// package-private: apenas para classes dentro do pacote
}
private void logAction(String action) {
// apenas para necessidades internas da classe
}
}
3. Exemplos de código: classe com diferentes níveis de acesso
Vamos criar uma classe que tenha de tudo: membros públicos, privados, protegidos e de pacote. Ao mesmo tempo, vamos tentar acessá-los a partir de outras classes e ver o que acontece.
package zoo;
public class Animal {
public String publicName = "Disponível para todos";
protected String protectedName = "Somente para herdeiros e para o pacote";
String packageName = "Somente para o pacote";
private String privateName = "Somente para Animal";
public void publicMethod() {
System.out.println("Método público");
}
protected void protectedMethod() {
System.out.println("Método protegido");
}
void packageMethod() {
System.out.println("Método de pacote");
}
private void privateMethod() {
System.out.println("Método privado");
}
}
Agora vamos tentar acessar esses membros a partir de outra classe no mesmo pacote:
package zoo;
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
System.out.println(animal.publicName); // OK
System.out.println(animal.protectedName); // OK
System.out.println(animal.packageName); // OK
System.out.println(animal.privateName); // Erro: private
animal.publicMethod(); // OK
animal.protectedMethod(); // OK
animal.packageMethod(); // OK
animal.privateMethod(); // Erro: private
}
}
Agora vamos tentar acessar esses membros a partir de outro pacote:
package other;
import zoo.Animal;
public class Test {
public static void main(String[] args) {
Animal animal = new Animal();
System.out.println(animal.publicName); // OK
System.out.println(animal.protectedName); // Erro: protected
System.out.println(animal.packageName); // Erro: package-private
System.out.println(animal.privateName); // Erro: private
animal.publicMethod(); // OK
animal.protectedMethod(); // Erro: protected
animal.packageMethod(); // Erro: package-private
animal.privateMethod(); // Erro: private
}
}
Conclusão:
- public — acessível em qualquer lugar.
- protected — acessível dentro do pacote e em subclasses (inclusive de outros pacotes, se via herança).
- package-private — somente dentro do pacote.
- private — somente dentro da classe.
4. Boas práticas: como escolher o modificador de acesso
Minimize a visibilidade
Quanto menos código puder ver seu campo ou método, melhor. Exponha apenas o que for realmente necessário ao código externo. Isso é chamado de princípio do menor privilégio (principle of least privilege).
- Campos quase sempre devem ser private. Exceções — apenas para constantes reais (public static final), mas falaremos mais sobre isso nas próximas aulas.
- Métodos são public apenas se fizerem parte da interface externa da classe.
- Métodos auxiliares (lógica interna) — private.
- Métodos para herdeiros — protected.
- Métodos de serviço internos do pacote — package-private.
Por que isso é importante?
- Qualquer mudança nos detalhes de implementação pode quebrar o código de terceiros se esses detalhes estiverem expostos.
- A classe se torna mais difícil de testar e manter.
- Erros acidentais (por exemplo, modificar um campo indevidamente) podem levar a bugs.
Às vezes iniciantes pensam: “Pra que essa complicação toda? Deixa tudo public!” Mas depois, quando o projeto cresce, é preciso reescrever metade do programa só porque alguém mudava os campos da classe diretamente.
5. Erros comuns ao trabalhar com modificadores de acesso
Erro nº 1: Deixar campos public ou package-private por padrão.
Se você não especificar um modificador, o campo ou método ficará acessível a todas as classes dentro do pacote. Isso pode levar a situações inesperadas se alguém começar a alterar seus campos diretamente.
Erro nº 2: Tentar acessar um membro private a partir de outra classe.
O compilador não permitirá — você terá um erro. Mas se você resolver contornar isso via reflection (reflexão) — bem-vindo ao mundo de bugs e quedas inesperadas.
Erro nº 3: Public demais.
Se você declarar tudo como public, a classe fica parecida com uma caixa aberta cheia de fios — qualquer um pode puxar o fio errado e quebrar tudo.
Erro nº 4: Não usar protected para métodos necessários apenas às subclasses.
Se um método é necessário apenas para extensão em classes filhas, declare-o protected, e não public.
Erro nº 5: Visibilidade package-private implícita.
Às vezes esquecemos de especificar o modificador, e o método fica acessível a todo o pacote. Isso pode ser uma surpresa se você achava que ele era private.
GO TO FULL VERSION