Anotações OCJP

Minhas anotações para o exame da Oracle (OCJP)

Arquivos Mensais: maio 2011

Definindo passagem de parâmetros em Java

Neste artigo, vamos falar um pouco sobre as referências passadas por parâmetro para o método.

Sabia que existem algumas regras essenciais para a passagem de valores para métodos? Mais ainda, que existem diferenças essenciais entre passar um valor primitivo para um método e passar um objeto para um método?

Veja o exemplo abaixo.

class Pessoa{
  private String nome;
  public void setNome(String nome){
    this.nome = nome;
  }
  public String getNome() {
    return nome;
  }
}

class Referencia{
  public int m(int paramInt){
    paramInt = 20;
    return paramInt;
  }
  public Pessoa m1(Pessoa p){
    p.setNome("Bruno");
    return p;
  }
}

public class TesteReferencias {
  public static void main(String[] args) {
    //passando copia do valor de i para o metodo
    int i = 10;
    Referencia rf = new Referencia();
    System.out.println("Valor retornado: " +rf.m(i));
    System.out.println("Valor de i: " + i);

    //passando copia da referencia do objeto para o objeto
    Pessoa p = new Pessoa();
    p.setNome("Roberto");
    Referencia rf1 = new Referencia();
    System.out.println("Valor retornado: " + ((Pessoa)rf1.m1(p)).getNome());
    System.out.println("Valor de p.nome: " + p.getNome());
  }
}

As duas primeiras linhas da saída podem não ser surpresa:

Valor retornado: 20
Valor de i: 10

Quando se passa um valor de um atributo do tipo primitivo para um método, uma cópia do valor que está neste atributo é passada. Desta forma, qualquer alteração realizada dentro do método será feita em um valor novo, que não tem ligação com o atributo passado por parâmetro.

Já as duas últimas linhas da saída podem surpreender:

Valor retornado: Bruno
Valor de p.nome: Bruno

Quando se passa um objeto para um método, é passada a cópia da referência do objeto para qual a referência está apontando. Desta forma, qualquer alteração realizada através da cópia da referência no objeto será realizada no objeto, uma vez que esta cópia tem relação com o objeto instanciado pela referência copiada.

Porém, preste muita atenção no que foi dito em relação a passagem de parâmetros de objeto para métodos. Lembre-se sempre que é feita uma cópia da referência. Caso esta cópia da referência seja modificada para referenciar um outro objeto, todas as alterações dirão respeito apenas a este outro objeto referenciado. O exemplo abaixo deixará mais claro o que foi dito.

//classe Pessoa
class Referencia{
  //metodos m e m1
  public Pessoa m2(Pessoa p){
    //copia da referencia aponta para novo objeto
    p = new Pessoa();
    p.setNome("Bruno");
    return p;
  }
}

public class TesteReferencias {
  public static void main(String[] args) {
    //instancia de um objeto com nome alessandro, modificacao no metodo m2
    Pessoa p2 = new Pessoa();
    p2.setNome("Alessandro");
    Referencia rf2 = new Referencia();
    System.out.println("Valor retornado: " + ((Pessoa)rf2.m2(p2)).getNome());
    System.out.println("Valor de p2.nome: " +p2.getNome());
  }
}

Seria então uma surpresa o retorno abaixo?

Valor retornado: Bruno
Valor de p2.nome: Alessandro

Descrevendo em detalhes a execução no método main:

1. Foi instanciado um objeto da classe Pessoa.

2. O atributo nome deste objeto é modificado para “Alessandro”

3. A classe referência é instanciada.

4. O método m2 da classe Referencia é chamado, foi passada a cópia da referência do objeto Pessoa anteriormente instanciado.

5. (No método m2) É instanciado um novo objeto da classe Pessoa

5.1 Este objeto é atribuido a referência passada ao método.

6. O atributo nome do novo objeto é modificado para “Bruno”.

7. É retornado este novo objeto pelo método m2.

8. (No método main) O objeto novo é recuperado e o atributo nome é retornado pela chamada a getNome()

9. É impresso na tela:

Valor retornado: Bruno

10. O atributo nome do objeto não modificado da referência p2 é retornado pela chamada a getNome().

11. É impresso na tela:

Valor de p2.nome: Alessandro

Então é isso, bons estudos e até a próxima!

Mantenha a classe com classes internas

Resolvi começar este artigo com uma bonita citação extraída diretamente de um tutorial da Oracle de Java:

A class is the blueprint from which individual objects are created.

Traduzindo de forma livre e poética o texto acima:

Uma classe é como um padrão pelo qual objetos individuais são criados.

Geralmente utilizamos este padrão em uma ou mais classes de primeiro nível. As classes de primeiro nível, também conhecidas como top level class, são assim chamadas pois estas classes não estão dentro de outra classe.

Já as classes internas são classes que estão dentro de outras classes (logo devem estar abaixo ao menos de uma classe de primeiro nível). O exemplo abaixo explica melhor o que foi dito.

class PrimeiroNivel{
  //uma classe de primeiro nivel
}
class PrimeiroNivel2{
  //outra classe de primeiro nivel
  class Interna{
    //esta e uma classe interna, dentro da classe PrimeiroNivel2
  }
}

