Anotações OCJP

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

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.

Saiba mais sobre sobrescrita e sobrecarga

Vou falar neste artigo sobre os pontos mais importantes sobre a sobrescrita e sobrecarga.

Definição
Sobrescrita: Utilizada para definir o comportamento de um método de mesmo nome já definido na superclasse.
Sobrecarga: Utiliza o nome do método já definido na classe ou superclasse.
O código abaixo fornece um exemplo que demonstra melhor o que foi explicado acima.

import java.io.IOException;
import java.util.*;

class Sobrecarregado{
List metodo() throws Exception{
List s = new ArrayList();
s.add("metodo classe sobrecarregado");
return s;
}
}
class SobrecargaSobrescrita extends Sobrecarregado{
//sobrescrita
public ArrayList metodo() throws IOException{
ArrayList s = new ArrayList();
s.add("metodo sobrescrito");
return s;
}
//sobrecarga
String metodo(String s){
return "metodo sobrecarregado com argumento String";
}
}

Obrigações da sobrescrita
1. Não modificar a quantidade, ordem e tipo dos argumentos.
2. Ter o tipo de retorno igual ou compatível respeitando a herança (retorno covariante). Como no exemplo acima, a classe SobrecargaSobrescrita define o retorno como ArrayList. E isso funciona pois o retorno do método da superclasse é de uma classe List que é superclasse de ArrayList (note que o inverso não funciona neste caso).
3. A regra de exceção em um método que realiza a sobrescrita também funciona como explicado no item 2 (método que sobrescreve pode lançar IOException e a superclasse pode lançar Exception).
4. A visibilidade do método que sobrescreve não pode ser mais restritiva do que o método sobrescrito (o método que sobrescreve nesse exemplo é public, logo não há problemas pois o método sobrescrito tem visibilidade default).

