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


770

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

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

동물 (부모)

- 고양이 (어린이)

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

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


17
그리고 지금 저를 괴롭 히고 전혀 관련이없는 문법 질문 - 내 제목은 "왜해야 하지 자바의 제네릭"또는 "왜 아니다 자바의 제네릭"?? "generics"는 하나의 엔티티이기 때문에 s 또는 단수형으로 인해 복수형입니까?
froadie

25
Java에서 수행되는 제네릭은 매개 변수 다형성의 형태가 매우 좋지 않습니다. 언젠가 그들의 한심한 한계에 부딪 칠 것이기 때문에 그들에게 너무 많은 믿음을 두지 마십시오. 외과의 사는 Handable <Scalpel>, Handable <Sponge> KABOOM! 하는가 하지 [TM]를 계산한다. Java 제네릭 제한이 있습니다. 모든 OOA / OOD는 Java로 잘 번역 될 수 있으며 (MI는 Java 인터페이스를 사용하여 매우 훌륭하게 수행 할 수 있음) 제네릭은이를 잘리지 않습니다. "컬렉션"과 절차 적 프로그래밍에 적합합니다 (대부분의 Java 프로그래머는 그렇게합니다 ...).
SyntaxT3rr0r

8
List <Dog>의 수퍼 클래스는 List <Animal>이 아니라 List <?> (즉, 알 수없는 유형의 목록)입니다. 제네릭은 컴파일 된 코드에서 형식 정보를 지 웁니다. 이것은 generics (java 5 이상)를 사용하는 코드가 generics가없는 이전 버전의 java와 호환되도록하기 위해 수행됩니다.
rai.skumar


9
@froadie는 아무도 응답하지 않는 것처럼 보였으므로 분명히 "Java의 제네릭이 아닌 이유는 ..."이어야합니다. 다른 문제는 "제네릭"이 실제로 형용사이므로 "제네릭"은 "제네릭"에 의해 수정 된 복수 명사를 삭제하는 것입니다. "그 함수는 제네릭"이라고 말할 수 있지만 "그 함수는 제네릭"이라고 말하는 것보다 더 번거로울 것입니다. 그러나 "Java에는 제네릭이 있습니다"대신 "Java에는 제네릭 함수와 클래스가 있습니다"라고 말하는 것이 다소 번거 롭습니다. 형용사에 대한 석사 논문을 쓴 사람으로서, 나는 당신이 매우 흥미로운 질문에 걸려 넘어 졌다고 생각합니다!
dantiston

답변:


916

아니,이 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>.


50
흥미롭게도 모든 개 목록 실제로 직감이 알려주 듯이 동물 목록입니다. 요점은 모든 동물 목록이 개 목록이 아니기 때문에 고양이를 추가하여 목록을 변경하는 것이 문제라는 것입니다.
Ingo

66
@Ingo : 아니요, 실제로는 아닙니다. 고양이를 동물 목록에 추가 할 수는 있지만 고양이를 개 목록에 추가 할 수는 없습니다. 개 목록은 읽기 전용 의미에서 동물의 목록 일뿐입니다.
Jon Skeet

12
@JonSkeet-물론, 누가 고양이와 개 목록에서 새 목록을 만들면 실제로 개 목록이 바뀐다는 명령이 있습니까? 이것은 Java에서 임의의 구현 결정입니다. 논리와 직관에 반하는 것.
Ingo

7
@Ingo : "확실히"시작하는 데 사용하지 않았을 것입니다. 상단에 "우리가 가고 싶은 호텔"이라고 적힌 목록이 있고 누군가 수영장을 추가했다면, 이것이 유효하다고 생각하십니까? 아니요-건물 목록이 아닌 호텔 목록입니다. 그리고 "개 목록은 동물 목록이 아닙니다"라고 말한 것과는 다릅니다- 코드 용어 로 코드 글꼴로 넣었습니다 . 나는 여기에 모호성이 있다고 생각하지 않습니다. 어쨌든 서브 클래스를 사용하는 것은 올바르지 않습니다. 서브 클래스가 아니라 할당 호환성에 관한 것입니다.
Jon Skeet

