JavaRush /Blogue Java /Random-PT /Curingas em Java genéricos

Curingas em Java genéricos

Publicado no grupo Random-PT
Olá! Continuamos estudando o tema dos genéricos. Você já tem bastante conhecimento sobre eles em palestras anteriores (sobre o uso de varargs ao trabalhar com genéricos e sobre apagamento de tipos ), mas ainda não abordamos um tópico importante: curingas . Esta é uma característica muito importante dos genéricos. Tanto é que dedicamos uma palestra separada para isso! Porém, não há nada de complicado em curingas, você verá isso agora :) Curingas em genéricos - 1Vejamos um exemplo:
public class Main {

   public static void main(String[] args) {

       String str = new String("Test!");
       // ниHowих проблем
       Object obj = str;

       List<String> strings = new ArrayList<String>();
       // ошибка компиляции!
       List<Object> objects = strings;
   }
}
O que está acontecendo aqui? Vemos duas situações muito semelhantes. No primeiro deles, estamos tentando converter um objeto Stringpara type Object. Não há problemas com isso, tudo funciona como deveria. Mas na segunda situação, o compilador gera um erro. Embora pareça que estamos fazendo a mesma coisa. Só que agora estamos usando uma coleção de vários objetos. Mas por que o erro ocorre? Qual é a diferença, essencialmente, entre convertermos um objeto Stringem um tipo Objectou 20 objetos? Há uma diferença importante entre um objeto e uma coleção de objetos . Se uma classe é sucessora de uma classe , então ela não é sucessora . É por esta razão que não pudemos trazer o nosso . é herdeiro , mas não é herdeiro . Intuitivamente, isso não parece muito lógico. Por que os criadores da linguagem foram guiados por este princípio? Vamos imaginar que o compilador não nos daria erro aqui: BАCollection<B>Collection<A>List<String>List<Object>StringObjectList<String>List<Object>
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 erros e nos permitiu criar uma referência List<Object> objecta uma coleção de strings strings, podemos adicionar stringsnão uma string, mas simplesmente qualquer objeto Object! Assim, perdemos a garantia de que nossa coleção contém apenas os objetos especificados no genéricoString . Ou seja, perdemos a principal vantagem dos genéricos – tipo segurança. E como o compilador nos permitiu fazer tudo isso, significa que só teremos um erro durante a execução do programa, o que é sempre muito pior que um erro de compilação. Para evitar tais situações, o compilador nos dá um erro:
// ошибка компиляции
List<Object> objects = strings;
...e lembra a ele que List<String>ele não é um herdeiro List<Object>. Esta é uma regra rígida para a operação de genéricos e deve ser lembrada ao usá-los. Vamos continuar. Digamos 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()");
   }
}
No topo da hierarquia estão simplesmente os animais: os animais de estimação são herdados deles. Os animais de estimação são divididos em 2 tipos – Cães e Gatos. Agora imagine que precisamos criar um método simples iterateAnimals(). O método deve aceitar uma coleção de quaisquer animais ( Animal, Pet, Cat, Dog), iterar por todos os elementos e gerar algo no console a cada vez. Vamos tentar escrever este método:
public static void iterateAnimals(Collection<Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
Parece que o problema está resolvido! No entanto, como descobrimos recentemente, List<Cat>ou não List<Dog>somos List<Pet>herdeiros List<Animal>! Portanto, quando tentarmos chamar um método iterateAnimals()com uma lista de gatos, receberemos um erro do compilador:
import java.util.*;

public class Main3 {


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

       for(Animal animal: animals) {

           System.out.println("Еще один шаг в цикле пройден!");
       }
   }

   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());

       //ошибка компилятора!
       iterateAnimals(cats);
   }
}
A situação não parece boa para nós! Acontece que teremos que escrever métodos separados para enumerar todos os tipos de animais? Na verdade, não, você não precisa :) E os curingas nos ajudarão com isso ! Resolveremos 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("Еще один шаг в цикле пройден!");
   }
}
Este é o curinga. Mais precisamente, este é o primeiro de vários tipos de curinga - “ estende ” (outro nome é Upper Bounded Wildcards ). O que esse design nos diz? Isso significa que o método recebe como entrada uma coleção de objetos de classe Animalou objetos de qualquer classe descendente Animal (? extends Animal). AnimalEm outras palavras, o método pode aceitar a coleção , Pet, Dogou como entrada Cat- não faz diferença. Vamos ter certeza de que isso 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:

Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
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 com ligação a tipos específicos em um método. Eliminamos a necessidade de escrever um método separado para cada tipo de animal. Imagine quantos métodos teríamos se nosso aplicativo fosse usado em um zoológico ou clínica veterinária :) Agora vamos ver uma situação diferente. Nossa hierarquia de herança permanecerá a mesma: a classe de nível superior é Animal, logo abaixo está a classe Pets Pete no próximo nível está Cate Dog. Agora você precisa reescrever o método iretateAnimals()para que ele funcione com qualquer tipo de animal, exceto cães . Collection<Animal>Ou seja, deveria aceitar , Collection<Pet>ou como entrada Collection<Cat>, mas não deveria funcionar com Collection<Dog>. Como podemos conseguir isso? Parece que a perspectiva de escrever um método separado para cada tipo está surgindo diante de nós novamente:/ De que outra forma podemos explicar nossa lógica ao compilador? E isso pode ser feito de forma muito simples! Aqui, os curingas virão em nosso auxílio novamente. Mas desta vez usaremos um tipo diferente - “ super ” (outro nome é Lower Bounded Wildcards ).
public static void iterateAnimals(Collection<? super Cat> animals) {

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

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
O princípio é semelhante aqui. A construção <? super Cat>informa ao compilador que o método iterateAnimals()pode tomar como entrada uma coleção de objetos da classe Catou de qualquer outra classe ancestral Cat. CatNo nosso caso, a própria classe , seu ancestral - Petse o ancestral do ancestral - se enquadram nessa descrição Animal. A classe Dognão se enquadra nesta restrição e, portanto, tentar usar um método com uma lista List<Dog>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);

   //ошибка компиляции!
   iterateAnimals(dogs);
}
Nosso problema foi resolvido e, novamente, os curingas revelaram-se extremamente úteis :) Isso conclui a palestra. Agora você vê como o tópico genérico é importante ao aprender Java - passamos 4 palestras sobre isso! Mas agora você tem um bom domínio do assunto e poderá provar seu valor na entrevista :) E agora é a hora de voltar às tarefas! Boa sorte em seus estudos! :)
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION