Conversão de tipo de objeto em Java

Conversão de tipo de objeto em Java

1. Visão geral

O sistema de tipos Java é composto de dois tipos de tipos: primitivas e referências.

Cobrimos as conversões primitivas emthis article e vamos nos concentrar na conversão de referências aqui, para obter uma boa compreensão de como o Java trata os tipos.

Leitura adicional:

Noções básicas de genéricos Java

Uma introdução rápida aos conceitos básicos do Java Generics.

Read more

Instância Java do Operador

Aprenda sobre a instância do operador em Java

Read more

2. Primitivo vs. Referência

Embora as conversões primitivas e a conversão de variáveis ​​de referência possam parecer semelhantes, são bastantedifferent concepts.

Em ambos os casos, estamos “transformando” um tipo em outro. Mas, de uma maneira simplificada, uma variável primitiva contém seu valor, e a conversão de uma variável primitiva significa mudanças irreversíveis em seu valor:

double myDouble = 1.1;
int myInt = (int) myDouble;

assertNotEquals(myDouble, myInt);

Após a conversão no exemplo acima, a variávelmyInt é1, e não podemos restaurar o valor anterior1.1 a partir dele.

Reference variables are different; a variável de referência apenas se refere a um objeto, mas não contém o objeto em si.

A transmissão de uma variável de referência não toca no objeto a que se refere, mas apenas rotula esse objeto de outra maneira, expandindo ou diminuindo as oportunidades de trabalhar com ele. Upcasting narrows the list of methods and properties available to this object, and downcasting can extend it.

Uma referência é como um controle remoto para um objeto. O controle remoto possui mais ou menos botões, dependendo do seu tipo, e o próprio objeto é armazenado em uma pilha. Quando fazemos a transmissão, alteramos o tipo do controle remoto, mas não o próprio objeto.

3. Upcasting

Casting from a subclass to a superclass is called upcasting. Normalmente, a conversão é realizada implicitamente pelo compilador.

Upcasting está intimamente relacionado à herança - outro conceito central em Java. É comum usar variáveis ​​de referência para se referir a um tipo mais específico. E toda vez que fazemos isso, ocorre upcasting implícito.

Para demonstrar o upcasting, vamos definir uma classeAnimal:

public class Animal {

    public void eat() {
        // ...
    }
}

Agora vamos estenderAnimal:

public class Cat extends Animal {

    public void eat() {
         // ...
    }

    public void meow() {
         // ...
    }
}

Agora podemos criar um objeto da classeCat e atribuí-lo à variável de referência do tipoCat:

Cat cat = new Cat();

E também podemos atribuí-lo à variável de referência do tipoAnimal:

Animal animal = cat;

Na atribuição acima, ocorre upcasting implícito. Poderíamos fazê-lo explicitamente:

animal = (Animal) cat;

Mas não há necessidade de fazer uma conversão explícita na árvore de herança. O compilador sabe quecat é umAnimale não exibe nenhum erro.

Observe que essa referência pode se referir a qualquer subtipo do tipo declarado.

Usando upcasting, restringimos o número de métodos disponíveis para a instânciaCat, mas não alteramos a própria instância. Agora não podemos fazer nada que seja específico paraCat –, não podemos invocarmeow() na variávelanimal.

Embora o objetoCat permaneça objetoCat, chamarmeow() causaria o erro do compilador:

// animal.meow(); The method meow() is undefined for the type Animal

Para invocarmeow(), precisamos fazer downcast deanimal e faremos isso mais tarde.

Mas agora vamos descrever o que nos dá a antecipação. Graças ao upcasting, podemos tirar proveito do polimorfismo.

3.1. Polimorfismo

Vamos definir outra subclasse deAnimal, uma classeDog:

public class Dog extends Animal {

    public void eat() {
         // ...
    }
}

Agora podemos definir o métodofeed() que trata todos os gatos e cães comoanimals:

public class AnimalFeeder {

