Continuando o artigo Desmistificando a Certificação SCJP6 Parte IV - Parte 1

Modificadores de Acesso de Membros


Veremos agora o que quer dizer alterar uma declaração de método ou atributo. Existe uma quantidade maior de opções quando lidamos com membros do que quando tratamos de classes.
Se uma classe pode utilizar apenas 2 dos 4 níveis de acesso, public ou default, os membros podem fazer uso de 4: public, private, default e protected.
A proteção default é a que se recebe quando não é digitado nenhum modificador de acesso na declaração do membro. Os tipos de controle de acesso protected e default têm comportamentos muito parecidos.
Existem 2 situações relacionadas ao acesso que precisam ser compreendidas: se o código de um método em uma classe pode ter acesso um membro de outra classe, que é o acesso sem herança, e se uma subclasse pode herdar um membro de sua superclasse, que é o acesso com herança.
Observe um exemplo do primeiro tipo de acesso, com classes de um mesmo pacote.

package devmedia;
class Teste1 {
    int atributo;
    int getAtributo() {
        return atributo;
    }
}
class Teste2 {
    public void metodoA() {
        Teste1 t1 = new Teste1();
        t1.atributo = 10;
        t1.getAtributo();
    }
}

O próximo tipo de acesso refere-se a quais membros de uma classe pai (superclasse), se é que há membros, uma subclasse pode ter acesso por meio da herança.
Relembre que uma subclasse herda membros; é como se a própria subclasse os possuísse declarados. Sendo assim, se uma subclasse herda um membro, ela possui esse membro. Veja o próximo exemplo.

package devmedia;
public class Mamifero {
    public String name;
    public String getName(){
        return name;
    }
}
class Cao extends Mamifero{
    public void metodo() {
        name = “Pastor”;
        System.out.println(getName());
    }
}

Membros public

Se um membro é declarado como public, isso quer dizer que todas as outras classes, independente de pacote, podem acessar o membro em questão. Assumiremos que a classe seja visível.
Existem três modos de acessar um método: invocando através de uma referência da classe, realizando a invocação na mesma classe e invocando o método herdado.

Membros default e protected

Um membro default só é acessado se a classe que o estiver acessando for pertencente ao mesmo pacote, já um membro protected é acessado apenas, através do mecanismo de herança, por uma classe filha (subclasse), mesmo se a subclasse estiver em outro pacote.
O exemplo a seguir lida com as classes no mesmo pacote.

package devmedia;
public class X{
    public int x;
}

package devmedia;
class Y{
    void enxergar(){
        X x = new X();
        x.x = 35;
    }
}

No exemplo seguinte, as classes estão em pacotes diferentes. Perceba, inicialmente, que o modificador de acesso do atributo da classe X foi alterado para protected.

package devemedia;
public class X{
    protected int x;
}

package another;
import devmedia.X;
class Y extends X{
    void enxergar(){
        x = 45;
    }
}

Sendo assim, quando pensarmos em acesso default, lembre-se de restrições de pacote. Sem exceção alguma. Entretanto, quando lidar com protected, tenha em mente default + filhos.
O que quer dizer, então, uma subclasse de pacote diferente ter acesso a um membro da superclasse? Quer dizer que a subclasse herda o membro. Não significa, porém, que a subclasse de fora do pacote possa ter acesso ao membro por meio de uma instância da superclasse.
Após a subclasse de fora do pacote herdar o membro protected, automaticamente ele torna-se private para qualquer código fora da subclasse, com exceção das próprias subclasses dessa subclasse.

Membros private

Membros declarados como private não podem ter acesso por código de nenhuma classe que não seja ela mesma.
A nível de exame, precisamos entender que uma subclasse não pode ter acesso, e nem usar os membros private de sua superclasse.

Mais Modificadores Não-Referentes a Acesso

Primeiro analisaremos os modificadores usados em métodos e em seguida os aplicados a atributos. Esta parte será finalizada com o conceito de static, aplicado a métodos e atributos.

Métodos final

A palavra-chave final não permite que um método possa ser sobreposto/sobrescrito em uma classe filha (subclasse).
Suponha um método da classe Math que retorne a raiz de um número. Sem desmerecer nossos conhecimentos matemáticos, mas, se Java proporcionasse a sobrescrição desse método, estaria aberta uma lacuna para que ele não mais executasse a operação exata. Ou seja, por garantia, este método é declarado como final.

Argumentos final

Os argumentos de métodos são as variáveis que aparecem entre parênteses, na definição de um método.
Argumentos de métodos são, basicamente, o mesmo que variáveis locais. Isso quer dizer que elas podem ter, também, o modificador final.
Nesta situação, alterar é o mesmo que atribuir outro valor à variável. Ou seja, um argumento final deve permanecer com o mesmo valor que o parâmetro tinha quando foi passado ao método.

Métodos abstract

Um método abstract é um que foi declarado como abstract e, em consequência disso, não possui implementação.
Declarações de métodos abstract não possuem nem mesmo chaves. Um método é declarado como abstract quando se deseja forçar as subclasses a implementarem este método.
Uma classe tem que ser declarada abstract se possuir ao menos um método abstract.
Qualquer classe concreta que estenda uma classe abstract tem que implementar todos os métodos abstract da superclasse.
Um método não pode nunca ser marcado como final e abstract e também nem como private e abstract simultaneamente. Os métodos abstract têm que ser implementados, ou seja, ser sobreposto por uma subclasse. Já os métodos com modificadores final e private não podem ser sobrepostos.

Métodos synchronized

A palavra-chave synchronized significa que um método só pode ser acessado por um thread por vez.
É necessário saber que o modificador synchronized só pode ser usado em métodos, não sendo permitido a sua utilização em classes e atributos. Uma declaração synchronized se assemelha com o seguinte:

    public synchronized Object retornarObject() { }

É importante também saber que o modificador synchronized pode vir seguido por qualquer um dos 4 níveis de controle de acesso.

Métodos native

O modificador native quer dizer que o método utilizado é implementado de forma dependente da plataforma, normalmente em C.

Métodos strictfp

Mesmo que não se declare uma classe strictfp, ainda assim é possível declarar um método individual como strictfp.
Esse modificador força os pontos flutuantes, e quaisquer operações com pontos flutuantes, a se integrarem ao padrão IEEE754.
Para a certificação, entretanto, não precisa-se saber nada a respeito desse modificador além da sua utilidade e do fato de que pode alterar uma declaração de classe ou de método e que uma variável nunca pode ser declarada como strictfp.

Métodos com Listas de Argumentos Variáveis

Uma das inovações do Java 5 é que você pode ter métodos com uma lista variável de argumentos.
Para que se declare um método usando um parâmetro vararg, se escreve após o tipo um sinal de reticências(...), um espaço e em seguida o nome do array que irá guardar os parâmetros recebidos. É viável ter outros parâmetros em um método que use um vararg. O vararg tem que ser o último parâmetro na assinatura do método e só existe a possibilidade de ter apenas um único vararg por método.
São exemplos de declarações corretas:

public class X{
    void executar(int... a) { }
    void executar2(double b, int... a) { }
    void executar3(String... y) { }
}

São exemplos de declarações incorretas:

void executar4(int a...)
void executar5(int... a, double... b) { }
void executar6(String... t, short z) { }


Declarações de Variáveis

Java é extremamente tipificada. Os tipos estão em todas as declarações. Existem 2 tipos de variáveis em Java: variáveis de referência e primitivos.
Um tipo primitivo possui 8 tipos: char, boolean, byte, short, int, long, float ou double. Uma vez que esteja declarado, o seu tipo primitivo não pode nunca ser alterado, embora, na maioria das situações, seu valor possa ser modificado.
As variáveis de referência são utilizadas para fazer referência a um objeto e possui um endereço.

Primitivos

As variáveis primitivas podem ser declaradas como variáveis static (de classe), atributos, parâmetros de métodos ou variáveis locais. Pode-se declarar um ou mais primitivos do mesmo tipo em uma mesma instrução ou linha.
É necessário entender que, para os tipos inteiros, a sequência de menor capacidade para o de maior capacidade é: byte short, int e long, e que doubles são maiores que floats.

Variáveis de referência

As variáveis de referência, assim como as variáveis primitivas, podem ser declaradas como variáveis static, atributos, parâmetros de métodos ou variáveis locais. Pode-se declarar uma ou mais variáveis de referência, do mesmo tipo, na mesma linha.

Atributos ou variáveis de instância

As variáveis de instância são declaradas dentro da classe, mas fora de qualquer método e só são inicializadas quando um objeto da classe for instanciado.
Para o exame da certificação, precisa-se saber que os atributos podem utilizar qualquer um dos níveis de acesso, ou seja que podem ser definidos com qualquer um dos três modificadores de acesso: public, private e protected. Atributos podem ser marcados como final, transient, static e volatile. Além disso, atributos não podem ser declarados como abstract, synchronized, strictfp, native e static.

Variáveis Locais(Automáticas, de Pilha ou de Método)

As variáveis locais são as declaradas dentro de um método. Isso quer dizer que a variável não apenas é declarada dentro de um método, como também é obrigatoriamente inicializada dentro dele.
As declarações de variáveis locais não utilizam a maioria dos modificadores que podem ser aplicados aos métodos e atributos. Entretanto, elas podem ser declaradas como final.
O ponto crucial é perceber que a variável de método precisa ser inicializada antes de ser utilizada. O compilador rejeitará qualquer código que tente acessar uma variável local que não tenha sido inicializada anteriormente, uma vez que as variáveis locais não recebem valores default.

Arrays

No Java, arrays guardam múltiplas variáveis do mesmo tipo, ou variáveis que são todas subclasses do mesmo tipo. Os arrays podem armazenar referências a objetos ou primitivos.
Para a certficação, existem 3 conceitos que você deve ter domínio: como declarar, inicializar e instanciar um array.
Veja exemplos de declaração de arrays.

int [ ] pagamentos;
int pagamentos [ ];
String [ ] bermudas;
String bermudas [ ];
Thread [ ] [ ] consultas;
Thread [ ] consultas [ ];

Jamais é válido incluir o tamanho do array na sua declaração. Temos que ter cuidado com questões que incluam código como esse:

    int [8] valores;

Este código não compilaria. Não se esqueça: a JVM não aloca espaço até que se instancie de fato o objeto array. Só assim é que o tamanho faz diferença.

Variáveis final

Marcar uma variável como final não permite o reuso dessa variável após ela ter sido inicializada com um valor. Para primitivos, é o mesmo que, uma vez que a variável tenha recebido o valor, esse valor não poderá mais ser modificado.

Variáveis transient

Ao declarar um atributo como transient, informa-se à JVM para ignorar essa variável quando se tentar serializar o objeto que o contém.

Variáveis volatile

O modificador volatile diz à JVM que um thread que acesse a variável deve reconciliar a sua cópia private da variável com a cópia master que está na memória. Para a prova, tudo o que se precisa saber sobre volatile é que, assim como transient, esse modificador é aplicado apenas para a atributos.

Variáveis e métodos static

O modificador static é utilizado para criar métodos e variáveis que existirão independente de quaisquer instâncias criadas para a classe. Existirá, em memória, apenas uma cópia do membro static que independe da quantidade de instâncias criadas.
Pode-se marcar como static: métodos, atributos, classes aninhadas dentro de outra classe, mas não dentro de um método além de blocos de inicialização. O que não se pode marcar com static são: construtores, classes(a não ser que sejam classes aninhadas), classes internas locais de métodos, interfaces, métodos de classes internas, além de variáveis locais.

Enum

A utilização de enums pode auxiliar a redução de bugs do seu código. Por exemplo, na sua aplicação de automação de bares, poderiamos desejar reduzir os tamanhos possíveis de petiscos a: GRANDE, ENORME e GIGANTE. Se deixar a possibilidade que um pedido de tamanho MÉDIO seja efetuado, isso causaria, provavelmente, uma falha.
Não é obrigado que as constantes enums estejam escritas totalmente em caixa alta, porém, tomando emprestado da convenção de códigos iniciados na Sun, que estabelece que as constantes tenham seus nomes em maiúsculas, é uma boa decisão fazê-lo aqui.
Os enums podem ser marcados como as suas próprias classes separadas, ou como membros de classes, mas não podem ser declarados em um método.
O mais importante a ficar guardado em nossas mentes é que o enum pode ser declarado apenas com nível de acesso default ou public, assim como uma classe não-interna.
Regras para o uso de enums fora de uma classe:

- 1º declare o enum, com as suas constantes. O ponto-e-vírgula não é obrigatório.
- Em seguida crie uma classe que tenha como atributo o mesmo enum.
- Em uma classe de executável (de teste), instancie um objeto da classe e use o enum como se fosse um membro dessa classe.

package devmedia;
class Bar {
    enum TamanhoDoPetisco { GRANDE, ENORME, GIGANTE }
    TamanhoDoPetisco tp;
}
public class Executavel {
    public static void main(String args[ ])
    {
        Bar b = new Bar();
        b.tp = Bar.TamanhoDoPetisco.GIGANTE;
    }
}

Declarando Construtores, Métodos e Variáveis em um enum

O modo mais simples é tratar os valores dos seus enums(GRANDE, ENORME e GIGANTE) como objetos que podem ter seus próprios atributos. Sendo assim, poderemos atribuir esses valores no instante em que os enums forem inicializados, passando um valor através do construtor do enum. Analise o código a seguir.

package devmedia;

