Informações
| Tipo: | Tutorial |
|---|---|
| Data de Publicação: | 01/01/2004 |
| Revisado em: | 01/01/2004 |
Vote!
Tags Relacionadas
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); ... |
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); ... |
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; ... |
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; ... |
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:
boolean não pode ser convertido para nenhum outro;
Em muitos casos, o novo tipo tem mais bits que o original. A conversão é ilustrada na figura abaixo:
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
bytepode ser convertido em umshort,int,long,floatoudouble - Um
shortpode ser convertido em umint,long,floatoudouble - Um
charpode ser convertido em umint,long,floatoudouble - Um
intpode ser convertido em umlong,floatoudouble - Um
longpode ser convertido em umfloatoudouble - Um
floatpode ser convertido em umdouble
A Figura 2 ilustra as possíveis conversões de tipo primitivo que podem ocorrer em Java.
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; ... |
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; ... |
Á 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; ... |
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); ... |
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; ... |
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 | + | - & | * ^ | / | | % | >> | >>> | << |
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; ... |
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; ... |
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);
...
|
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);
...
|
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 = |
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; ... |
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.
ClasseTipoOriginalClasse |
ClasseTipoOriginalInterface |
ClasseTipoOriginalArray |
|
ClasseNovoTipoClasse |
ClasseTipoOriginal deve ser uma subclasse de ClasseNovoTipo |
ClasseNovoTipo deve ser um Object |
ClasseNovoTipo deve ser um Object |
ClasseNovoTipoInterface |
ClasseTipoOriginal deve implementar ClasseNovoTipo |
ClasseTipoOriginal deve ser uma subinterface de ClasseNovoTipo |
ClasseNovoTipo deve ser Cloneable ou Serializable |
ClasseNovoTipoArray |
Erro |
Erro |
ClasseTipoOriginal é um array de objetos "A".
ClasseNovoTipo é um array de objetos "B".
"B" pode ser convertido em "A".
|
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
arraypode ser convertido para uma referência aObject, para uma InterfaceCloneableouSerializable, ou para umarray. Nesse último caso, oarrayoriginal deverá armazenar objetos de um tipo que possa ser convertido para objetos armazenado no novoarray.
Para ilustrar essas regras, considere a árvore de hierarquia apresentada na Figura 3.
Cachorro, Gato e Papagaio
Vejamos na Tabela 17 o primeiro exemplo:
... 1. Gato g = new Gato(); 2. AnimalDomestico ad = g; ... |
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; ... |
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!!! ... |
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!!!
...
|
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; |
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); ... |
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; ... |
É 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.
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; ... |
|
|
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:
TipoOriginalClasse "não-final" |
TipoOriginalClasse "final" |
TipoOriginalInterface |
TipoOriginalArray |
|
NovoTipoClasse "não-final" |
TipoOriginal deve herdar de NovoTipo ou vice-versa |
TipoOriginal deve herdar de NovoTipo |
Sempre OK | TipoOriginal deve ser Object |
NovoTipoClasse "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 |
NovoTipoInterface |
Sempre OK | TipoOriginal deve implementar a interface NovoTipo |
Sempre OK | Erro de Compilação |
NovoTipoArray |
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 |
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:
- Quando as referências
NovoTipoeTipoOriginalsão ambas Classes, então uma deve ser subclasse da outra. - Quando
NovoTipoeTipoOriginalsão arrays, ambos devem conter objetos (não primitivos). Deve ser possível casting um objeto deTipoOriginalem um objeto deNovoTipo. - 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:
- Se
NovoTipoé uma Classe, a referência que está sendo convertida deve ser do tipoNovoTipoou herdar dele. - Se
NovoTipoé uma Interface, a classe que está sendo casting deve implementarNovoTipo.
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) ... |
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) ... |
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; ... |
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.
...
|
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.
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; ... |
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
