[java] List <Dog>는 List <Animal>의 하위 클래스입니까? Java 제네릭이 암시 적으로 다형성이 아닌 이유는 무엇입니까?

Java 제네릭이 상속 / 다형성을 처리하는 방법에 대해 약간 혼란 스럽습니다.

다음과 같은 계층 구조를 가정하십시오.

동물 (부모)

고양이 (어린이)

따라서 방법이 있다고 가정 doSomething(List<Animal> animals)합니다. 상속과 다형성의 규칙 모든함으로써, 나는이 가정 것 List<Dog> 입니다List<Animal>하고는 List<Cat> 있습니다List<Animal> 그래서 둘 중 하나가이 메서드에 전달 될 수있다 -. 별로. 이 동작을 달성하려면을 말하여 Animal의 하위 클래스 목록을 수락하도록 메소드에 명시 적으로 지시해야합니다 doSomething(List<? extends Animal> animals).

나는 이것이 Java의 행동이라는 것을 이해합니다. 내 질문은 ? 다형성이 일반적으로 암시적인 이유는 있지만 제네릭과 관련하여 지정해야합니까?



답변

아니,이 List<Dog>입니다 하지List<Animal> . 당신이 함께 할 수있는 일이 무엇인지 생각해 List<Animal>– 당신은 추가 할 수 있는 고양이를 포함 … 그것에 동물. 이제 강아지의 쓰레기에 논리적으로 고양이를 추가 할 수 있습니까? 절대적으로하지.

// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new ArrayList<Dog>(); // ArrayList implements List
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?

갑자기 당신은 매우 혼란스러운 고양이 를 가지고 있습니다 .

지금, 당신은 할 수없는 a를 CatA와 List<? extends Animal>당신이 그것이 알고하지 않기 때문에 List<Cat>. 값을 검색하여 값이임을 알 Animal수 있지만 임의의 동물을 추가 할 수는 없습니다. 반대의 경우도 마찬가지입니다 List<? super Animal>.이 경우 Animal에는에 안전하게 추가 할 수 있지만 검색 할 수있는 항목에 대한 정보는 알 수 없습니다 List<Object>.


답변

찾고있는 것을 공변량 유형 매개 변수 라고 합니다. 즉, 메소드에서 한 유형의 오브젝트를 다른 유형 Animal으로 대체 할 수있는 경우 (예 : 로 대체 가능 Dog) 해당 오브젝트를 사용하는 표현식에도 동일하게 적용됩니다 (따라서 List<Animal>대체 가능 List<Dog>). 문제는 공분산이 일반적으로 가변 목록에 대해 안전하지 않다는 것입니다. 가 있고 List<Dog>으로 사용되고 있다고 가정합니다 List<Animal>. 이에 고양이를 추가하려고하면 어떻게됩니까 List<Animal>정말이다 List<Dog>? 유형 매개 변수를 공변량으로 자동 허용하면 유형 시스템이 중단됩니다.

형식 매개 변수를 공변량으로 지정할 수 있도록 구문을 추가하면 ? extends Fooin 메서드 선언 을 피할 수 있지만 더 복잡해집니다.


답변

a List<Dog>가 아닌 이유는 List<Animal>예를 들어,에 a Cat를 삽입 할 수 List<Animal>있지만 a 에 삽입 할 수는 없습니다 List<Dog>. 가능한 경우 와일드 카드를 사용하여 제네릭을보다 확장 가능하게 만들 수 있습니다. 예를 들어 a에서 읽는 것은 a List<Dog>에서 읽는 것과 비슷 List<Animal>하지만 쓰기는 아닙니다.

자바 언어제네릭자바 튜토리얼의 제네릭 섹션은 왜 어떤 것이 다형성이거나 제네릭으로 허용되지 않는지에 대해 매우 잘 설명되어 있습니다.


답변

다른 답변에서 언급 한 것에 추가해야한다고 생각하는 점 은

List<Dog>자바가 아니다List<Animal>

또한 사실이다

강아지의 목록입니다-A 동물의 목록 영어 (물론, 합리적인 해석에 따라)

OP의 직관이 작동하는 방식은 물론 완전히 유효합니다. 후자의 문장입니다. 그러나이 직관을 적용하면 유형 체계에 Java가 아닌 언어가 생깁니다. 언어에서 개 목록에 고양이를 추가 할 수 있다고 가정합니다. 그게 무슨 뜻이야? 그것은 목록이 개 목록이 아니라 동물 목록 일 뿐이라는 의미입니다. 그리고 포유류의 목록과 4 인의 목록.

List<Dog>다시 말해서, 자바에서 A 는 영어로 “개 목록”을 의미하는 것이 아니라 “개를 가질 수있는 목록”을 의미합니다.

보다 일반적으로 OP의 직관은 객체에 대한 작업이 유형을 변경할 수있는 언어에 적합 하거나 오히려 객체의 유형이 값의 (동적) 함수입니다.


답변

나는 Generics의 요점은 그것을 허용하지 않는다는 것입니다. 해당 유형의 공분산을 허용하는 배열의 상황을 고려하십시오.

  Object[] objects = new String[10];
  objects[0] = Boolean.FALSE;

이 코드는 잘 컴파일되지만 런타임 오류가 발생합니다 ( java.lang.ArrayStoreException: java.lang.Boolean두 번째 줄). 형식이 안전하지 않습니다. 제네릭의 요점은 컴파일 타임 타입 안전을 추가하는 것입니다. 그렇지 않으면 제네릭이없는 일반 클래스를 고수 할 수 있습니다.

이제이 좀 더 유연하게해야 할 시간은 그리고 그 무엇이다 ? super Class? extends Class에 대한 것입니다. 전자는 형식에 삽입해야 할 때 Collection(예 :), 후자는 형식에 안전한 형식으로 읽어야 할 때입니다. 그러나 동시에 두 가지를 수행하는 유일한 방법은 특정 유형을 사용하는 것입니다.


답변

문제를 이해하려면 배열과 비교하는 것이 좋습니다.

List<Dog>의 하위 클래스 가 아닙니다List<Animal> .
그러나 Dog[] 이다 의 서브 클래스 Animal[].

배열은 수정 가능 하며 공변량 입니다.
수정 가능은 유형 정보가 런타임시 완전히 사용 가능함을 의미합니다.
따라서 배열은 런타임 유형 안전을 제공하지만 컴파일 타임 유형 안전은 제공하지 않습니다.

    // All compiles but throws ArrayStoreException at runtime at last line
    Dog[] dogs = new Dog[10];
    Animal[] animals = dogs; // compiles
    animals[0] = new Cat(); // throws ArrayStoreException at runtime

제네릭의 경우도 마찬가지입니다.
제네릭이 지워지고 변하지 않습니다 .
따라서 제네릭은 런타임 형식 안전을 제공 할 수 없지만 컴파일 타임 형식 안전을 제공합니다.
아래 코드에서 제네릭이 공변량 인 경우 3 행에서 힙 오염 을 만들 수 있습니다 .

    List<Dog> dogs = new ArrayList<>();
    List<Animal> animals = dogs; // compile-time error, otherwise heap pollution
    animals.add(new Cat());


답변

여기에 주어진 대답은 완전히 확신하지 못했습니다. 대신 다른 예를 들어 보겠습니다.

public void passOn(Consumer<Animal> consumer, Supplier<Animal> supplier) {
    consumer.accept(supplier.get());
}

잘 들리나요? 그러나 Consumers 에만 s와 Suppliers를 전달할 수 있습니다 Animal. 당신이 경우 Mammal소비자하지만, Duck공급 업체 모두 동물이 있지만, 그들은 맞지한다. 이것을 허용하지 않기 위해 추가 제한이 추가되었습니다.

위 대신에 사용하는 유형 간의 관계를 정의해야합니다.

예 :

public <A extends Animal> void passOn(Consumer<A> consumer, Supplier<? extends A> supplier) {
    consumer.accept(supplier.get());
}

소비자에게 적합한 유형의 물건을 제공하는 공급 업체 만 사용할 수 있도록합니다.

OTOH, 우리도 할 수 있었다

public <A extends Animal> void passOn(Consumer<? super A> consumer, Supplier<A> supplier) {
    consumer.accept(supplier.get());
}

우리가 다른 방향으로가는 곳 :의 유형을 정의하고에 Supplier넣을 수 있도록 제한합니다 Consumer.

우린 할 수있어

public <A extends Animal> void passOn(Consumer<? super A> consumer, Supplier<? extends A> supplier) {
    consumer.accept(supplier.get());
}

직관적 인 관계를 가진 경우, Life-> Animal-> Mammal-> Dog, Cat등, 우리는 심지어 넣을 수 Mammal으로 Life소비자 아니지만 StringLife소비자.