14
@ruakh : 문제는 컴파일 타임에 차단 될 수있는 무언가를 실행 시간에 맞추는 것입니다. 그리고 배열 공분산은 처음부터 설계 실수라고 주장합니다.
Jon Skeet

84

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

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


44

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

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


36

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

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

또한 사실이다

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

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

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

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


그렇습니다. 인간의 언어는 더 모호합니다. 그러나 여전히 개 목록에 다른 동물을 추가하면 여전히 동물 목록이지만 더 이상 개 목록은 아닙니다. 퍼지 논리를 가진 인간의 차이점은 일반적으로 그것을 깨닫는 데 아무런 문제가 없습니다.
Vlasec

배열에 대한 지속적인 비교를 훨씬 더 혼란스럽게 생각하는 사람이 있으면이 답변이 그것을 못 박았습니다. 내 문제는 언어 직관이었다.
FLonLon

32

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

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

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

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


13
아마도 배열 공분산은 언어 설계 버그입니다. 유형 삭제로 인해 일반 수집에는 동일한 동작이 기술적으로 불가능합니다.
Michael Borgwardt

" 제네릭의 요점은 그것이 허용되지 않는다는 것입니다. ". 당신은 결코 할 수 있는지 : (OOPSLA 2016에서 발표) 널 포인터의 실존 적 위기 : 자바와 스칼라의 타입 시스템 불건전 있습니다 (수정 이후 보인다)
데이비드 Tonhofer

15

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

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

2
정확히 그것 때문에, 자바의 배열이 깨 졌다고 주장 될 수있다 .
leonbloy

공변량 인 배열은 컴파일러 "기능"입니다.
Cristik

6

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

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소비자.


1
4 가지 버전 중 # 2가 잘못되었을 수 있습니다. 예를 들어 (Consumer<Runnable>, Supplier<Dog>)while Dog은 하위 유형 인Animal & Runnable
ZhongYu

5

이러한 행동의 기본 논리 Generics는 유형 삭제 메커니즘 을 따르는 것입니다. 따라서 런타임 에는 이러한 삭제 프로세스가없는 유형과 collection다른 유형을 식별 할 수 없습니다 arrays. 다시 질문으로 돌아와서

따라서 아래에 주어진 방법이 있다고 가정하십시오.

add(List<Animal>){
    //You can add List<Dog or List<Cat> and this will compile as per rules of polymorphism
}

이제 java가 호출자 가이 메소드에 Animal 유형의 List를 추가 할 수있게하면 콜렉션에 잘못된 것을 추가 할 수 있으며 런타임에도 유형 삭제로 인해 실행됩니다. 배열의 경우 그러한 시나리오에 대해 런타임 예외가 발생합니다 ...

따라서 본질적으로이 동작은 구현되어 잘못된 것을 컬렉션에 추가 할 수 없습니다. 이제 제네릭없이 레거시 Java와 호환되도록 유형 삭제가 존재한다고 생각합니다 ....


4

서브 타이핑은 매개 변수화 된 유형에 대해 변하지 않습니다 . 클래스 Dog가 하위 유형 인 Animal경우에도 매개 변수화 된 유형 List<Dog>은 하위 유형 이 아닙니다 List<Animal>. 반대로 공변량 하위 유형 지정은 배열에서 사용되므로 배열 유형 Dog[]은의 하위 유형입니다 Animal[].

변하지 않는 서브 타이핑은 Java에 의해 시행되는 유형 제약 조건이 위반되지 않도록합니다. @Jon Skeet이 제공 한 다음 코드를 고려하십시오.

List<Dog> dogs = new ArrayList<Dog>(1);
List<Animal> animals = dogs;
animals.add(new Cat()); // compile-time error
Dog dog = dogs.get(0);

