CodeGym /Blogue Java /Random-PT /Curingas em genéricos
John Squirrels
Nível 41
San Francisco

Curingas em genéricos

Publicado no grupo Random-PT
Oi! Vamos continuar nosso estudo dos genéricos. Você já adquiriu um conhecimento substancial sobre eles em lições anteriores (sobre o uso de varargs ao trabalhar com genéricos e sobre apagamento de tipo ), mas há um tópico importante que ainda não consideramos - curingas . Esta é uma característica muito importante dos genéricos. Tanto é assim que dedicamos uma aula separada para isso! Dito isso, não há nada particularmente complicado sobre curingas. Você verá isso imediatamente :) Curingas em genéricos - 1Vejamos um exemplo:

public class Main {

   public static void main(String[] args) {
      
       String str = new String("Test!");
       // No problem
       Object obj = str;
      
       List<String> strings = new ArrayList<String>();
       // Compilation error!
       List<Object> objects = strings;
   }
}
O que está acontecendo aqui? Vemos duas situações muito semelhantes. No caso, lançamos um Stringobjeto para um Objectobjeto. Não há problemas aqui - tudo funciona conforme o esperado. Mas na segunda situação, o compilador gera um erro. Mas estamos fazendo a mesma coisa, não estamos? Desta vez, estamos simplesmente usando uma coleção de vários objetos. Mas por que o erro ocorre? Qual é a diferença? Estamos lançando um Stringobjeto para um Objectou 20 objetos? Há uma distinção importante entre um objeto e uma coleção de objetos . Se a Bclasse é filha da Aclasse, então Collection<B>não é filha de Collection<A>. É por isso que não conseguimos lançar nosso List<String>para umList<Object>. Stringé filho de Object, mas List<String>não é filho de List<Object>. Isso pode não parecer super intuitivo. Por que os criadores da linguagem fizeram isso dessa maneira? Vamos imaginar que o compilador não nos dê um erro:

List<String> strings = new ArrayList<String>();
List<Object> objects = strings;
Neste caso, poderíamos, por exemplo, fazer o seguinte:

objects.add(new Object());
String s = strings.get(0);
Como o compilador não nos deu nenhum erro e nos permitiu criar uma List<Object>referência que aponta para strings, podemos adicionar qualquer Objectobjeto antigo à stringscoleção! Assim, perdemos a garantia de que nossa coleção contém apenas os Stringobjetos especificados pelo argumento de tipo na invocação de tipo genérico . Em outras palavras, perdemos a principal vantagem dos genéricos — segurança de tipo. E como o compilador não nos impediu de fazer isso, teremos um erro apenas em tempo de execução, que é sempre muito pior do que um erro de compilação. Para evitar situações como esta, o compilador nos dá um erro:

// Compilation error
List<Object> objects = strings;
...e lembra que List<String>não é descendente de List<Object>. Esta é uma regra rígida para genéricos e deve ser lembrada ao trabalhar com eles. Vamos continuar. Suponha que temos uma pequena hierarquia de classes:

public class Animal {

   public void feed() {

       System.out.println("Animal.feed()");
   }
}

public class Pet extends Animal {

   public void call() {

       System.out.println("Pet.call()");
   }
}

public class Cat extends Pet {

   public void meow() {

       System.out.println("Cat.meow()");
   }
}
A hierarquia é encimada por uma classe Animal simples, que é herdada por Pet. Pet tem 2 subclasses: Dog e Cat. Agora suponha que precisamos criar um iterateAnimals()método simples. O método deve pegar uma coleção de quaisquer animais ( Animal, Pet, Cat, Dog), iterar sobre todos os elementos e exibir uma mensagem no console durante cada iteração. Vamos tentar escrever tal método:

public static void iterateAnimals(Collection<Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
Parece que o problema está resolvido! No entanto, como aprendemos recentemente, List<Cat>, List<Dog>e List<Pet>não são descendentes de List<Animal>! Isso significa que quando tentamos chamar o iterateAnimals()método com uma lista de gatos, obtemos um erro de compilação:

import java.util.*;

public class Main3 {


   public static void iterateAnimals(Collection<Animal> animals) {

       for(Animal animal: animals) {

           System.out.println("Another iteration in the loop!");
       }
   }

   public static void main(String[] args) {


       List<Cat> cats = new ArrayList<>();
       cats.add(new Cat());
       cats.add(new Cat());
       cats.add(new Cat());
       cats.add(new Cat());

       // Compilation error!
       iterateAnimals(cats);
   }
}
A situação não nos parece muito boa! Temos que escrever métodos separados para enumerar cada tipo de animal? Na verdade, não, não temos :) E acontece que os curingas nos ajudam com isso! Podemos resolver o problema com um método simples usando a seguinte construção:

