Anotações OCJP

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

Arquivos Mensais: abril 2011

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!