    public void feed(List animals) {
        animals.forEach(animal -> {
            animal.eat();
        });
    }
}

Não queremos queAnimalFeeder se preocupe com qualanimal está na lista - aCat ou aDog. No métodofeed(), eles são todosanimals.

O upcasting implícito ocorre quando adicionamos objetos de um tipo específico à listaanimals:

List animals = new ArrayList<>();
animals.add(new Cat());
animals.add(new Dog());
new AnimalFeeder().feed(animals);

Adicionamos gatos e cães e eles são convertidos para o tipoAnimal implicitamente. CadaCat é umAnimale cadaDog é umAnimal. Eles são polimórficos.

A propósito, todos os objetos Java são polimórficos porque cada objeto é umObject pelo menos. Podemos atribuir uma instância deAnimal à variável de referência do tipoObjecte o compilador não reclamará:

Object object = new Animal();

É por isso que todos os objetos Java que criamos já têm métodos específicos deObject, por exemplo,toString().

Fazer upcast para uma interface também é comum.

Podemos criar a interfaceMew e fazerCat implementá-la:

public interface Mew {
    public void meow();
}

public class Cat extends Animal implements Mew {

    public void eat() {
         // ...
    }

    public void meow() {
         // ...
    }
}

Agora, qualquer objetoCat também pode ser atualizado paraMew:

Mew mew = new Cat();

Cat é umMew, o upcasting é legal e feito implicitamente.

Assim,Cat é aMew,Animal,Object eCat. Ele pode ser atribuído a variáveis ​​de referência dos quatro tipos em nosso exemplo.

3.2. Substituindo

No exemplo acima, o métodoeat() é substituído. Isso significa que emboraeat() seja chamado na variável do tipoAnimal, o trabalho é feito por métodos chamados em objetos reais - gatos e cães:

public void feed(List animals) {
    animals.forEach(animal -> {
        animal.eat();
    });
}

Se adicionarmos algum registro às nossas classes, veremos que os métodos deCat eDog são chamados:

web - 2018-02-15 22:48:49,354 [main] INFO com.example.casting.Cat - cat is eating
web - 2018-02-15 22:48:49,363 [main] INFO com.example.casting.Dog - dog is eating

Resumindo:

  • Uma variável de referência pode se referir a um objeto se o objeto for do mesmo tipo que uma variável ou se for um subtipo

  • Upcasting acontece implicitamente

  • Todos os objetos Java são polimórficos e podem ser tratados como objetos de supertipo devido a upcasting

4. Downcasting

E se quisermos usar a variável do tipoAnimal para invocar um método disponível apenas para a classeCat? Aí vem o downcasting. It’s the casting from a superclass to a subclass.

Vamos dar um exemplo:

Animal animal = new Cat();

Sabemos que a variávelanimal se refere à instância deCat. E queremos invocar o métodomeow() deCat noanimal. Mas o compilador reclama que o métodomeow() não existe para o tipoAnimal.

Para chamarmeow(), devemos reduziranimal paraCat:

((Cat) animal).meow();

Os parênteses internos e o tipo que eles contêm são chamados de operador de conversão. Observe que parênteses externos também são necessários para compilar o código.

Vamos reescrever o exemplo anteriorAnimalFeeder com o métodomeow():

public class AnimalFeeder {

    public void feed(List animals) {
        animals.forEach(animal -> {
            animal.eat();
            if (animal instanceof Cat) {
                ((Cat) animal).meow();
            }
        });
    }
}

Agora temos acesso a todos os métodos disponíveis para a classeCat. Observe o log para se certificar de quemeow() é realmente chamado:

web - 2018-02-16 18:13:45,445 [main] INFO com.example.casting.Cat - cat is eating
web - 2018-02-16 18:13:45,454 [main] INFO com.example.casting.Cat - meow
web - 2018-02-16 18:13:45,455 [main] INFO com.example.casting.Dog - dog is eating