public static void iterateAnimals(Collection<? extends Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
Este é um curinga. Mais precisamente, este é o primeiro de vários tipos de curingas. É conhecido como curinga de limite superior e é expresso por ? estende . O que essa construção nos diz? Isso significa que o método aceita uma coleção de Animalobjetos ou uma coleção de objetos de qualquer classe descendente de Animal(? estende Animal). Em outras palavras, o método pode aceitar uma coleção de objetos Animal, Pet, Dogou Catobjetos — não faz diferença. Vamos nos convencer de que funciona:

public static void main(String[] args) {

   List<Animal> animals = new ArrayList<>();
   animals.add(new Animal());
   animals.add(new Animal());

   List<Pet> pets = new ArrayList<>();
   pets.add(new Pet());
   pets.add(new Pet());

   List<Cat> cats = new ArrayList<>();
   cats.add(new Cat());
   cats.add(new Cat());

   List<Dog> dogs = new ArrayList<>();
   dogs.add(new Dog());
   dogs.add(new Dog());

   iterateAnimals(animals);
   iterateAnimals(pets);
   iterateAnimals(cats);
   iterateAnimals(dogs);
}
Saída do console:

Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Another iteration in the loop!
Criamos um total de 4 coleções e 8 objetos, e há exatamente 8 entradas no console. Tudo funciona muito bem! :) O curinga nos permitiu ajustar facilmente a lógica necessária vinculada a tipos específicos em um único método. Eliminamos a necessidade de escrever um método separado para cada tipo de animal. Imagine quantos métodos precisaríamos se nosso aplicativo fosse usado por um zoológico ou um consultório veterinário :) Mas agora vamos ver uma situação diferente. Nossa hierarquia de herança permanece inalterada: a classe de nível superior é Animal, com a Petclasse logo abaixo e as classes Cate Dogno próximo nível. Agora você precisa reescrever o iterateAnimals()método para que funcione com qualquer tipo de animal, exceto cães . Ou seja, deve aceitar Collection<Animal>,Collection<Pet>ou Collection<Car>, mas não deve funcionar com Collection<Dog>. Como podemos conseguir isso? Parece que enfrentamos novamente a perspectiva de escrever um método separado para cada tipo :/ De que outra forma explicamos ao compilador o que queremos que aconteça? Na verdade é bem simples! Mais uma vez, os curingas vêm em nosso auxílio aqui. Mas desta vez usaremos outro tipo de curinga — um curinga de limite inferior , que é expresso usando super .

public static void iterateAnimals(Collection<? super Cat> animals) {

   for(int i = 0; i < animals.size(); i++) {

       System.out.println("Another iteration in the loop!");
   }
}
Aqui o princípio é semelhante. A <? super Cat>construção informa ao compilador que o iterateAnimals()método pode aceitar como entrada uma coleção de Catobjetos ou qualquer ancestral da Catclasse. Nesse caso, a Catclasse, seu pai Pete o pai de seu pai, Animaltodos correspondem a essa descrição. A Dogclasse não corresponde à nossa restrição, portanto, uma tentativa de usar o método com um List<Dog>argumento resultará em um erro de compilação:

public static void main(String[] args) {

   List<Animal> animals = new ArrayList<>();
   animals.add(new Animal());
   animals.add(new Animal());

   List<Pet> pets = new ArrayList<>();
   pets.add(new Pet());
   pets.add(new Pet());

   List<Cat> cats = new ArrayList<>();
   cats.add(new Cat());
   cats.add(new Cat());

   List<Dog> dogs = new ArrayList<>();
   dogs.add(new Dog());
   dogs.add(new Dog());

   iterateAnimals(animals);
   iterateAnimals(pets);
   iterateAnimals(cats);
  
   // Compilation error!
   iterateAnimals(dogs);
}
Resolvemos nosso problema e, mais uma vez, os curingas se mostraram extremamente úteis :) Com isso, a lição chegou ao fim. Agora você vê como os genéricos são importantes em seu estudo de Java — tivemos 4 lições completas sobre eles! Mas agora você está bem familiarizado com o assunto e pode provar suas habilidades em entrevistas de emprego :) E agora é hora de voltar às tarefas! Muito sucesso em seus estudos! :)
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION