안녕하세요! 우리는 제네릭 주제를 계속 연구합니다. 이전 강의에서 이미 이에 대한 많은 지식을 갖고 있지만( 제네릭 작업 시 varargs 사용 및 유형 삭제 에 대해) 아직 한 가지 중요한 주제인 와일드카드를 다루지 않았습니다 . 이는 제네릭의 매우 중요한 기능입니다. 우리는 그것에 대해 별도의 강의를 할 정도로 너무 많습니다! 그러나 와일드카드에는 복잡한 것이 없습니다. 이제 그 사실을 알게 될 것입니다. :) 예를 살펴보겠습니다.
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 에 지정된 개체만 포함된다는 보장이 손실되었습니다 . 즉, 우리는 제네릭의 주요 이점인 유형 안전성을 잃었습니다. 그리고 컴파일러는 우리에게 이 모든 것을 허용했기 때문에 프로그램 실행 중에만 오류가 발생한다는 것을 의미하며 이는 항상 컴파일 오류보다 훨씬 더 나쁩니다. 이러한 상황을 방지하기 위해 컴파일러는 다음과 같은 오류를 표시합니다. strings
Object
String
// ошибка компиляции
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번의 강의를 들었습니다! 하지만 이제 주제를 잘 이해하셨고 인터뷰에서 자신을 증명하실 수 있을 것입니다. :) 이제 다시 작업으로 돌아갈 시간입니다! 공부에 행운을 빕니다! :)
GO TO FULL VERSION