Observe que, no exemplo acima, estamos tentando reduzir apenas os objetos que são realmente instâncias deCat. Para fazer isso, usamos o operadorinstanceof.

4.1. instanceof Operador

Freqüentemente usamos o operadorinstanceof antes do downcasting para verificar se o objeto pertence ao tipo específico:

if (animal instanceof Cat) {
    ((Cat) animal).meow();
}

4.2. ClassCastException

Se não tivéssemos verificado o tipo com o operadorinstanceof, o compilador não teria reclamado. Mas em tempo de execução, haveria uma exceção.

Para demonstrar isso, vamos remover o operadorinstanceof do código acima:

public void uncheckedFeed(List animals) {
    animals.forEach(animal -> {
        animal.eat();
        ((Cat) animal).meow();
    });
}

Esse código é compilado sem problemas. Mas se tentarmos executá-lo, veremos uma exceção:

java.lang.ClassCastException: com.example.casting.Dog não pode ser lançado emcom.example.casting.Cat

Isso significa que estamos tentando converter um objeto que é uma instância deDog em uma instânciaCat.

ClassCastException's sempre lançado em tempo de execução se o tipo para o qual fizemos o downcast não corresponder ao tipo do objeto real.

Observe que se tentarmos fazer o downcast para um tipo não relacionado, o compilador não permitirá isso:

Animal animal;
String s = (String) animal;

O compilador diz "Não é possível transmitir de Animal para String".

Para o código compilar, os dois tipos devem estar na mesma árvore de herança.

Vamos resumir:

  • O downcasting é necessário para obter acesso a membros específicos da subclasse

  • O downcasting é feito usando o operador de conversão

  • Para fazer o downcast de um objeto com segurança, precisamos do operadorinstanceof

  • Se o objeto real não corresponder ao tipo para o qual fizemos downcast, entãoClassCastException será lançado em tempo de execução

5. MétodoCast()

Há outra maneira de lançar objetos usando os métodos deClass:

public void whenDowncastToCatWithCastMethod_thenMeowIsCalled() {
    Animal animal = new Cat();
    if (Cat.class.isInstance(animal)) {
        Cat cat = Cat.class.cast(animal);
        cat.meow();
    }
}

No exemplo acima, os métodoscast() eisInstance() são usados ​​em vez dos operadores cast einstanceof correspondentemente.

É comum usar os métodoscast()eisInstance() com tipos genéricos.

Vamos criar a classeAnimalFeederGeneric<T> com o métodofeed() que “alimenta” apenas um tipo de animal - gatos ou cães, dependendo do valor do parâmetro de tipo:

public class AnimalFeederGeneric {
    private Class type;

    public AnimalFeederGeneric(Class type) {
        this.type = type;
    }

    public List feed(List animals) {
        List list = new ArrayList();
        animals.forEach(animal -> {
            if (type.isInstance(animal)) {
                T objAsType = type.cast(animal);
                list.add(objAsType);
            }
        });
        return list;
    }

}

O métodofeed() verifica cada animal e retorna apenas aqueles que são instâncias deT.

Observe que a instânciaClass também deve ser passada para a classe genérica, pois não podemos obtê-la no parâmetro de tipoT. No nosso exemplo, passamos no construtor.

Vamos tornarT igual aCate garantir que o método retorne apenas gatos:

@Test
public void whenParameterCat_thenOnlyCatsFed() {
    List animals = new ArrayList<>();
    animals.add(new Cat());
    animals.add(new Dog());
    AnimalFeederGeneric catFeeder
      = new AnimalFeederGeneric(Cat.class);
    List fedAnimals = catFeeder.feed(animals);

    assertTrue(fedAnimals.size() == 1);
    assertTrue(fedAnimals.get(0) instanceof Cat);
}

6. Conclusão

Neste tutorial básico, exploramos o que é upcasting, downcasting, como usá-los e como esses conceitos podem ajudá-lo a tirar proveito do polimorfismo.

Como sempre, o código deste artigo está disponívelover on GitHub.