@Jon Skeet이 말했듯 이이 코드는 불법입니다. 그렇지 않으면 개가 예상했을 때 고양이를 반환하여 유형 제약 조건을 위반하기 때문입니다.

위의 배열과 비슷한 코드를 비교하는 것이 좋습니다.

Dog[] dogs = new Dog[1];
Object[] animals = dogs;
animals[0] = new Cat(); // run-time error
Dog dog = dogs[0];

코드는 합법적입니다. 그러나 배열 저장소 예외가 발생 합니다. JVM은 공변량 서브 타이핑의 유형 안전성을 강제 할 수있는 방식으로 런타임에 해당 유형을 전달합니다.

이를 이해하기 위해 javap아래 클래스에서 생성 한 바이트 코드를 살펴 보겠습니다 .

import java.util.ArrayList;
import java.util.List;

public class Demonstration {
    public void normal() {
        List normal = new ArrayList(1);
        normal.add("lorem ipsum");
    }

    public void parameterized() {
        List<String> parameterized = new ArrayList<>(1);
        parameterized.add("lorem ipsum");
    }
}

command를 사용하면 javap -c Demonstration다음 Java 바이트 코드가 표시됩니다.

Compiled from "Demonstration.java"
public class Demonstration {
  public Demonstration();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void normal();
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: iconst_1
       5: invokespecial #3                  // Method java/util/ArrayList."<init>":(I)V
       8: astore_1
       9: aload_1
      10: ldc           #4                  // String lorem ipsum
      12: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      17: pop
      18: return

  public void parameterized();
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: iconst_1
       5: invokespecial #3                  // Method java/util/ArrayList."<init>":(I)V
       8: astore_1
       9: aload_1
      10: ldc           #4                  // String lorem ipsum
      12: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      17: pop
      18: return
}

번역 된 메소드 본문의 코드가 동일한 지 확인하십시오. 컴파일러는 각 매개 변수화 된 유형을 삭제 로 대체했습니다 . 이 속성은 이전 버전과의 호환성을 손상시키지 않았다는 의미입니다.

결론적으로 컴파일러는 각 매개 변수화 된 유형을 삭제로 대체하기 때문에 매개 변수화 된 유형에는 런타임 안전성이 불가능합니다. 이것은 매개 변수화 된 유형을 구문 설탕에 지나지 않습니다.


3

실제로 인터페이스를 사용하여 원하는 것을 얻을 수 있습니다.

public interface Animal {
    String getName();
    String getVoice();
}
public class Dog implements Animal{
    @Override 
    String getName(){return "Dog";}
    @Override
    String getVoice(){return "woof!";}

}

그런 다음 컬렉션을 사용하여 사용할 수 있습니다

List <Animal> animalGroup = new ArrayList<Animal>();
animalGroup.add(new Dog());

1

목록 항목이 주어진 수퍼 유형의 서브 클래스임을 확신하는 경우 다음 방법을 사용하여 목록을 캐스트 할 수 있습니다.

(List<Animal>) (List<?>) dogs

생성자에서 목록을 전달하거나 반복 할 때 유용합니다.


2
실제로 해결하는 것보다 더 많은 문제가 발생합니다
Ferrybig

고양이를 목록에 추가하려고하면 문제가 발생하지만 반복 목적으로 나는 그것이 장황하지 않은 유일한 대답이라고 생각합니다.
sagits

1

대답 뿐만 아니라 다른 답변이 정확합니다. 나는 도움이 될 것이라고 생각되는 해결책으로 그 대답에 덧붙일 것입니다. 나는 이것이 프로그래밍에서 자주 나온다고 생각한다. 주목해야 할 것은 컬렉션 (리스트, 세트 등)의 주요 문제는 컬렉션에 추가한다는 것입니다. 그곳이 상황이 무너지는 곳입니다. 제거해도 괜찮습니다.