As classes internas podem ser dividas entre três categorias (alguém pode se perguntar, e as classes static que estão dentro de outras classes? Falarei delas em um futuro artigo sobre classes estáticas aninhadas), as quais descreverei abaixo:

  • Classe interna: Classe declarada dentro de outra classe. Pode ter todos os modificadores de acesso (public, private, protected ou sem modificador). Tem acesso direto a todos os atributos e métodos da classe de nível mais acima, porém a classe acima não tem acesso direto aos atributos e métodos da classe mais abaixo. Para acessar este tipo de classe e seus membros, só no momento da instância da classe interna.
class PrimeiroNivel2{
  private class InternaPrivada{
    //uma classe de acesso privado, acesso somente dentro da classe de nivel mais acima
  }
  protected class InternaProtegida{
    //uma classe de acesso protegido, so quem esta no package ou por heranca acessa
  }
  public class InternaPublica{
    //uma classe de acesso publico, todas as classes tem acesso por instancia
  }
  class InternaDefault{
    //esta e uma classe de acesso default, so quem esta no package acessa
  }
}
//esta no mesmo package, por isso acessa a classe protegida
public class ClassesInternasAninhadas {
  public static void main(String[] args) {
    //lembre-se sempre de instanciar o objeto de niveis acima ate o primeiro nivel
    //acesso protegido
    PrimeiroNivel2.InternaProtegida cipr = new PrimeiroNivel2().new InternaProtegida();
    //acesso publico
    PrimeiroNivel2.InternaPublica cipu = new PrimeiroNivel2().new InternaPublica();
    //ou acesso default
    PrimeiroNivel2.InternaDefault cipd = new PrimeiroNivel2().new InternaDefault();
  }
}
  • Classe internas locais (de método/construtor/blocos static e de instância (são blocos de código executados durante a carga e a instância da classe respectivamente)): Classe declarada dentro dos casos citados acima. Pode ter somente o modificador padrão de acesso (sem modificador). Tem acesso direto a todos os atributos e métodos da classe de nível mais acima. O acesso destas classes internas é somente dentro destes casos citados (não é possível acessar por exemplo um método de uma classe interna de construtor fora deste construtor).
class ClasseLocal{
  static{
    class ClasseLocalStatic{
      //um bloco static que tem uma classe acessivel apenas a outras classes deste bloco
      void m1(){
      System.out.println("ClasseLocalStatic - m1");
      }
    }
    ClasseLocalStatic c1 = new ClasseLocalStatic();
    c1.m1();
  }
  {
    class ClasseLocalInstancia{
      //um bloco de instancia que tem uma classe acessivel apenas a outras classes deste bloco
      void m1(){
        System.out.println("ClasseLocalStatic - m1");
      }
    }
    ClasseLocalInstancia c1 = new ClasseLocalInstancia();
    c1.m1();
  }
  public ClasseLocal(){
    class ClasseLocalConstrutor{
	  //um construtor que tem uma classe acessivel apenas a outras classes deste construtor
      void m1(){
        System.out.println("ClasseLocalConstrutor - m1");
      }
    }
    ClasseLocalConstrutor c1 = new ClasseLocalConstrutor();
    c1.m1();
  }
  void metodoClasseLocal(){
    class ClassesLocaisMetodo{
    	//um metodo que tem uma classe acessivel apenas a outras classes deste metodo
    	void m1(){
          System.out.println("ClasseLocalMetodo - m1");
        }
    }
    ClassesLocaisMetodo c1 = new ClassesLocaisMetodo();
    c1.m1();
  }
}

public class ClassesInternasAninhadas {
  public static void main(String[] args) {
    //a vida das classes perdura durante a execucao
    //executados bloco static, bloco de instancia e o construtor
    ClasseLocal cl = new ClasseLocal();
    //executado o metodo
    cl.metodoClasseLocal();
  }
}
    • Classe internas anônimas (não estática): Utiliza-se o conceito de classes anônimas para redefinir o comportamento de um método de uma classe ou interface. Não se aplicam modificadores de acesso a classe anônima. Porém esta classe tem acesso apenas as variáveis e métodos final da classe acima que realiza a instância da classe anonima. Por último, a classe anônima possui acesso normal a todas as variáveis e métodos da classe e sua respectiva árvore de herança (já falei de herança em Herança, casting e outros laços de familia) que a classe anônima implementa. O exemplo abaixo explica melhor o que foi dito.
    class Classe1{
      void m1(){System.out.println("Classe1");}
    }
    interface Interface1{
      String m2();
    }
    public class ClassesAnonimas {
      public static void main(String[] args) {
        //reimplementando a classe com classe anonima
        Classe1 c1 = new Classe1(){
          //sobrescreve comportamento original de m1
          void m1() {
            System.out.println("Classe Anonima da Classe1");
          }
        };
        //utilizando o metodo m1
        //com implementacao da classe anonima
        c1.m1();
        //a classe anonima pode usar variaveis
        //do tipo final na classe que a implementa
        final int i = 1;
        Interface1 i1 = new Interface1(){
          //a classe anonima tem obrigacao
          //de sobrescrever o metodo
          public String m2() {
            return "Classe Anonima da Interface" + i;
          }
        };
        System.out.println(i1.m2());
      }
    }
    

    Ao rodar esta classe, o resultado será:

    Classe Anonima da Classe1
    Classe Anonima da Interface1

    Então é isso, bons estudos e até a próxima.