Informações

Tipo: Tutorial
Data de Publicação: 01/01/2004
Revisado em: 01/01/2004

Vote!

  • Currently 4,7/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags Relacionadas

introducao certificacao

Comentários ( 3 )

Imprimir

Conversão e Casting

por:

Ulisses Telemaco (ulisses@j2eebrasil.com.br)

Nesse módulo iremos discutir as diversas alternativas de conversão e mudança de tipo que Java disponibiliza. As conversões são, genericamente, divididas em dois grupos: implícitas e explícitas.

1. Introdução

Todas as variáveis em Java tem um tipo definido. Enquanto os tipos primitivos são formados por um conjunto já definido (char, int, double, ...), os tipos "referências de objetos" podem ser Classes (String, Vector, ...) ou Interfaces (char, char, ...). As variáveis podem ainda ser do tipo array de primitivos, referências ou array.

Nesse módulo iremos discutir as diversas alternativas de conversão e mudança de tipo que Java disponibiliza. As conversões são, genericamente, divididas em dois grupos: implícitas e explícitas.

1.1. Mudança de Tipo Explícita e Implícita

As conversões implícitas ocorrem geralmente na aplicação de alguns operadores: atribuição, intanceof, etc. Enquanto as conversões explícitas ocorrem através de um mecanismo conhecido como 'cast'. No jargão Java, as mudanças de tipo implícitas são conhecidas apenas por 'conversão' enquanto as explícitas são chamadas 'casting'. Portanto, a partir daqui, chamaremos conversão ou casting para designar, respectivamente, as mudanças de tipo implícitas e explícitas.

Existem diversas regras que devem ser seguidas ao utilizar a conversão ou o casting. A grande parte dessas regras são tratadas em tempo de compilação outras são tratadas apenas na execução do programa.

Para utilizar o cast deve-se colocar um prefixo na expressão, indicando o novo tipo entre parênteses. O exemplo da Tabela 1 ilustra um clássico exemplo de recuperação de dados de um objeto java.util.Vector:

  ...
  Vector v;
  ...
  String s = (String) v.elementAt(0);
  ...
Tabela 1: Exemplo de Casting

Para que o exemplo acima execute sem problemas é necessário que o elemento recuperado possa se comportar como um java.lang.String. Ou seja, deve-se armazenar no Vector objetos do tipo String. Veja o exemplo da Tabela 2.

  java.util.Vector v;
  ...
  String s = "Vixe que é gente!!!!";
  v.add(s);	 
  ...
Tabela 2: Exemplo de Conversão

Nas convesões (mudança de tipo implícita) o sistema executa as mudanças de tipo sem a necessidade de utilizar o cast. O método add() da classe java.util.Vector é declarado com o parâmetro java.lang.Object e não java.lang.String. No exemplo acima, o sistema executa, em background a mudança de tipo.

Conversões seguem algumas regras porém, ao contrário dos Casting, todas são tratadas em tempo de compilação.

1.2. Tipos Primitivos e Objetos

Os tipos em Java são divididos em duas grandes categorias: primitivos e objetos. São 8 os tipos primitivos são: boolean, byte, short, char, int, long, float e double.

O restante (Classes e Interfaces) são do tipo objeto. Na verdade, vamos esclarecer que os tipos objetos são na verdade referências para objetos.

Podemos utilizar a conversão ou casting tanto nos tipos primitivos quanto nos objetos. Dessa forma, existem quatro casos a serem estudados:

  • Conversão de Tipos Primitivos;
  • Casting de Tipos Primitivos;
  • Conversão de Referência de Objetos;
  • Casting de Referências de Objetos.

2. Conversão de Tipos Primitivos

Esse é o mais simples dos tópicos a serem estudados. As conversões de tipos primitivos são tratadas em tempo de compilação. Toda a informação necessária para efetuar a mudança de tipo já está disponível para o compilador Java no momento em que ele efetua o seu trabalho.

A conversão de primivos ocorrem em três situações:

  • Atribuições;
  • Chamada de Métodos;
  • Operações Aritméticas.

2.1. Conversões de Tipos Primitivos: Atribuição