대부분의 경우 우리는 Collection<? extends T>그 대신 사용할 수 Collection<T>있으며 이것이 첫 번째 선택이되어야합니다. 그러나 그렇게하기 어려운 경우를 찾고 있습니다. 그것이 항상 최선의 방법인지에 대한 논쟁이 있습니다. 여기에 표준 접근 방식을 사용할 때 사용되는 Collection<? extends T>a Collection<T>( a , List, Set, NavigableSet, ..에 대한 유사한 클래스를 정의 할 수 있음)로 변환 할 수있는 DownCastCollection 클래스가 제시 되어 있습니다. 아래는 그것을 사용하는 방법의 예입니다 ( Collection<? extends Object>이 경우 에도 사용할 수 있지만 DownCastCollection을 사용하여 설명하는 것이 간단합니다).

/**Could use Collection<? extends Object> and that is the better choice. 
* But I am doing this to illustrate how to use DownCastCollection. **/

public static void print(Collection<Object> col){  
    for(Object obj : col){
    System.out.println(obj);
    }
}
public static void main(String[] args){
  ArrayList<String> list = new ArrayList<>();
  list.addAll(Arrays.asList("a","b","c"));
  print(new DownCastCollection<Object>(list));
}

이제 수업 :

import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;

public class DownCastCollection<E> extends AbstractCollection<E> implements Collection<E> {
private Collection<? extends E> delegate;

public DownCastCollection(Collection<? extends E> delegate) {
    super();
    this.delegate = delegate;
}

@Override
public int size() {
    return delegate ==null ? 0 : delegate.size();
}

@Override
public boolean isEmpty() {
    return delegate==null || delegate.isEmpty();
}

@Override
public boolean contains(Object o) {
    if(isEmpty()) return false;
    return delegate.contains(o);
}
private class MyIterator implements Iterator<E>{
    Iterator<? extends E> delegateIterator;

    protected MyIterator() {
        super();
        this.delegateIterator = delegate == null ? null :delegate.iterator();
    }

    @Override
    public boolean hasNext() {
        return delegateIterator != null && delegateIterator.hasNext();
    }

    @Override
    public  E next() {
        if(!hasNext()) throw new NoSuchElementException("The iterator is empty");
        return delegateIterator.next();
    }

    @Override
    public void remove() {
        delegateIterator.remove();

    }

}
@Override
public Iterator<E> iterator() {
    return new MyIterator();
}



@Override
public boolean add(E e) {
    throw new UnsupportedOperationException();
}

@Override
public boolean remove(Object o) {
    if(delegate == null) return false;
    return delegate.remove(o);
}

@Override
public boolean containsAll(Collection<?> c) {
    if(delegate==null) return false;
    return delegate.containsAll(c);
}

@Override
public boolean addAll(Collection<? extends E> c) {
    throw new UnsupportedOperationException();
}

@Override
public boolean removeAll(Collection<?> c) {
    if(delegate == null) return false;
    return delegate.removeAll(c);
}

@Override
public boolean retainAll(Collection<?> c) {
    if(delegate == null) return false;
    return delegate.retainAll(c);
}

@Override
public void clear() {
    if(delegate == null) return;
        delegate.clear();

}

}


Java SE에 이미 존재하는 것이 좋습니다. ; )Collections.unmodifiableCollection
Radiodef

1
맞지만 내가 정의한 컬렉션은 수정할 수 있습니다.
dan b

예, 수정할 수 있습니다. Collection<? extends E>형식이 안전하지 않은 방식으로 사용하지 않는 한 (예 : 다른 것으로 캐스팅하지 않는 한) 이미 해당 동작을 올바르게 처리합니다. 내가 볼 수있는 유일한 이점은 add작업 을 호출 할 때 캐스팅 한 경우에도 예외가 발생한다는 것입니다.
Vlasec

0

JavaSE 튜토리얼 에서 예제를 보자

public abstract class Shape {
    public abstract void draw(Canvas c);
}

public class Circle extends Shape {
    private int x, y, radius;
    public void draw(Canvas c) {
        ...
    }
}

public class Rectangle extends Shape {
    private int x, y, width, height;
    public void draw(Canvas c) {
        ...
    }
}