enum TamanhoDoPetisco { GRANDE(500), ENORME(800), GIGANTE(1200);
private int peso;
TamanhoDoPetisco (int peso)
{
    this.peso = peso;
}
public int getPeso()
{
    return peso;
}

}
class Bar {
    TamanhoDoPetisco tp;
}
public class Main {
    public static void main (String args[ ])
    {
        Bar bar = new Bar();
        bar.tp = TamanhoDoPetisco.GIGANTE;
        int a = bar.tp.getPeso();
    }
}

É essencial saber que é impossível invocar o construtor de um enum de forma direta. Esse construtor é invocado de modo automático, com argumentos que se define após o valor da constante.

Mini-teste.

1. Analise o seguinte trecho de código:

package devmedia;
interface Ajustavel {
    public void ajustar();
}
interface Modificavel {
    void modificar();
}
interface Acrescentavel extends Modificavel, Ajustavel {
    int acrescentar();
}
class Executavel implements Acrescentavel {
    public void ajustar(){ }
    int calcular(){ return 11; }
    double somar(double x, double y){ return x + y; }
    public int acrescentar(){ return 11; }
    public void modificar(){ }
}

Qual(is) método(s) deve(m), obrigatoriamente, ser implementado(s) pela classe Executavel?

a)ajustar(), somar() e modificar()
b)ajustar(), modificar() e acrescentar()
c)ajustar() somente
d)calcular() somente
e)acrescentar() somente

2. Qual(is) modificador(es) pode(m) ser adicionado(s) a um método de interface?

a)protected
b)private
c)abstract
d)final
e)static
f)public

3. Analise o seguinte trecho de código:

interface Basic {
boolean n1();
byte n2(short a);
}

Qual(is) fragmento(s) de código compilará(ão)

a)abstract class Class1 extends Basic{ public Boolean n1() return true;}
b)abstract class Class1 implements Basic {}
c)abstract class Class1 implements Basic { public boolean n1() return (false);}
d)class Class1 implements Basic { boolean n1() return true;} byte me(short a) { return 65;}
e)interface Basic2 implements Basic {}

4. Analise o seguinte trecho de código:

class Blablabla {
    public static void main (String args[ ]) {
        executar(1);
        executar(1,2);
    }
    //adicione código aqui
}

Se inserida independente na linha em que está o comentário, qual(is) alternativa(s) compilará(ão)?

a)static void executar(int x, int... y)
b)static void executar(int... x) {}
c)static void executar(int [ ] x) {}
d)static void executar(int x...) {}


5. Das declarações a seguir, quais são válidas?

a)short [7] a1;
b)short b1 [7];
c)short [ ] b [ ] [ ];
d)short [ ] c1 = [7];
e)short a [ ];
f)short [ ] c;

Gabarito comentado

1. Resposta correta: B
Uma interface estende outras interfaces e isso é feito através da palavra-chave extends. Sendo assim, a sintaxe de declaração da interface Acrescentavel está exata.
A classe Executavel que é concreta tem obrigação de implementar todos os métodos da interface Acrescentavel. A fato é que Acrescentavel estende as interfaces Modificavel e Ajustavel. Então todos os métodos, de todas as interfaces citadas, precisam ser implementados pela classe Executavel, o que corresponde ao item B.

2. Resposta correta: C e F
Toda interface é implicitamente abstrata e pública. Temos que ter um grande cuidado com questões que dizem respeito à declaração de interfaces e usam esses modificadores de forma explicita. Isso ocorre porque no dia-a-dia é muito difícil encontrar os modificadores implícitos de interfaces presentes em uma declaração.

3. Resposta correta: B e C
Sem ser abstrata, qualquer classe que implementar a interface Basic tem obrigação de implementar os métodos n1() e n2(), seguindo suas assinaturas. Uma interface estende outra interface e isso é feito através do uso da palavra-chave extends. Uma classe implementa uma interface e isso é feito por meio da palavra-chave implements.
Todo método declarado em uma interface é implicitamente public. Então, quando existe sobreposição, a classe que sobrepõe os métodos da interface deve marcá-lo como public. Note que isso não acontece com a classe Class1. Além disso, na interface não existe o método byte me(short a) e os dois fatos combinados eliminam a opção D.

4. Resposta correta: A, B e C
Para sobrecarregar um método estático é preciso que o método sobrecarregado, além de ser estático, tenha sua assinatura modificada. Então, as opções A, B(sintaxe correta de varargs) e C(que declara um array de ints) estão corretas.

5. Resposta correta: C, E e F
Ao declararmos um array não definimos seu tamanho. Desse modo, as opções C, E e F estão certas. A alternativa D está inválida devido à sintaxe.

Leia todos artigos da série