A conversão durante a atribuição ocorre quando atribui-se um valor a uma variável de um tipo diferente do valor original. Veja o exemplo abaixo:

  ...
  1. int i;
  2. double d;
  3. i = 100;
  4. d = i;
  ...
Tabela 3: Exemplo de Conversão de Tipos Primitivos durante uma atribuição

A variável 'd' é do tipo double e portanto armazena somente variáveis desse tipo. Perceba que ocorre uma conversão na linha 4. O valor que está armazenado na variável 'i', que é 100, é convertindo para o valor 100.00000000000000.

Veja outro exemplo:

  ...
  1. double d;
  2. int i;
  3. d = 100.321;
  4. i = d;
  ...
Tabela 4: Exemplo de Conversão de Tipos Primitivos durante uma atribuição

Esse código não compila!!! Na verdade o compilador verifica que o programa está tentado atribuir um valor double a uma variável int, ou seja, armazenar em uma variável um valor que pode ser maior do que o suportado. Ao tentar compilar o código acima, o compilador exibirá um erro do tipo "possible loss of precision" ou "Incompatible types for =." (dependendo do compilador utilizado). A regra é que a conversão não pode ocorrer a menos que não haja perda de precisão. Nesses casos a mudança de tipo deve ser feita de forma explícita através de Casting (isso será tratado em detalhes na próxima seção).

