Java Modificadores de Acesso

Até aqui, qualquer parte do programa podia ler e mudar qualquer atributo: conta.saldo = -1000000; compilava sem reclamar. Os modificadores de acesso são palavras colocadas antes de classes, atributos, métodos e construtores para dizer quem pode usá-los. São quatro níveis: public, protected, o padrão (sem palavra nenhuma) e private.

Você vai aprender
  • O que cada um dos quatro níveis permite, com uma tabela de visibilidade.
  • Como private protege um atributo e o erro real has private access.
  • Métodos private como "ajudantes internos" da classe.
  • O acesso padrão (de pacote) e o erro is not public … cannot be accessed from outside package.
  • Quais modificadores valem para classes e quais são os modificadores que não são de acesso (static, final, abstract).

A ideia antes do código: as portas da escola

A escola da Ana tem lugares com regras de entrada diferentes:

LugarQuem entraModificador
O pátio e a recepçãoQualquer pessoa, de qualquer lugarpublic ("público")
A sala dos professoresQuem trabalha naquele prédio, e também professores de outras unidades que são "da família" da escolaprotected ("protegido")
O corredor internoSó quem é do mesmo prédiopadrão, sem palavra ("de pacote")
O cofre da diretoriaSó a diretoriaprivate ("privado")

Em Java, o "prédio" é o pacote (uma pasta de classes; veja Pacotes e import), a "família" são as subclasses (veja Herança) e a "diretoria" é a própria classe.


A tabela de visibilidade

Esta é a tabela que todo programador Java consulta. Cada linha é um modificador; cada coluna, um lugar de onde alguém tenta usar o membro (atributo, método ou construtor):

ModificadorNa própria classeMesmo pacoteSubclasse em outro pacoteQualquer lugar
publicsimsimsimsim
protectedsimsimsimnão
(nenhum) — padrão / de pacotesimsimnãonão
privatesimnãonãonão

Lida de cima para baixo, a tabela vai do mais aberto ao mais fechado. Na prática, o dia a dia usa quase só os extremos: private para atributos e public para os métodos que formam a "porta de entrada" da classe. O padrão e o protected aparecem em situações específicas.

Até agora, tudo era "padrão"

Nas aulas anteriores, os atributos e métodos não tinham modificador: eram de acesso padrão. Como todos os exemplos ficavam na mesma pasta, sem declaração de pacote, estavam no mesmo "pacote sem nome" e se enxergavam. Por isso nunca houve erro de acesso.


private: só a própria classe

A Ana marcou o saldo da conta como private. Agora, quem está fora da classe Conta não pode tocar nele:

class Conta {
    String titular;
    private double saldo;
}

public class Main {
    public static void main(String[] args) {
        Conta c = new Conta();
        c.saldo = 1000000;
        System.out.println(c.saldo);
    }
}
$ javac Main.java
Main.java:9: error: saldo has private access in Conta
        c.saldo = 1000000;
         ^
Main.java:10: error: saldo has private access in Conta
        System.out.println(c.saldo);
                            ^
2 errors

"saldo tem acesso privado em Conta". Nem escrever nem ler: o main está em outra classe. Então como alguém deposita ou consulta? Pelos métodos da própria classe, que podem usar o atributo privado à vontade:

class Conta {
    String titular;
    private double saldo;

    void depositar(double valor) {
        if (valor > 0) {
            saldo = saldo + valor;
        }
    }

    double consultarSaldo() {
        return saldo;
    }
}

public class Main {
    public static void main(String[] args) {
        Conta c = new Conta();
        c.titular = "Ana";
        c.depositar(500);
        c.depositar(-1000);
        System.out.println(c.titular + ": " + c.consultarSaldo());
    }
}
$ javac Main.java && java Main
Ana: 500.0          « o depósito negativo foi ignorado pela regra do método
private double saldo;
O modificador vem antes do tipo. Só o código escrito dentro das chaves de class Conta pode usar saldo.
void depositar(double valor)
Está dentro da classe: pode mexer em saldo. E impõe uma regra: só aceita valores positivos.
double consultarSaldo()
Deixa ler o saldo sem deixar mudá-lo.
c.depositar(-1000);
Quem está fora não consegue estragar o saldo: o único caminho passa pela regra.

Esse é o coração do encapsulamento, o primeiro pilar da POO: os dados ficam escondidos e só mudam por métodos que conferem o que está acontecendo. A aula Encapsulamento mostra o padrão completo, com getters e setters.

Missão (provoque o erro): torne o atributo estoque da classe Fruta privado e veja o javac recusar a linha do main que tenta mudá-lo. A missão está cumprida quando aparecer has private access.

class Fruta {
    String nome;
    int estoque;
}

public class Main {
    public static void main(String[] args) {
        Fruta f = new Fruta();
        f.nome = "maçã";
        f.estoque = -50;
        System.out.println(f.nome + ": " + f.estoque);
    }
}
class Fruta {
    String nome;
    private int estoque;
}

public class Main {
    public static void main(String[] args) {
        Fruta f = new Fruta();
        f.nome = "maçã";
        f.estoque = -50;
        System.out.println(f.nome + ": " + f.estoque);
    }
}

Métodos private: ajudantes internos

Métodos também podem ser privados. É útil para pedaços de lógica que só fazem sentido dentro da classe, como uma conferência usada por outros métodos:

class Conta {
    private double saldo;
    private double limite = 200;

    boolean sacar(double valor) {
        if (!podeSacar(valor)) {
            System.out.println("Saque de " + valor + " recusado.");
            return false;
        }
        saldo = saldo - valor;
        System.out.println("Saque de " + valor + " feito. Saldo: " + saldo);
        return true;
    }

    private boolean podeSacar(double valor) {
        return valor > 0 && valor <= saldo + limite;
    }
}

public class Main {
    public static void main(String[] args) {
        Conta c = new Conta();
        c.sacar(150);
        c.sacar(100);
    }
}
$ javac Main.java && java Main
Saque de 150.0 feito. Saldo: -150.0     « usou o limite (cheque especial)
Saque de 100.0 recusado.                « passaria de 200 negativos

Se o main tentar chamar o ajudante:

$ javac Main.java
Main.java:25: error: podeSacar(double) has private access in Conta
        c.podeSacar(10);
         ^
1 error

Vantagem: a Ana pode mudar ou apagar podeSacar amanhã sem quebrar nenhuma outra classe, porque ninguém de fora depende dele.


Padrão (de pacote) e public

A diferença entre o acesso padrão e o public só aparece quando há pacotes diferentes. Um pacote é uma pasta de classes com uma declaração package no topo de cada arquivo; você vai estudá-los em Pacotes e import. Para ver os modificadores funcionando, a Ana separou o programa em duas pastas:

modif/
├── banco/
│   └── Conta.java      (package banco;)
└── app/
    └── Main.java       (package app;)
package banco;

public class Conta {
    public String titular;
    double saldo;
    private String senha = "1234";

    public Conta(String titular) {
        this.titular = titular;
    }

    public void depositar(double valor) {
        saldo = saldo + valor;
    }

    public double consultarSaldo() {
        return saldo;
    }
}
package app;

import banco.Conta;

public class Main {
    public static void main(String[] args) {
        Conta c = new Conta("Ana");
        c.depositar(500);
        System.out.println(c.titular + ": " + c.consultarSaldo());
        System.out.println(c.saldo);
        System.out.println(c.senha);
    }
}
$ javac -d out banco/Conta.java app/Main.java
app/Main.java:10: error: saldo is not public in Conta; cannot be accessed from outside package
        System.out.println(c.saldo);
                            ^
app/Main.java:11: error: senha has private access in Conta
        System.out.println(c.senha);
                            ^
2 errors
Membro de ContaModificadorO Main (pacote app) pode usar?
titularpublicSim
construtor, depositar, consultarSaldopublicSim
saldopadrãoNão: is not public in Conta; cannot be accessed from outside package
senhaprivateNão: has private access in Conta

Tirando as duas linhas proibidas, o programa compila e roda. A opção -d out manda o javac colocar os .class na pasta out, organizados por pacote, e -cp out diz ao java onde procurá-los:

$ javac -d out banco/Conta.java app/Main.java
$ java -cp out app.Main
Ana: 500.0

Repare que a classe Conta e o seu construtor também precisaram de public: sem isso, o pacote app não conseguiria nem enxergar a classe nem criar contas.

E o protected?

protected é o padrão "com um extra": vale para o mesmo pacote e para as subclasses, mesmo em outro pacote. Ele só faz sentido com herança, então fica para a aula Herança (e super). Por enquanto, guarde a posição dele na tabela: entre o padrão e o public.


Modificadores em classes

Uma classe "de primeiro nível" (que não está dentro de outra) só aceita duas opções:

DeclaraçãoQuem enxerga a classeRegra
public class ContaTodo mundoTem de estar no arquivo Conta.java; só uma por arquivo
class ContaSó o mesmo pacotePode dividir o arquivo com outras classes

private e protected não fazem sentido para uma classe de primeiro nível (privada de quem?):

$ javac Main.java
Main.java:1: error: modifier private not allowed here
private class Conta {
        ^
1 error

Classes declaradas dentro de outras podem ser privadas; isso aparece em Classes Internas e Anônimas.


Modificadores que não são de acesso

Existem outras palavras que aparecem no mesmo lugar, antes do tipo, mas que não falam de quem acessa, e sim de como a coisa funciona. Cada uma tem a sua aula:

ModificadorEm uma fraseAula
staticPertence à classe, não a cada objetostatic
finalNão pode mudar: variável que não recebe outro valor, método que não pode ser sobrescrito, classe que não pode ser herdadafinal
abstractIncompleto de propósito: classe que não pode ser instanciada, método sem corpoClasses Abstratas
synchronizedSó uma thread por vez executa o métodosynchronized e Locks

Um membro pode ter um modificador de acesso e vários outros: public static void main, private static final double TAXA = 0.05;. A ordem não muda o significado, mas a convenção é: acesso primeiro, depois static, depois final. Só não dá para usar dois modificadores de acesso juntos:

$ javac Main.java
Main.java:2: error: illegal combination of modifiers: public and private
    public private double saldo;
                          ^
1 error

Qual usar? (regras práticas)

O quêUsePor quê
AtributosprivateNinguém de fora estraga o estado; mudanças passam por métodos
Métodos que outras classes devem usarpublicSão a "porta de entrada" da classe
Métodos auxiliares, internosprivateLiberdade para mudá-los depois
Construtorespublic (em geral)Para outras classes criarem objetos
Constantes compartilhadaspublic static finalNão mudam, então não há risco em expor (veja Constantes)
Na dúvidaO mais restrito que funcioneAbrir depois é fácil; fechar algo que outros já usam quebra o código deles

Quando dá errado

"… has private access in …"
$ javac Main.java
Main.java:9: error: saldo has private access in Conta
        c.saldo = 1000000;
         ^

O que aconteceu: o código está fora da classe Conta e tentou usar um membro private dela (atributo, método ou construtor).

Como resolver: não tire o private por reflexo. Use (ou crie) um método público da classe que faça o que você precisa: c.depositar(…), c.consultarSaldo(). Veja Encapsulamento.

"… is not public in …; cannot be accessed from outside package"
$ javac -d out banco/Conta.java app/Main.java
app/Main.java:10: error: saldo is not public in Conta; cannot be accessed from outside package
        System.out.println(c.saldo);
                            ^

O que aconteceu: o membro tem acesso padrão (nenhum modificador) e o código que tenta usá-lo está em outro pacote. A mesma mensagem aparece para classes e construtores sem public.

Como resolver: se o membro deve mesmo ser usado de fora, marque-o public (ou ofereça um método public que o use). Se não deve, o erro está certo: reorganize o código.

"modifier private not allowed here"

O que aconteceu: private ou protected numa classe de primeiro nível.

Como resolver: tire o modificador; para classes, use public ou nada. Atenção a um primo deste erro: variáveis locais (dentro de um método) não aceitam modificador de acesso, e private int x = 0; no meio do main produz uma cascata de mensagens que começa com error: illegal start of expression. Tire o private: o escopo do método já esconde a variável.

"illegal combination of modifiers"

O que aconteceu: dois modificadores de acesso no mesmo membro (public private), ou outra combinação proibida (como abstract final).

Como resolver: escolha um só nível de acesso.


Referência

ModificadorVisível emUso típico
publicTodo lugarClasses, construtores e métodos da "porta de entrada"
protectedMesmo pacote + subclassesMembros pensados para quem herda
(nenhum)Mesmo pacoteClasses e métodos de uso interno de um pacote
privateSó a própria classeAtributos e métodos auxiliares
Erro has private accessUso de membro private fora da classe
Erro is not public in …; cannot be accessed from outside packageUso de membro padrão fora do pacote

Missão final: o termômetro protegido

Missão: torne o atributo graus privado e crie um método public void ajustar(double novo) que só aceite valores a partir de -273.15 (o zero absoluto); para valores menores, imprima Valor recusado: seguido do valor. O main já usa ajustar e mostrar.

class Termometro {
    double graus;

    public void mostrar() {
        System.out.println("Temperatura: " + graus);
    }
}

public class Main {
    public static void main(String[] args) {
        Termometro t = new Termometro();
        t.ajustar(22.5);
        t.ajustar(-300);
        t.mostrar();
    }
}
class Termometro {
    private double graus;

    public void ajustar(double novo) {
        if (novo < -273.15) {
            System.out.println("Valor recusado: " + novo);
            return;
        }
        graus = novo;
    }

    public void mostrar() {
        System.out.println("Temperatura: " + graus);
    }
}

public class Main {
    public static void main(String[] args) {
        Termometro t = new Termometro();
        t.ajustar(22.5);
        t.ajustar(-300);
        t.mostrar();
    }
}

Exercícios

1. O mais fechado

Qual modificador deixa um atributo visível só dentro da própria classe?

2. Declarar protegido

Complete a declaração de um atributo double chamado saldo, privado:

3. O mais aberto

Qual modificador deixa um método visível em qualquer lugar do programa?

4. Um atributo sem modificador nenhum pode ser usado por…

5. A classe Conta tem private double saldo e double consultarSaldo() { return saldo; }. O main (em outra classe do mesmo pacote) pode fazer c.consultarSaldo()?

6. Qual destas declarações de classe de primeiro nível é válida?


Resumo

  • Modificadores de acesso dizem quem pode usar uma classe, atributo, método ou construtor.
  • public: todos. protected: pacote + subclasses. Padrão (nada): só o pacote. private: só a própria classe.
  • Regra prática: atributos private, métodos da "porta de entrada" public, ajudantes private.
  • Erros: has private access (membro privado usado de fora) e is not public in …; cannot be accessed from outside package (membro padrão usado de outro pacote).
  • Classes de primeiro nível: só public ou nada.
  • static, final e abstract também são modificadores, mas não de acesso; cada um tem a sua aula.