CodeGym/Java Blog/무작위의/제네릭의 와일드카드
John Squirrels
레벨 41
San Francisco

제네릭의 와일드카드

무작위의 그룹에 게시되었습니다
회원
안녕! 제네릭 연구를 계속합시다. 당신은 이미 이전 수업( 제네릭으로 작업할 때 varargs 사용유형 삭제 에 대한 정보)에서 이에 대한 상당한 지식을 얻었 지만 아직 고려하지 않은 중요한 주제인 와일드카드 가 있습니다 . 이것은 제네릭의 매우 중요한 기능입니다. 우리는 그것에 대해 별도의 레슨을 할애했습니다! 즉, 와일드카드에 대해 특별히 복잡한 것은 없습니다. 바로 확인할 수 있습니다 :) 제네릭의 와일드카드 - 1예를 살펴보겠습니다.
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;
   }
}
무슨 일이야? 우리는 매우 유사한 두 가지 상황을 봅니다. 이 경우 String객체를 객체로 캐스트합니다 Object. 여기에는 문제가 없습니다. 모든 것이 예상대로 작동합니다. 그러나 두 번째 상황에서 컴파일러는 오류를 생성합니다. 하지만 우리는 같은 일을 하고 있습니다. 그렇죠? 이번에는 단순히 여러 개체의 컬렉션을 사용하고 있습니다. 그런데 왜 오류가 발생합니까? 차이점이 뭐야? String하나 의 객체를 하나 Object또는 20개의 객체 로 캐스팅합니까 ? 개체개체 모음 사이에는 중요한 차이점이 있습니다 . 클래스가 클래스의 하위인 경우 는 의 하위가 아닙니다 . BACollection<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>를 가리키는 참조를 만들 수 있도록 허용했기 때문에 컬렉션 에 strings이전 개체를 추가할 수 있습니다 ! 따라서 컬렉션 에 일반 형식 호출의 형식 인수로 지정된 개체 만 포함된다는 보장이 없습니다 . 즉, 제네릭의 주요 이점인 형식 안전성을 잃었습니다. 그리고 컴파일러가 이 작업을 중단하지 않았기 때문에 런타임에만 오류가 발생하며 이는 항상 컴파일 오류보다 훨씬 나쁩니다. 이와 같은 상황을 방지하기 위해 컴파일러는 다음과 같은 오류를 표시합니다. ObjectstringsString
// Compilation error
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()");
   }
}
계층 구조는 Pet에 의해 상속되는 간단한 Animal 클래스에 의해 맨 위에 있습니다. Pet에는 Dog와 Cat이라는 2개의 하위 클래스가 있습니다. 이제 간단한 메서드를 만들어야 한다고 가정합니다 iterateAnimals(). 메서드는 모든 동물( Animal, Pet, Cat, Dog) 컬렉션을 가져와 모든 요소를 ​​반복하고 각 반복 동안 콘솔에 메시지를 표시해야 합니다. 다음과 같은 방법을 작성해 봅시다.
public static void iterateAnimals(Collection<Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
문제가 해결된 것 같습니다! 그러나 최근에 알게 된 것처럼 , 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("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);
   }
}
상황이 좋지 않아 보입니다! 각 종류의 동물을 열거하기 위해 별도의 메서드를 작성해야 합니까? 실제로는 그렇지 않습니다. :) 그리고 공교롭게도 와일드카드가 도움이 됩니다! 다음 구성을 사용하여 간단한 방법으로 문제를 해결할 수 있습니다.
public static void iterateAnimals(Collection<? extends Animal> animals) {

   for(Animal animal: animals) {

       System.out.println("Another iteration in the loop!");
   }
}
이것은 와일드카드입니다. 보다 정확하게는 여러 유형의 와일드카드 중 첫 번째 유형입니다. 이는 상한 와일드카드 로 알려져 있으며 ? 로 표현됩니다. 확장합니다 . 이 구조는 우리에게 무엇을 말합니까? 이는 메서드가 (? extends Animal) 에서 파생된 모든 클래스의 개체 컬렉션 Animal또는 개체 컬렉션을 허용함을 의미합니다. 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);
}
콘솔 출력:
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!
총 4개의 컬렉션과 8개의 개체를 만들었고 콘솔에는 정확히 8개의 항목이 있습니다. 모든 것이 훌륭하게 작동합니다! :) 와일드카드를 사용하면 특정 유형에 연결된 필수 논리를 단일 메서드에 쉽게 맞출 수 있습니다. 각 동물 유형에 대해 별도의 메서드를 작성할 필요가 없습니다. 우리의 애플리케이션이 동물원이나 수의사 사무실에서 사용된다면 얼마나 많은 메서드가 필요했을지 상상해 보세요 :) 하지만 이제 다른 상황을 살펴보겠습니다. 상속 계층 구조는 변경되지 않습니다. 최상위 클래스는 이고 Animal바로 Pet아래에 클래스가 있고 다음 수준에 클래스가 있습니다 Cat. 이제 개를 제외한 모든 유형의 동물과 작동하도록 메서드를 Dog다시 작성해야 합니다 . 즉, 받아들여야 한다 .iterateAnimals()Collection<Animal>Collection<Pet>또는 Collection<Car>와 함께 작동하지 않아야 합니다 Collection<Dog>. 이것을 어떻게 달성할 수 있습니까? 각 유형에 대해 별도의 메소드를 작성할 가능성에 다시 직면하는 것 같습니다./우리가 원하는 것을 컴파일러에 설명하는 다른 방법은 무엇입니까? 실제로 매우 간단합니다! 여기서 다시 한 번 와일드카드가 도움이 됩니다. 그러나 이번에는 다른 유형의 와일드카드를 사용할 것입니다. 즉, 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!");
   }
}
여기서 원리는 비슷합니다. 구성 은 메서드가 개체 컬렉션 이나 클래스의 조상을 입력으로 받아들일 수 있음 <? super Cat>을 컴파일러에 알립니다 . 이 경우 클래스, 해당 부모 및 해당 부모의 부모 가 모두 이 설명과 일치합니다. 클래스 가 우리의 제한 사항과 일치하지 않으므로 인수와 함께 메서드를 사용하려고 하면 컴파일 오류가 발생합니다. iterateAnimals()CatCatCatPetAnimalDogList<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);

   // Compilation error!
   iterateAnimals(dogs);
}
우리는 문제를 해결했고 다시 한 번 와일드카드가 매우 유용한 것으로 판명되었습니다 :) 이것으로 수업이 끝났습니다. 이제 Java 연구에서 제네릭이 얼마나 중요한지 알 수 있습니다. 제네릭에 대한 4개의 전체 강의가 있습니다! 하지만 이제 당신은 주제에 정통하고 면접에서 당신의 능력을 증명할 수 있습니다 :) 그리고 이제 작업으로 돌아갈 시간입니다! 귀하의 학업에서 최고의 성공을 거두십시오! :)
코멘트
  • 인기
  • 신규
  • 이전
코멘트를 남기려면 로그인 해야 합니다
이 페이지에는 아직 코멘트가 없습니다