Java com café: Conversão de Tipos ou Casting - Java

Conversão de Tipos ou Casting - Java

Não sei se alguem já notou mas as anotações sobre Java que estou postando aqui são apropriadas para quem vai prestar o exame SCJP (Sun Certified Java Programmer). Como também pretendo ser um programador certificado pela Sun, estou estudando e quero aproveitar para compartilhar com você meus resumos.
Hoje vou falar sobre o objetivo 5.2 do exame que é sobre Conversão de Variáveis de Referência (também conhecido por Cast ou Casting de tipos):
Dado um cenário, desenvolver código que demonstre o uso do polimorfismo. Além disso, determinar quando a conversão será necessária e saber diferenciar erros de compilação de erros de tempo de execução relacionados a conversão de referências a objetos.
Temos 2 pontos principais neste objetivo: Uso do polimorfismo e saber diferenciar erros de compilação e de tempo de execução. Veja também que no final está "referências a objetos". Isto quer dizer que esse tipo de conversão não é como conversão entre tipos primitivos (que não será abordado neste post).
Vamos começar analisando o código:
//Superclasse
public class Animal {
 
 void comer(){
  System.out.println("Animal comendo...");
 }
 
}
//Subclasse
class Dog extends Animal{
 
 void rolar(){
  System.out.println("Dog rolando...");
 }
}

Upcasting ou Conversão ampliadora

Considerando o código acima poderíamos naturalmente fazer as seguintes declarações:
Animal animal; //Declarou um tipo genérico
animal = new Dog(); //Apontou para um mais específico
//Ou simplesmente:
Animal animal = new Dog();
//Ou ainda:
Animal animal = (Animal)new Dog(); //Conversão explícita

Porque? Tipos genéricos podem apontar para tipos mais específicos. Isto chama-se upcasting ou conversão ampliadora. Esta conversão é sempre segura, pois estamos indo de um tipo mais específico para um mais genérico (de baixo pra cima na árvore de herança) e sabemos que a superclasse poderá fazer tudo que a subclasse fazia pois herdou dela. Mas fazendo isto estamos perdendo os métodos de Dog.
Nesse caso a variável de referência animal que é do tipo Animal faz referência a Dog e isto é possível porque Dog É-UM Animal (Este relacionamento É-UM significa que Dog extends Animal).
Por estes motivos o compilador permite fazer o cast sem nenhuma notação especial nem nada explícito.


Até aqui tudo bem, mas e se tentar realizar um comportamento mais específico? Por exemplo, tentar chamar um método que só existe em Dog? Não pode porque só comportamentos de Animal serão aceitos:
animal.rolar(); //Problema! Método não definido para o tipo Animal

O código não compila porque animal não tem o método rolar(). Entenda que estamos transformando um Dog num tipo mais genérico e que ele agora só poderá executar ações deste novo tipo. Ele perdeu os comportamentos específicos que tinha.
Mas um coisa interessante é que se Dog sobrescrever algum método de Animal, qual dos métodos será executado? Vamos modificar o código:
//Superclasse
public class Animal {
 
 void comer(){
  System.out.println("Animal comendo...");
 }
 
}
//Subclasse
class Dog extends Animal{
 
 void rolar(){
  System.out.println("Dog rolando...");
 }
 //Dog sobrescreveu o método comer
 void comer(){
  System.out.println("Dog comendo...");
 }
}

Suponhamos que faça a seguinte chamada:
Animal animal = new Dog();
animal.comer();

Quem vai comer? Animal ou Dog? Neste caso o método em Dog seria executado porque ele sobrescreveu. Então quando fazemos o upcasting perdemos os métodos do subtipo mas se algum for sobrescrito a versão a ser executada é a do subtipo. Muito louco isso não é?

Downcasting

Seguindo a raciocínio do upcasting, se quisermos "voltar" um tipo genérico para o mais específico (conversão redutora), devemos fazer a conversão explicitamente:
Dog dog = (Dog) animal;
dog.comer();

Veja que mudamos apenas o tipo da variável, não o tipo da referência. Então esta conversão se torna possível pois animal faz referência a Dog.
Então você se pergunta: Porque o upcasting é automático e o downcasting não? Agora chegamos num ponto interessante: No upcasting a conversão nunca falha. No downcasting, nem sempre teremos esta certeza. No exemplo citado acima, a conversão deu certo porque animal fazia referência a Dog mas e se não fizesse? Se animal fizesse referência a Animal mesmo?
Animal animal = new Animal();
Dog dog = (Dog) animal;

Como isso poderá ser feito se Animal não é um Dog? Animal nem sabe da existência de Dog. A conversão não pode ser feita porque os tipos são incompatíveis.
Só que o mais intrigante vem agora: O código compila!
Se você usa o Eclipse vá em Project > Clean..., selecione seu projeto e veja se apareceu algum erro em Problems? Não vai aparecer nada, porque pro compilador está tudo bem, ele não sabe e nem quer saber se animal faz referência a Animal ou Dog. Mas quando tentar executar:
Exception in thread "main" java.lang.ClassCastException: Animal cannot be cast to Dog
at Animal.main(Animal.java:46)
Então não tente converter um objeto para um tipo que ele não é. Mas lembre-se que o compilador não sabe se você vai tentar isto.
Para saber se dada conversão é aceita devemos usar o operador instanceof. Este operador retorna true ou false para uma comparação entre tipos:
 if (dog instanceof Animal)
 {
  System.out.println("dog é um Animal.");
 }

Na IDE Eclipse, quando começar a digitar "ins" e pressionar Ctrl + Barra de espaço ele já sugere o operador, então pressionando Enter ele já vai preencher um bloco de código igual a este:
 if (name instanceof type) {
  type new_name = (type) name;
  
 }

Aí é só você ir dando TAB e preenchendo os campos. Ele faz isso porque sabe que quase sempre que usamos o instanceof é pra saber se um objeto é de determinado tipo, e sendo positivo, já queremos que o objeto seja convertido neste tipo.
Aproveitando que já dei essa dica, tem outras duas que eu também uso muito para criar o método main e pro comando System.out.println: Para o main apenas digite main e Ctrl + Barra de espaço, para o println digite sysout Ctrl + Barra de espaço.

Só por curiosidade: Podemos converter qualquer objeto no tipo Object, como já falei no outro post, o Object é como uma classe "mãe" no Java:
Object obj = (Object)new Animal();

Mas essa conversão não tem muita utilidade pois faz com que animal perca todos seus métodos como já foi explicado. Por hoje é só!

Bons estudos e boas conversões!

12 comentários:

  1. Show!! Sempre ficava insegura se realmente tinha entendido, assim ficou muito mais fácil. Agradeço muito.

    ResponderExcluir
  2. Excelente! Agora consegui entender o casting. Obrigado!

    ResponderExcluir
  3. Legal seu raciocínio para explicar. Consegui finalmente entender como se faz, resta agora saber sua aplicabilidade na prática. abs

    ResponderExcluir
    Respostas
    1. Você entenderá a aplicabilidade no momento certo. Obrigado pelo comentário!

      Excluir
  4. Muito boa a explicação! Valeu!

    ResponderExcluir
  5. Show de bola, parabéns pela didática.
    Agora realmente eu entendi.

    ResponderExcluir
  6. Ótimo post. Aconselho quem estiver estudando para certificação não usar IDE para ver os erros, pois precisa treinar sua percepção de encontrar erros nos códigos sem nenhuma ajuda.

    ResponderExcluir