따라서 개 (원) 목록이 암시 적으로 동물 (모양) 목록으로 간주되어서는 안되는 이유는 다음과 같습니다.

// drawAll method call
drawAll(circleList);


public void drawAll(List<Shape> shapes) {
   shapes.add(new Rectangle());    
}

따라서 Java "architects"에는이 문제를 해결하는 두 가지 옵션이 있습니다.

  1. 하위 유형이 암시 적으로 수퍼 유형이라는 것을 고려하지 말고 컴파일 오류를 발생 시키십시오.

  2. 하위 유형을 상위 유형으로 간주하고 컴파일시 "add"메소드를 제한하십시오. 따라서 drawAll 메소드에서 원의 목록, 하위 유형의 모양이 전달되는 경우 컴파일러는이를 감지하고 컴파일 오류가 발생하도록 제한해야합니다. 그).

명백한 이유로, 그것은 첫 번째 방법을 선택했습니다.


0

또한 컴파일러가 일반 클래스를 위협하는 방법을 고려해야합니다. 일반 인수를 채울 때마다 다른 유형을 "인스턴스화"합니다.

따라서 우리가 ListOfAnimal, ListOfDog, ListOfCat, 등, 마지막까지 우리가 일반적인 인수를 지정 컴파일러에 의해 "창조"되고 있음을 별개의 클래스입니다있다. 그리고 이것은 평평한 계층입니다 (실제로 List는 계층이 아닙니다).

일반 클래스의 경우 공분산이 의미가없는 또 다른 주장은 기본적으로 모든 클래스가 동일하다는 사실입니다 List. 인스턴스입니다. List일반 인수를 작성하여 a 를 전문화 하면 클래스가 확장되지 않고 특정 일반 인수에 대해서만 작동합니다.


0

문제가 잘 식별되었습니다. 그러나 해결책이 있습니다. 확인 해봐요은 일반적인 :

<T extends Animal> void doSomething<List<T> animals) {
}

이제 List <Dog> 또는 List <Cat> 또는 List <Animal>을 사용하여 doSomething을 호출 할 수 있습니다.


0

또 다른 해결책은 새로운 목록을 작성하는 것입니다

List<Dog> dogs = new ArrayList<Dog>(); 
List<Animal> animals = new ArrayList<Animal>(dogs);
animals.add(new Cat());

0

이 예제 코드를 사용하는 Jon Skeet의 답변 외에도

// 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?

가장 깊은 수준에서, 여기에 문제는 즉 dogsanimals공유 참조. 즉,이 작업을 수행하는 한 가지 방법은 전체 목록을 복사하여 참조 평등을 위반하는 것입니다.

// This code is fine
List<Dog> dogs = new ArrayList<Dog>();
dogs.add(new Dog());
List<Animal> animals = new ArrayList<>(dogs); // Copy list
animals.add(new Cat());
Dog dog = dogs.get(0);   // This is fine now, because it does not return the Cat

호출 한 후 List<Animal> animals = new ArrayList<>(dogs);, 당신은 이후 직접 할당 할 수 없습니다 animals하나에 dogs또는 cats:

// These are both illegal
dogs = animals;
cats = animals;

따라서 잘못된 하위 유형 Animal이 없으므로 목록에 잘못된 하위 유형을 넣을 수 없습니다. 하위 유형의 객체를에 ? extends Animal추가 할 수 있습니다 animals.

분명히 이것은 목록 animalsdogs더 이상 공유되지 않기 때문에 시맨틱을 변경 하므로 하나의 목록에 추가해도 다른 목록에 추가되지 않습니다 (정확히 원하는 것 Cat입니다)는 목록에만 추가 될 수있는 문제를 피하기 위해 Dog개체 를 포함해야 합니다). 또한 전체 목록을 복사하면 비효율적 일 수 있습니다. 그러나 이것은 참조 동등성을 깨뜨림으로써 유형 동등성 문제를 해결합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.