As regras para conversão com tipos primitivos podem ser resumidas na lista abaixo:

  • O tipo boolean não pode ser convertido para nenhum outro;
  • Os tipos 'não-booleanos' podem ser convertidos para outros tipos 'não-booleanos' contanto que não haja perda de precisão. Ou seja, podem ser convertidos apenas para tipos que suportem um limite igual ou maior do que o seu.

    Em muitos casos, o novo tipo tem mais bits que o original. A conversão é ilustrada na figura abaixo:

    Figura 1: Conversão de Tipos Primitivos

    Nesse tipo de conversão não há perda do valor armazenado. No exemplo da Tabela 3, um valor int é convertido para um valor double. Essa conversão é legal porque double é "maior" do que int. As conversões podem ser resumidas da seguinte forma:

    • Um byte pode ser convertido em um short, int, long, float ou double
    • Um short pode ser convertido em um int, long, float ou double
    • Um char pode ser convertido em um int, long, float ou double
    • Um int pode ser convertido em um long, float ou double
    • Um long pode ser convertido em um float ou double
    • Um float pode ser convertido em um double

    A Figura 2 ilustra as possíveis conversões de tipo primitivo que podem ocorrer em Java.

    Figura 2: Gráfico de Conversão de Tipos Primitivos

    O gráfico é fácil de memorizar e pode ser um boa fonte de ajuda numa prova de certificação.

    Valores Literais
    Existem alguns detalhes peculiares na conversão de tipos primitivos, sobretudo quando a conversão é feita a partir de valores literais. Veja o exemplo abaixo:

      ...
      float f = 9.999;
      ...
    
    Tabela 5: Conversão durante atribuição

    O código acima não compila, isso porque os números literais em Java ou são double ou int. Dessa forma, o programa tenta atribuir um valor double (1.23456) a uma variável float.

    Veja outro exemplo:

      ...
      byte  b = 2;
      short s = 4;
      char  c = 3; 
      ...
    
    Tabela 6: Conversão durante atribuição

    Á partir da informação de que o código da Tabela 5 não compila é comum o leitor chegar a conclusão de que o exemplo da Tabela 6 apresenta o mesmo tipo de erro: atribuir a vairáveis byte, short e char o valor de um literal int. Contudo esse programa, ao contrário do anterior, executa sem nenhum problema. A razão é que Java resolve a conversão durante a atribuição apenas quando um literal do tipo int é atribuído a uma variável "menor" (byte, short ou char). É importante saber ainda, que para a conversão ser efetuada é necessário que o valor do literal esteja dentro da capacidade da variável. Veja outro exemplo:

      ...
      1. byte b = 987654321;
      2. int i = 10;
      3. byte b2 = i;
      ...
    
    Tabela 7: Trecho com Conversões de Tipos Primitivos

    O código acima possue dois erros. O primeiro deles é o da linha 1: o valor que está sendo atribuído a variável do tipo byte é maior do que sua capacidade. O segundo erro aparece na linha 3: a conversão do tipo int para tipos menores só funciona para valores literais.

    2.2. Conversões de Tipos Primitivos: Chamada de Método

    A conversão de tipos primitivos durante a chamada de um método ocorre quando, se passa um valor de um tipo como argumento para um método que espera um tipo diferente. Por exemplo, a maioria dos métodos da classe java.lang.Math recebe parâmetros do tipo double. Porém, ao acionar tais métodos fornecendo variáveis "menores", elas são automaticamente convertidas para double. Veja o exemplo abaixo:

      ...
      1. int i = 2;
      2. double d = Math.log(i);
      ...
    
    Tabela 8: Conversão durante a chamada de método

    O valor da variável int é automaticamente convertido para double na chamada do método da linha 2.

    As regras para coversão de tipos primitivos na chamada de métodos são as mesmas para conversão para atribuição.

    Conversões de Tipos Primitivos: Operações Aritméticas
    Ocorre na execução de uma operação matemática. O programa Java pode executar uma série de operações matemáticas, algumas delas tratando variáveis de tipos diferentes. Resta ao compilador efetuar as devidas conversões antes de executar a operação. Veja o exemplo abaixo:

      ...
      1. byte b = 2;
      2. int  i = 5;
      3. float f = 11.1f;
      4. double d = b * i - f;
      ...
    
    Tabela 9: Conversão durante uma operação matemática

    Na linha 4 o programa executa 3 operações. Para isso executa também a conversões de tipos primitivos: na primeira operação (multiplicação) a variável byte é convertida para int. Na segunda operação (subtração) o resultado da operação anterior que está representado pelo tipo int é convertido para float. Por fim, a operação de atribuição converte o resultado da operação, que é do tipo float, para double.

    As regras para conversão de tipos primitivos para operadores unários são diferentes das regras para operadores binários.

    Enquanto os operadores binários trabalham em cima de um único valor, os operadores binários trabalham com dois valores. A Tabela 10 resume os operadores unários e binários em Java:

    Operadores Unários + - ++ -- ~
    Operadores Binários + -
    &
    *
    ^
    /
    |
    % >> >>> <<
    Tabela 10: Operadores Unários e Binários

    Para operadores unários existem duas regras:
    1. Se o operando for do tipo byte, short ou char ele será convertido para int. (Com exceção dos operadores '++' e '--').
    2. Senão nenhuma conversão será executada.

    Para operadores binários existem quatro regras:
    1. Se um dos operandos for do tipo double, o outro operando será convertido para double.
    2. Se um dos operandos for do tipo float, o outro operando será convertido para float.
    3. Se um dos operandos for do tipo long, o outro operando será convertido para long.
    4. Senão todos os operandos serão convertidos para int.

    A regra geral é a seguinte, caso os operandos sejam de tipos diferentes, o operando de menor tipo será convertido para o tipo do maior. No caso dos tipos menores do que int, todos eles são convertidos para int.

    Veja o código abaixo:

      ...
      1. byte b1 = 2;
      2. byte b2 = 5;
      3. byte b3 = b1 + b2;
      ...
    
    Tabela 11: Conversão na execução de um operador binário

    Um programador desatento diria que o código acima compilaria sem problemas, contudo, perceba que a partir da regra 4 de operadores binários, ambos os operandos, b1 e b2 são convertidos para o tipo int. Como o resultado da operação da linha 3 é do tipo int, a atribuição não é permitida e o compilador identifica o erro na compilação.

    3. Casting de Tipos Primitivos

    As mudanças de tipos primitivos que foram detalhadas na seção anterior são executadas automaticamente. Não é necessário informar ao compilador quando a conversão deve ser feita. O Casting por sua vez representa uma espécie de mudança de tipo que ocorre quando informamos ao compilador através do uso de um prefixo indicando o novo tipo. Após a execução do casting pode haver ou não perda de informação. O exemplo abaixo efetua o cast do tipo int para byte:

      ...
      1. byte b1  = 2;
      2. byte b2  = 5;
      3. double d = (double) b1 + b2;
      4. byte b3  = (byte)   b1 + b2;
      ...
    
    Tabela 12: Casting de tipos primitivos

    O código acima executa dois casts. O primeiro deles (linha 3) é desnecessário já que a mudança de tipo é feita de um tipo menor para um maior (int para double). Já o segundo cast é exigido para efetuar a mudança de tipo (int para byte).

    Os casts são necessários quando a mudança ocorrer de um tipo maior para um menor. Na verdade o cast informa ao compilador de que a mudança pode ser feita mesmo que haja o risco de perda de informação.

    Considere o exemplo da Tabela 13.

      ...
      1. int i = 16777473;
      2. short s = i;
      3. byte b = i;
      4. System.out.println("Valor int:"+i);
      5. System.out.println("Valor short:"+s);
      6. System.out.println("Valor byte:"+b);
      ...
    
    Tabela 13: Ausência de Casting para mudança onde pode haver perda de informações

    A compilação do código acima irá detectar dois problemas (linhas 2 e 3) de mudança de tipos. Observe que ambas as mudanças ocorrem de tipos maiores para menores. a Tabela 14 mostra o código pronto para ser compilado.

      ...
      1. int i = 16777473;
      2. short s = (short) i;
      3. byte b = (byte) i;
      4. System.out.println("Valor int:"+i);
      5. System.out.println("Valor short:"+s);
      6. System.out.println("Valor byte:"+b);
      ...
    
    Tabela 14: Presença de Casting para mudança onde pode haver perda de informações

    A execução de código provoca duas conversões. Em ambas ocorre perda de informação. Observe a Figura 3 que ilustra o que acontesse durante o casting:

    
     i = 00000001 00000000 00000001 00000001 (representaçao int   - valor: 16777473)
    			   
     s = 00000001 00000000 00000001 00000001 (representaçao short - valor: 257)
    
     b = 00000001 00000000 00000001 00000001 (representaçao byte  - valor: 1)
    
    Figura 3: Valores Binários das Variáveis int, short e byte

    O valor da variável int é convertido para um short. Como o tipo short suporta apenas 2 bytes, os bits de mais baixa ordem (0-15) são transferidos para a nova variável. Os restantes (16-31) são descartados. O real valor armazenado na nova variável será 257. O mesmo raciocínio pode ser aplicado ao segundo casting, assim, a variável byte armazena o valor 1.

    Podemos sumarizar a utilização de casting para tipos primitivos com as seguintes regras:

    • É possível utilizar o caisting para converter qualquer tipo primitivo "não booleano" em outro tipo primitivo "não booleano";
    • Não é possível utilizar o caisting em variáveis do tipo boolean.

    4. Conversão de Referências a Objetos

    A conversão de objetos é um pouco mais complicada que a de tipos primitivos. Veremos nessa seção as regras que controlam a conversão de objetos. É importante saber nesse momento, quando falamos de "conversão de objetos" queremos na verdade dizer "conversão de referências a objetos" (ao final desse seção iremos entender isso com clareza). A conversão de tipos ocorre, tipicamente, nas mesmas situações da conversão de primitivos, ou seja, durante a atribuição e chamade de métodos. Não ocorre, contudo, conversão de objetos em operações aritméticas.

    4.1. Conversão de Objetos: atribuição

    Conversão de objetos ocorre quando atribuimos a uma referência a um objeto o valor de um objeto de outro tipo. Existem três tipos de referência a objetos:

    • Classes
    • Interfaces
    • Arrays

    A Tabela 15 ilustra o formato geral de uma conversão de referência a objetos.

      ...
      1. ClasseTipoOriginal o = new ClasseTipoOriginal();
      2. ClasseNovoTipo n = o;
      ...
    
    Tabela 15: Conversão de Objetos

    A atribuição da linha 2 armazena em uma referência do tipo ClasseNovoTipo um objeto do tipo ClasseTipoOriginal. Contudo, esse código só compilará se a conversão seguir algumas regras. Como ClasseTipoOriginal e ClasseNovoTipo podem ser uma classe, uma interface ou um array, existem 9 combinações possíveis de conversão e consequentemente 9 regras de conversão. A Tabela 16 traz um sumário dessas combinações.

      ClasseTipoOriginal
    Classe
    ClasseTipoOriginal
    Interface
    ClasseTipoOriginal
    Array
    ClasseNovoTipo
    Classe
    ClasseTipoOriginal deve ser uma subclasse de ClasseNovoTipo ClasseNovoTipo deve ser um Object ClasseNovoTipo deve ser um Object
    ClasseNovoTipo
    Interface
    ClasseTipoOriginal deve implementar ClasseNovoTipo ClasseTipoOriginal deve ser uma subinterface de ClasseNovoTipo ClasseNovoTipo deve ser Cloneable ou Serializable
    ClasseNovoTipo
    Array
    Erro Erro ClasseTipoOriginal é um array de objetos "A". ClasseNovoTipo é um array de objetos "B". "B" pode ser convertido em "A".
    Tabela 16: Regras de Conversão de Referências de Objetos

    Em geral, a conversão para objetos é permitida quando a direção da mudança de tipo ocorre "de baixo para cima" na árvore de hierarquia. Isto é, o tipo original deve ser uma subclasse do novo tipo. Esta regra não pode ser usada para explicar as nove combinações de conversão ilustradas na Tabela 16. Contudo, ela oferece uma boa ajuda para enteder quando podemos aplicar a conversão.

    Podemos, ainda, resumir essas regras em três casos:

    • Uma referência do tipo Interface pode ser convertida para uma referência do tipo Interface ou para uma referência a um Object. Se o novo tipo for uma Interface, então ela deverá ser uma superinterface da original.
    • Uma referência do tipo Classe pode ser convertida para uma referência do tipo Classe ou para uma referência do tipo Interface. Se o novo tipo for uma Classe, então ele deverá ser uma superclasse do original. Se for uma Interface, a Classe original deverá implementar essa Interface.
    • Um array pode ser convertido para uma referência a Object, para uma Interface Cloneable ou Serializable, ou para um array. Nesse último caso, o array original deverá armazenar objetos de um tipo que possa ser convertido para objetos armazenado no novo array.

    Para ilustrar essas regras, considere a árvore de hierarquia apresentada na Figura 3.

    Figura 3: Hierarquia das Classes Cachorro, Gato e Papagaio

    Vejamos na Tabela 17 o primeiro exemplo:

      ...
      1. Gato g = new Gato();
      2. AnimalDomestico ad = g;
      ...
    
    Tabela 17: Conversão de Objetos - Exemplo 1

    Este código está correto. Um Gato está sendo convertido e um AnimalDomestico. Perceba que como o novo tipo é uma superclasse do tipo original, a conversão é permitida. Conversões na ordem contrária da hierarquia, ou seja, "de cima para baixo", não são permitidas. Vejamos o código da Figura 19.

      ...
      1. AnimalDomestico ad = new AnimalDomestico();
      2. Gato g = ad;
      ...
    
    Tabela 18: Conversão de Objetos - Exemplo 2

    A compilação desse código irá identificar um erro de conversão na linha 2. Objetos da classe A Classe AnimalDomestico não podem ser convertidos para objetos da classe Gato porque essa última não é uma superclasse da primeira.

    Vejamos na Figura 19 um exemplo de conversão de objetos envolvendo agora uma Interface.

      ...
      1. Gato gato1 = new Gato();
      2. Mercadoria m = gato1;     //OK
      3. Gato gato2 = m;           //Ops!!!
      ...
    
    Tabela 19: Conversão de Objetos - Exemplo 2

    A conversão da linha 2 é perfeitamente legal. A Classe Gato implementa a Interface Mercadoria e, portanto, a conversão ocorre "de baixo para cima" na árvore de hierarquia (veja a Figura 3). A conversão da linha 3, por sua vez, não será aceita pelo compilador. O erro acontece porque uma Interface não pode ser convertida (pelo menos implicitamente) para uma Classe que não seja do tipo Object.

    Consideremos agora um exemplo de conversão entre objetos do tipo array.

      ...
      1. Animal animais[];
      2. Papagaio papagaios[];
      3. AnimalDomestico domesticos[] = new AnimalDomestico[5];
      4. for(int i=0; i < domesticos.length; i++){
      5.    domesticos[i] = new AnimalDomestico();
      6. }
      7. animais   = domesticos;   //OK
      8. papagaios = domesticos;   //Ops!!!
      ...
    
    Tabela 20: Conversão de Objetos - Exemplo 3

    A Figura 20 ilustra duas conversões (linhas 7 e 8). A primeira delas converte um array de AnimalDomestico e um array de Animal. Como Animal e é uma superclasse de AnimalDomestico, a conversão acontece "de baixo para cima" e portanto é legal. De outra forma, a conversão da linha 8 acontece "de cima para baixo" e portanto é ilegal (a Classe Papagaio está abaixo da Classe AnimalDomestico). Nesse momento é importante ter em mente a hierarquia ilustrada na Figura 3. Podemos, a partir dela, imaginar algumas das possíveis conversões entre os tipos apresentados:

    OK
      Gato g = new Gato();
      Object o = g;
    
    OK
      Cachorro c = new Cachorro();
      Animal a = c;
    
    OK
      Papagaio p = new Papagaio();
      Mercadoria m = p;
      Object o = m;
    
    Erro
      Gato g = new Gato();
      Cachorro c = g;
    
    Erro
      Object o = new Object();
      Animal a = o;
    
    Erro
      Animal a = new Animal();
      Papagaio p = a;
    
    Tabela 21: Exemplos de Conversão de Objetos

    4.2. Conversão de Objetos: chamada de métodos

    A conversão de objetos durante a chamada de um método ocorre quando, se passa um valor de objeto de um determinado tipo como argumento para um método que espera um objeto de tipo diferente. Tomemos como exemplo o método add da Classe java.util.Vector. Sua assinatura define que ele recebe objetos do tipo Object, contudo, na maioria das vezes utilizamos esse método para armazenar objetos de outros tipos. Vejamos a Figura 21.

      ...
      1. Gato gato = new Gato();
      2. Vector v = new Vector;
      3. v.add(gato);
      ...
    
    Tabela 22: Conversão de Objetos durante a chamada de um método

    O objeto do tipo Gato passado com argumento é automaticamente convertido para Object na chamada do método add.

    5. Casting de Referências a Objetos

    A sintaxe do casting de objetos é identica a utilizada para tipos primitivos. Veja o código abaixo:

      ...
      1. Gato g = new Gato();
      2. AnimalDomestico ad = (AnimalDomestico) g;
      ...
    
    Tabela 23: Casting de Objetos - Primeiro exemplo

    É sempre possível utilizar casting nas mesmas situações onde a conversão é possível. Apesar de não ser necessária, essa prática é usada por alguns programadores para tornar o código mais compreensível.

    A seguir, vamos estudar os importantes casos aonde o casting de objetos pode ser utilizado. Mas antes disso vamos entender um pouco melhor a diferença entre objetos e referências a objetos. Essa distinção será muito importante para a compreensão do restante desse texto.

    O operador new é usado para criar um novo objeto. O argumento de new determina a real classe de um objeto. Os programadores Java não manipulam diretamenteo com objetos. Ao contrário disso, trabalham apenas com referências a objetos. Veja o código da Tabela 23: a variável "g" não é um objeto, e sim uma referêncoa a um objeto. O objeto reside em algum lugar da memória do sistema. A variável "g" armazena, na verdade, o endereço do objeto na área de memória (esse endereço é representado por um número de 32 bits. Esse endereço é chamado de "Referência ao Objeto". Veja a Figura 4.

    Figura 4: Representação das Referências a Objetos

    A classe de um objeto é imutável. Contudo, ele pode ser referenciado por objetos de vários tipos. Veja a Figura 5 e entenda o que ocorre durante a execução de um programa bem simples.

      ...
      1. Gato g = new Gato();
      2. AnimalDomestico ad = g;
      3. Mercadoria m = g;
      ...
    

    Figura 5: Exemplo da Área de Memória durante a execução de um programa

    Existem dois conjunto de regras que regem o cating de objetos. O primeiro é formado por regras aplicadas em tempo de compilação. A Tabela 24 ilustra tais regras:

    TipoOriginal
    Classe "não-final"
    TipoOriginal
    Classe "final"
    TipoOriginal
    Interface
    TipoOriginal
    Array
    NovoTipo
    Classe "não-final"
    TipoOriginal deve herdar de NovoTipo ou vice-versa TipoOriginal deve herdar de NovoTipo Sempre OK TipoOriginal deve ser Object
    NovoTipo
    Classe "final"
    NovoTipo deve herdar de TipoOriginal TipoOriginal e NovoTipo devem ser a mesma Classe NovoTipo deve implementar uma interface ou Serializable Erro de Compilação
    NovoTipo
    Interface
    Sempre OK TipoOriginal deve implementar a interface NovoTipo Sempre OK Erro de Compilação
    NovoTipo
    Array
    NovoTipo deve ser um Object Erro de Compilação Erro de Compilação TipoOriginal contém objetos que possam ser casting para os objetos de NovoTipo
    Tabela 24: Regras de casting de objetos aplicadas em tempo de compilação

    São ao todo 16 regras. Uma quantidade não muito fácil de decorar. A maioria delas se referem a casos pouco utilizados, contudo, são importantes em um exame de certificação.

    O segundo conjunto de regras será aplicado em tempo de execução do programa. Essas regras avaliam a seguinte condição: se a classe do objeto que está sendo convertido é compatível com o novo tipo. Onde compatível significa que a classe pode ser convertida de acordo com as regras de conversão discutidas anteriormente. Podemos resumir as regras de compilação da seguinte forma:

    1. Quando as referências NovoTipo e TipoOriginal são ambas Classes, então uma deve ser subclasse da outra.
    2. Quando NovoTipo e TipoOriginal são arrays, ambos devem conter objetos (não primitivos). Deve ser possível casting um objeto de TipoOriginal em um objeto de NovoTipo.
    3. O casting entre uma Classe Final e uma Interface é sempre possível.

    As regras de execução devem garantir que a conversão seja possível. Portanto, tais regras são semelhante as regras de conversão (vistas na seção anterior) e são resumidas abaixo:

    1. Se NovoTipo é uma Classe, a referência que está sendo convertida deve ser do tipo NovoTipo ou herdar dele.
    2. Se NovoTipo é uma Interface, a classe que está sendo casting deve implementar NovoTipo.

    Vamos nos concentrar agora em alguns exemplos que ajudarão a entender melhor o que vimos até aqui. Todos os exemplos a partir daqui se baseiam na estrutura da Figura Figura 3. Veja o primeiro exemplo da Tabela 25.

      ...
      1. Cachorro c1, c2;
      2. Gato g;
      3. AnimalDomestico ad;
      4. c1 = new Cachorro(); //Criacao de um objeto da Classe Cachorro
      5. ad = c1;             //Converão na Atribuição. Não exige Cast
      6. c2 = (Cachorro)ad;   //Cast Legal
      7. g = (Gato) c1;       //Cast Legal (compilação)... Erro (execução)
      ...
    
    Tabela 25: Conversão e Casting entre Classes

    Este código explora bem a conversão e casting de objetos. São declaradas quatro referências mas existe apenas um único objeto da Classe Cachorro (observe o construtor chamado na linha 4). A atribuição da linha 5 é perfeitamente legal. Percebe que, uma vez que conversão é feita de "baixo para cima" segundo a estrutura de classes (veja Figura 3), o cast é desnecessário. Vamos nos concentrar agora no código das linhas 6 e 7:

    A referência AnimalDomestico é atribuída, através de cast, para referências do tipo Cachorro e Gato. Lembre-se que, quando as duas referências envolvidas num cating são classes, uma deve ser subclasse da outra. Em ambos os casos, essa exigência é cumprida e o código compila sem problemas.
    Em tempo de compilação a estória é um pouco diferente. O sistema, ao tenta executar a linha 6, atribui um objeto do tipo Cachorro (a variável "ad" faz referência a um objeto da Classe Cachorro) a uma referência também do tipo Cachorro. Nesse caso nenhuma conversão é de fato executada.

    Na linha 7, o sistema tenta atribuir um objeto do tipo Cachorro (a variável "ad" ainda faz referência a um objeto da Classe Cachorro) a uma referência do tipo Gato. Como as Classes Cachorro e Gato não tem nenhuma ligação de herança (na verdade são classes irmãs), a execução do cast da linha 7 aciona uma exceção do tipo java.lang.ClassCastException.

    Agora vejamos na Tabela 26 um exemplo de cast envolvendo classes e interfaces.

      ...
      1. Cachorro c1, c2;
      2. Mercadoria m;
      3. c1 = new Cachorro(); //Criacao de um objeto da Classe Cachorro
      4. m = c1;              //Converte Cachorro em Mercadoria
      5. c2 = m;              //Converte Mercadoria em Cachorro. Erro (compilação)
      ...
    
    Tabela 26: Conversão e Casting entre Classes e Interfaces

    Este código não irá compilar, uma vez que apresenta, na linha 5, uma conversão ilegal. Conversões implícitas (cating) de uma Interface em uma Classe não são permitidas. Nesse caso é necessário executar o cast. Veja agora a Tabela 27:

      ...
      5. c2 = (Cachorro)m;
      ...
    
    Tabela 27: Código corrigido

    A alteração acima permite que o código compile sem problemas. Porém, somente em tempo de execução, o Sistema poderá verificar se o casting será permitido.

    Para fecharmos essa seção, vejamos um caso envolvendo casting entre Arrays.

      ...
      1. Cachorro c1[], c2[];
      2. Mercadoria m[];
      3. for(int i=0; i < c1.length; i++){
      4.    c1[i] = new Cachorra();
      5. }
      6. m = c1;              //Converte o Array de Cachorros em Array de Mercadoria
      7. c2 = (Cachorro[])m;  //Converte através de Cast
                              //o Array de Mercadoria em Array de Cachorro.
      ...
    
    Tabela 28: Conversão e Casting entre Arrays

    O código acima contém uma conversão (linha 6) e um casting (linha 5). A conversão é legal já que a classe Cachorro implementa Mercadoria e, portanto, a conversão é feita de "baixo para cima". Na linha 6 um Array de Mercadoria (m) é casting para um Array de Cachorro (c2). O cast de Arrays é legal se o cast de seus elementos for possível (os elementos devem ser referências, não tipos primitivos). Dessa forma, o cast da linha 6 é perfeitamente legal.

    Para encerrarmos esse módulo, considere a Figura 6, uma extensão do modelo apresentado na Figura 3.

    Figura 6: Alteração do Modelo da Figura 3

    Observe a existência de uma classe Prato que não tem ligação com o modelo original (exceto por herdar da Classe Object). Veja o código na Tabela 29:

      ...
      1. Gato g = new Gato();
      2. Animal a = g;
      3. Prato p = (Prato) a;
      ...
    
    Tabela 29: Conversão e Casting - Mais Exemplos

    O código parece um pouco estranho, porém, compila sem problemas. É sempre possível converter uma Interface em uma Classe não-final (Veja as regras da Tabela 24). Contudo, em tempo de execução o sistema detecta um erro por não conseguir converter o objeto que está sendo referenciado pela variável a (Objeto Gato) em um objeto do tipo Prato.

  • Comentários (3)

    Muito interessante o artigo, serve para pessoas como eu, que estão começando na linguagem e serve como guia de referência. No mesmo nível dos conteúdos em inglês!!!
    postado por Igor em 18/05/2007 às 23:21
    Muito explicativo e aborda todas as situações de casting. Parabéns!
    postado por Reinaldo de Carvalho em 04/12/2007 às 23:21
    Muito show este artigo. Por ser bem amplo em conteudo, ele tirou muitas duvidas. Parabéns mesmo.
    postado por Renato em 18/08/2008 às 23:21
    Comente!

    Observações

    Os campos em negrito são obrigatórios.

    Para evitar problemas, este espaço é moderado. Após o envio do comentário será necessário aguardar pela sua aprovação.