Obrigação da sobrecarga
1. Deve mudar a quantidade ou tipo dos argumentos definidos no método que fará a sobrecarga (no exemplo o método aceita um argumento String, diferente do método definido anteriormente, que não possui argumentos.

Observe agora o comportamento de execução das classes. Faça o exercício do compilador e tente definir quais são as respostas apresentadas na tela.

public class ExecutaSobre{
public static void main(String args[]) throws Exception{
//1
System.out.println(new SobrecargaSobrescrita().metodo().get(0));
//2
System.out.println(new SobrecargaSobrescrita().metodo("sobrecarregado"));
//3
System.out.println(new Sobrecarregado().metodo().get(0));
Sobrecarregado s = new SobrecargaSobrescrita();
//4
System.out.println(s.metodo().get(0));
//5
SobrecargaSobrescrita s2 = (SobrecargaSobrescrita) new Sobrecarregado();
System.out.println(s2.metodo().get(0));
}
}

Bem, então vamos as respostas abaixo:
1. “método sobrescrito”: Ao realizarmos a instância de um novo objeto da classe SobrecargaSobrescrita, o método a ser chamado é o da própria classe.

2. “metodo sobrecarregado com argumento String”: Este outro objeto chama o método sobrecarregado por String, trazendo esta resposta.

3. “metodo classe sobrecarregado”: Ao realizarmos a instância de  um novo objeto da classe Sobrecarregado, o método a ser chamado é o da própria classe.

4. “metodo sobrescrito”:  É feita a instância de um novo objeto da classe SobrecargaSobrescrita então, mesmo que a referência aponte para a superclasse, o método a ser chamado é o método da instância.

5. “java.lang.ClassCastException”: Embora esteja forçando o cast para a classe SobrecargaSobrescrita, não se trata de um objeto da classe SobrecargaSobrescrita e sim de um objeto da classe acima na hierarquia (Sobrecarregado). Entretanto, como se indica forçadamente ao compilador que se trata de um objeto da classe SobrecargaSobrescrita, não é lançado um erro na compilação e sim lançada uma exceção em tempo de execução.

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

Herança, casting e outros laços de família

Quem está estudando para a certificação já percebeu que existem alguns obstáculos a serem superados no que diz respeito a herança.

Resolvi então mostrar alguns pontos importantes sobre herança que devem ser lembrados.

1. Herança direta (Filho do pai): Vou chamar de herança direta pois se trata de um extender o comportamento da superclasse para a classe imediatamente abaixo. A chamada do metodo sobrescrito do pai deve ser feita (“respeitosamente”) através da palavra chave super.

class Pai{
  public void metodo(){System.out.println("Primeiro vem o pai...");}
}
class Filho extends Pai{
  public void metodo(){
    super.metodo();
    System.out.println("...depois vem o filho");
  }
}
class PaisEFilhos {
  public static void main(String[] args) {
    new Filho().metodo();
  }
}

Ao instanciar o objeto da classe, a saida no console será:
Primeiro vem o pai…
…depois vem o filho

2. Herança indireta(Avós, Bisavós e outros da árvore genealógica): Vou chamar este de herança indireta pois se trata de herdar um comportamento causado em uma classe de forma indireta. Uma classe chamada Avô poderia ter o metodo chamado por Pai e Filho herdaria esse comportamento de forma indireta (Afinal deve-se respeitar os comportamentos de Avô e Pai).

Bom, agora vamos ao assunto que é o motivador deste artigo. Quando ocorre a criação dos objetos em Java, o comportamento da chamada de um método não é definido pela referência que aponta para o objeto e sim definido pelo objeto que foi instanciado o qual a referência aponta. O exemplo abaixo esclarece melhor:

class Bisavo{
  public void metodo(){System.out.println("Será o bisavô?");}
}
class Avo extends Bisavo{
  public void metodo(){
    System.out.println("Ou será o avô?");
  }
}
class PaisEFilhos {
  public static void main(String[] args) {
    Bisavo bisa = new Avo();
    //vamos chamar a implementacao do objeto avo
    bisa.metodo();
}
}

Até agora está tudo tranquilo. Mas vamos bagunçar este comportamento com o Casting. O Casting nada mais é do que a afirmação ao compilador: “Eu sei o que estou fazendo, confie em mim que este objeto armazenado nesta referência realmente aponta para esta classe”. Para exemplificar, vamos usar a velha relação da fazenda:

class Animal{}
class Gato extends Animal{}
class Cachorro extends Animal{}
class CastingFazenda {
  public static void main(String[] args) {
    //ate agora ok, gato é um animal.
    Animal a = new Gato();
    //conversao ok, é um objeto gato
    Gato g = (Gato) a;
    //nao e cachorro, mas o compilador nao sabe!
    Cachorro c = (Cachorro) a;
  }
}

A última linha de código vai compilar, mas vai lançar uma exceção ClassCastException. O mesmo ocorre com vetor destes objetos.

class CastingVetorFazenda {
  public static void main(String[] args) {
    //criando vetor de animal e populando com objetos da classe Cachorro
    Animal[] animais = new Animal[10];
    for(int i = 0; i < animais.length;i++){
      animais[i] = new Cachorro();
    }
    //Compila, mas vai dar class cast exception. Nao ha como garantir que esse vetor é de gato.
    Gato[] c= (Gato[])animais;
  }
}

Atenção

Sim, foi para chamar a sua atenção mesmo! Quando criamos listas que já possuem definidas as regras de quais objetos de classe irão aceitar, o erro se dará na compilação. Observe o exemplo abaixo com ArrayList:

import java.util.*;
//classes de animais aqui(...)
class CastingListFazenda {
  public static void main(String[] args) {
    //criando lista de animal e adicionando objetos da classe Cachorro
    ArrayList animais = new ArrayList();
    for(int i = 0; i < animais.size();i++){
      animais.add(new Cachorro());
    }
    //Nem compila. Nao ha como garantir que esta lista é de gato.
    ArrayList<Gato> g= (ArrayList<Gato>)animais;
  }
}

Listas criadas utilizando o conceito de generics (Veja esta boa definição de generics feita pela Oracle) já bloqueiam antes da compilação. Desta forma, assegura que não haverá um objeto que possa causar um ClassCastException (E garantindo sempre que não existirão listas que receberão objetos que não tem relação alguma com a Classe (imagina um Gato numa lista de Cachorros, que confusão!)
Bom é isso aí, bons estudos e até a próxima!

O curioso caso das Threads do tipo Daemon

Quando se começa a estudar Thread em java, algumas perguntas intrigantes ficam no ar: De que forma uma thread Daemon funciona? Qual seria o melhor exemplo prático de funcionamento destas threads?
Um conjunto de threads pode ser iniciada e colocada em fila para execução pelo escalonador. Threads comuns (não Daemon) são executadas até o fim,logo quando a última thread tem sua execução finalizada a JVM é encerrada.
Porém, certas threads não devem ter esta espera para o fim de sua execução, pois geralmente são threads que necessitam ser executadas a cada intervalo de tempo. Podemos definir estas threads como threads do tipo daemon.
Para definir uma thread como Daemon, basta chamar o método setDaemon(true) antes que se inicie a Thread (através do método start). Desta forma dizemos ao escalonador que não importa se esta thread será executada por completo e sim que ela deve ser executada enquanto outras threads não-Daemon estiverem em execução.
Abaixo segue um código que auxilia a compreensão de tudo que já foi falado:

class ClasseRotineira implements Runnable{
public void run(){
while(true){
try{
//gera um aviso na tela a cada 5s
Thread.sleep(5000);
System.out.println("+ 5s de execução");
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
class ClasseExecucao implements Runnable{
public void run(){
try{
//simula uma execucao demorada de 15s
Thread.sleep(15000);
System.out.println("fim da execução");
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
public class ClassePrincipal{
public static void main(String args[]){
System.out.println("Inicio classe principal");
Thread threadRot = new Thread(new ClasseRotineira());
Thread threadExec = new Thread(new ClasseExecucao());
//Modificando esta thread para daemon
threadRot.setDaemon(true);
threadRot.start();
threadExec.start();
}
}

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

O incrível comportamento de uma String

Alguem já parou para pensar na mecânica engenhosa que é a criaçao de uma String?
Sim, é uma construção fabulosa, que nos permite criar objetos sem explicitar o comando new:

class str{
public static void main(String args[]){
String s = "abc"
}
}

O processo de criação de uma string como acima, passa pela instância de um objeto String, atribuição do valor a este objeto e referencia deste objeto ao atributo de instância chamado de s.
Até agora, nenhuma surpresa para quem conhece java, parecem apenas detalhes de uma operação normal, mas existem algumas comparações com Strings que merecem alguma atenção:

class str2{
public static void main(String args[]){
String nome = "Bruno";
String nome2 = "Bruno";
String nome3 = new String("Bruno");
System.out.println(nome==nome2);//1
System.out.println(nome2==nome3);//2
System.out.println(nome.equals(nome2));//3
System.out.println(nome2.equals(nome3));//4
nome = nome3;
System.out.println(nome==nome2);//5
System.out.println(nome==″Bruno");//6
System.out.println(nome.equals(nome3));//7
}
}

Antes de compilar este codigo na jvm, tente ser o compilador. Nas linhas numeradas com comentários, quais são os resultados?
Depois de parar para pensar, confira se as respostas condizem com o que está abaixo:

1. True. Quando a jvm cria as Strings literais, ela armazena a referência desses objetos e localiza essa referência caso haja a comparação destes literais. Por isso que esta comparação retorna true.(Mais detalhes em http://www.guj.com.br/articles/103).

2. False. A referência para os objetos de nome2 e nome3 é diferente, pois um objeto novo é instanciado por nome 3 e a sua referência não funciona como as Strings literais.

3. True. O valor de nome é igual ao de nome2.

4. True. O valor de nome2 é igual ao de nome3.

5. False. A referência de nome agora é para o nome3, logo não é igual a referencia de literal.

6. False. Mesma justificativa da 5.

7. True. Os valores dos objetos são iguais.

Logo podemos concluir que as questões de String não são questões difíceis mas sim questões que exigem de nossa atenção.
Então é isso, até a próxima!

Interfaces e classes abstratas: Armadilha?

Começo os meus posts sendo bem direto: Até que ponto a herança em Java pode ser benéfica para aqueles que a usam?

Como diz aquela célebre frase: ” Grandes poderes trazem grandes responsabilidades.”

E é inegável que as interfaces trazem as responsabilidades para fazer nosso mundo melhor:

interface I{
void m1();
}

A interface acima deve ser implementada pela primeira classe concreta que a receber, o que seria facil de visualizar em um exemplo assim:

class C implements I{
public void m1(){
//...alguma coisa a programar
}
}

Porém, com a classe abstrata, a responsabilidade pode ser dividida. Muita atenção nesta parte:

interface J{
void m1();
String m2();
}

abstract class A implements J{
public void m1(){
//... algum código relevante
}
}

class C2 extends A implements J{
//m1 já foi implmentado por A, não é necessária mais a implementação

public String m2(){
//... mais código relevante
return "implementação de m2";
}

}

Pensei a princípio que daria erro, mas não, pois a classe C2 implementa os metodos m1 (pela classe A) e o método m2 (implementação na própria classe).

Mas o meu choque não parou por aí. Tinha muito mais a sair da manga do Sr. Java:

class C3{
void m1(){
System.out.println("m1.C3");
}
void m2(){
}
}
abstract class A2 extends C3{
abstract void m1();
}
class C4 extends A2{
void m1(){
System.out.println("m1.C4");
}
}
public class teste2{
public static void main(String args[]){
C3 c3 = new C4();
A2 a3 = new C4();
c3.m1();//saida m1.c4
a3.m1();//saída m1.c4
}
}

Não há erro algum neste código! O método m1 é redefinido como abstrato e depois e implementado pela classe C4.

E é isso, até a próxima!