JavaRush /Java Blog /Random-KO /Java Generics의 와일드카드

Java Generics의 와일드카드

Random-KO 그룹에 게시되었습니다
안녕하세요! 우리는 제네릭 주제를 계속 연구합니다. 이전 강의에서 이미 이에 대한 많은 지식을 갖고 있지만( 제네릭 작업 시 varargs 사용유형 삭제 에 대해) 아직 한 가지 중요한 주제인 와일드카드를 다루지 않았습니다 . 이는 제네릭의 매우 중요한 기능입니다. 우리는 그것에 대해 별도의 강의를 할 정도로 너무 많습니다! 그러나 와일드카드에는 복잡한 것이 없습니다. 이제 그 사실을 알게 될 것입니다. :) 제네릭의 와일드카드 - 1예를 살펴보겠습니다.
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;
   }
}
여기서 무슨 일이 일어나고 있는 걸까요? 우리는 매우 유사한 두 가지 상황을 봅니다. 첫 번째에서는 객체를 String유형으로 캐스팅하려고 합니다 Object. 여기에는 문제가 없으며 모든 것이 정상적으로 작동합니다. 그러나 두 번째 상황에서는 컴파일러에서 오류가 발생합니다. 우리도 같은 일을 하고 있는 것 같지만. 이제 우리는 여러 개체의 컬렉션을 사용하고 있습니다. 그런데 왜 오류가 발생하는 걸까요? String본질적으로 하나의 객체를 유형으로 캐스팅하든 20개 객체로 캐스팅하든 차이점은 무엇입니까 Object? 객체 와 객체 컬렉션 사이에는 중요한 차이점이 있습니다 . 클래스가 B클래스의 후속 클래스인 А경우 Collection<B>이는 후속 클래스가 아닙니다 Collection<A>. 이런 이유로 우리는 우리를 가져올 수 없었 List<String>습니다 List<Object>. String상속인이지만 Object상속인 List<String>은 아닙니다 List<Object>. 직관적으로 이것은 별로 논리적이지 않아 보입니다. 언어 창시자들이 이 원칙을 따르는 이유는 무엇입니까? 컴파일러가 여기서 오류를 발생시키지 않는다고 상상해 봅시다:
List<String> strings = new ArrayList<String>();
List<Object> objects = strings;
이 경우 예를 들어 다음을 수행할 수 있습니다.
objects.add(new Object());
String s = strings.get(0);
컴파일러는 우리에게 오류를 주지 않았고 우리가 List<Object> object문자열 모음에 대한 참조를 생성할 수 있도록 허용했기 때문에 우리는 문자열이 아니라 단순히 임의의 객체를 strings추가할 수 있습니다 ! 따라서 우리 컬렉션에는 generic 에 지정된 개체만 포함된다는 보장이 손실되었습니다 . 즉, 우리는 제네릭의 주요 이점인 유형 안전성을 잃었습니다. 그리고 컴파일러는 우리에게 이 모든 것을 허용했기 때문에 프로그램 실행 중에만 오류가 발생한다는 것을 의미하며 이는 항상 컴파일 오류보다 훨씬 더 나쁩니다. 이러한 상황을 방지하기 위해 컴파일러는 다음과 같은 오류를 표시합니다. stringsObjectString
// ошибка компиляции
List<Object> objects = strings;
...그리고 그에게 List<String>자신이 상속자가 아니라는 점을 상기시켜 줍니다 List<Object>. 이는 제네릭 작동에 대한 철칙이므로 사용 시 반드시 기억해야 합니다. 계속 진행합시다. 작은 클래스 계층 구조가 있다고 가정해 보겠습니다.
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()");
   }
}
계층 구조의 선두에는 단순히 동물이 있습니다. 애완 동물은 동물로부터 물려받습니다. 애완동물은 개와 고양이 2가지 유형으로 나뉜다. 이제 간단한 메소드를 작성해야 한다고 상상해 보십시오 iterateAnimals(). Animal이 메서드는 모든 동물( , Pet, Cat, ) 컬렉션을 허용 Dog하고 모든 요소를 ​​반복하며 매번 콘솔에 무언가를 출력해야 합니다. 이 메소드를 작성해 봅시다:
public static void iterateAnimals(Collection<Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
문제가 해결된 것 같습니다! 그러나 최근에 우리가 알게 된 것처럼, List<Cat>또는 상속인이 List<Dog>아닙니다 ! 따라서 고양이 목록이 포함된 메서드를 호출하려고 하면 컴파일러 오류가 발생합니다. List<Pet>List<Animal>iterateAnimals()
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);
   }
}
상황은 우리에게 좋지 않아 보입니다! 모든 종류의 동물을 열거하려면 별도의 메소드를 작성해야 합니까? 사실, 아니요, 그럴 필요는 없습니다 :) 와일드 카드가 도움이 될 것입니다 ! 다음 구성을 사용하여 하나의 간단한 방법으로 문제를 해결하겠습니다.
public static void iterateAnimals(Collection<? extends Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
이것이 와일드카드입니다. 더 정확하게 말하면 이것은 여러 유형의 와일드카드 중 첫 번째인 " 확장 "(다른 이름은 Upper Bounded Wildcards 입니다 )입니다. 이 디자인은 우리에게 무엇을 말해주는가? 즉, 메소드는 클래스 객체 컬렉션 Animal이나 하위 클래스의 객체 컬렉션을 입력으로 사용합니다 Animal (? extends Animal). Animal즉, 메서드는 컬렉션 , Pet또는 Dog을 입력으로 받아들일 수 Cat있으며 아무런 차이가 없습니다. 이것이 작동하는지 확인해 봅시다:
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);
}
콘솔 출력:

Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
Еще один шаг в цикле пройден!
총 4개의 컬렉션과 8개의 객체를 생성했으며 콘솔에는 정확히 8개의 항목이 있습니다. 모든 것이 잘 작동합니다! :) 와일드카드를 사용하면 특정 유형을 하나의 메소드로 바인딩하여 필요한 로직을 쉽게 맞출 수 있었습니다. 각 동물 유형에 대해 별도의 메서드를 작성할 필요가 없어졌습니다. 우리 애플리케이션이 동물원이나 동물병원에서 사용된다면 얼마나 많은 방법을 갖게 될지 상상해 보세요. :) 이제 다른 상황을 살펴보겠습니다. 상속 계층 구조는 동일하게 유지됩니다. 최상위 클래스는 이고 Animal, 바로 아래 클래스는 Pets 클래스 Pet이며, 다음 레벨은 Cat및 입니다 Dog. 이제 개를 제외한iretateAnimals() 모든 유형의 동물에 대해 작동할 수 있도록 메서드를 다시 작성해야 합니다 . 즉 , 또는 를 입력으로 받아들여야 하지만 와 함께 작동해서는 안 됩니다 . 어떻게 이를 달성할 수 있나요? 각 유형에 대해 별도의 메소드를 작성할 가능성이 다시 우리 앞에 다가오고 있는 것 같습니다./ 컴파일러에게 논리를 또 어떻게 설명할 수 있습니까? 그리고 이것은 매우 간단하게 이루어질 수 있습니다! 여기서 와일드카드가 다시 도움이 될 것입니다. 그러나 이번에는 " super "(다른 이름은 Lower Bounded Wildcards ) 라는 다른 유형을 사용하겠습니다 . Collection<Animal>Collection<Pet>Collection<Cat>Collection<Dog>
public static void iterateAnimals(Collection<? super Cat> animals) {

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

       System.out.println("Еще один шаг в цикле пройден!");
   }
}
여기서도 원리는 비슷합니다. 이 구성은 <? super Cat>메소드가 iterateAnimals()클래스 Cat또는 다른 상위 클래스 의 객체 컬렉션을 입력으로 사용할 수 있음을 컴파일러에 알려줍니다 Cat. Cat우리의 경우 클래스 자체 , 해당 조상 Pets및 조상의 조상이 이 설명에 적합합니다 Animal. 클래스는 Dog이 제약 조건에 맞지 않으므로 목록이 있는 메서드를 사용하려고 하면 List<Dog>컴파일 오류가 발생합니다.
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);
}
문제는 해결되었고 와일드카드는 매우 유용한 것으로 나타났습니다. :) 이것으로 강의를 마칩니다. 이제 Java를 배울 때 제네릭 주제가 얼마나 중요한지 알 수 있습니다. 우리는 이에 대해 4번의 강의를 들었습니다! 하지만 이제 주제를 잘 이해하셨고 인터뷰에서 자신을 증명하실 수 있을 것입니다. :) 이제 다시 작업으로 돌아갈 시간입니다! 공부에 행운을 빕니다! :)
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION