이 예에서 :
import java.util.*;
public class Example {
static void doesntCompile(Map<Integer, List<? extends Number>> map) {}
static <T extends Number> void compiles(Map<Integer, List<T>> map) {}
static void function(List<? extends Number> outer)
{
doesntCompile(new HashMap<Integer, List<Integer>>());
compiles(new HashMap<Integer, List<Integer>>());
}
}
doesntCompile()
컴파일하지 못했습니다 :
Example.java:9: error: incompatible types: HashMap<Integer,List<Integer>> cannot be converted to Map<Integer,List<? extends Number>>
doesntCompile(new HashMap<Integer, List<Integer>>());
^
compiles()
컴파일러가 승인하는 동안 .
이 답변 의 유일한 차이점은 그와 달리이라고 설명하고 <? ...>
, <T ...>
경우 될 것 같지 않는, 나중에 유형을 참조 할 수 있습니다.
차이점은 무엇이며 <? extends Number>
그리고 <T extends Number>
이 경우 왜 첫 번째 컴파일을하지 않는 이유는 무엇입니까?
답변
다음과 같은 서명으로 메소드를 정의합니다.
static <T extends Number> void compiles(Map<Integer, List<T>> map) {}
다음과 같이 호출하십시오.
compiles(new HashMap<Integer, List<Integer>>());
jls §8.1.2에서 우리는 다음과 같은 사실을 발견했습니다.
일반 클래스 선언은 유형 인수로 유형 매개 변수 섹션을 호출 할 때마다 하나씩 매개 변수화 된 유형 세트 (§4.5)를 정의합니다 . 이러한 매개 변수화 된 유형은 모두 런타임에 동일한 클래스를 공유합니다.
즉, 유형 T
이 입력 유형과 일치하고 할당됩니다 Integer
. 서명이 효과적으로됩니다 static void compiles(Map<Integer, List<Integer>> map)
.
그것이 올 때 doesntCompile
방법, JLS는 (하위 유형의 규칙을 정의 §4.5.1 내 옆에 굵은 글씨를)
유형 인수 T1은 T2로 표시되는 유형 세트가 다음 규칙의 재귀 및 전이 폐쇄에서 T1으로 표시되는 유형 세트의 서브 세트 인 경우 T2 <= T1로 작성된 다른 유형 인수 T2를 포함한다고합니다 ( 여기서 <:은 하위 유형을 나타냅니다 (§4.10).
? T <= 연장 T <: S 인 경우 S를 확장
? T <= 연장
? 슈퍼 T <=? S <: T 인 경우 슈퍼 S
? 슈퍼 T <=?
? 슈퍼 T <=? 객체를 확장
T <= T
T <=? T를 연장
T <=? 슈퍼 T
즉, ? extends Number
실제로 포함 Integer
하거나 List<? extends Number>
포함 List<Integer>
하고 있지만 Map<Integer, List<? extends Number>>
및 의 경우는 아닙니다 Map<Integer, List<Integer>>
. 이 주제에 대한 자세한 내용은 이 SO 스레드에서 찾을 수 있습니다 . 다음과 ?
같은 하위 유형이 있다고 선언 하여 와일드 카드를 사용하여 버전을 작동 시킬 수 있습니다 List<? extends Number>
.
public class Example {
// now it compiles
static void doesntCompile(Map<Integer, ? extends List<? extends Number>> map) {}
static <T extends Number> void compiles(Map<Integer, List<T>> map) {}
public static void main(String[] args) {
doesntCompile(new HashMap<Integer, List<Integer>>());
compiles(new HashMap<Integer, List<Integer>>());
}
}
답변
전화에서 :
compiles(new HashMap<Integer, List<Integer>>());
T는 Integer와 일치하므로 인수의 유형은 Map<Integer,List<Integer>>
입니다. 메서드의 경우에는 해당되지 않습니다. doesntCompile
인수의 유형은 Map<Integer, List<? extends Number>>
호출의 실제 인수에 관계없이 유지됩니다 . 그리고에서 할당 할 수 없습니다 HashMap<Integer, List<Integer>>
.
최신 정보
이 doesntCompile
방법 에서는 다음과 같은 일을 방해하는 것이 없습니다.
static void doesntCompile(Map<Integer, List<? extends Number>> map) {
map.put(1, new ArrayList<Double>());
}
분명히, 그것은 HashMap<Integer, List<Integer>>
논쟁으로 받아 들일 수 없습니다 .
답변
간단한 데모 예. 같은 예를 아래와 같이 시각화 할 수 있습니다.
static void demo(List<Pair<? extends Number>> lst) {} // doesn't work
static void demo(List<? extends Pair<? extends Number>> lst) {} // works
demo(new ArrayList<Pair<Integer>()); // works
demo(new ArrayList<SubPair<Integer>()); // works for subtype too
public static class Pair<T> {}
public static class SubPair<T> extends Pair<T> {}
List<Pair<? extends Number>>
다중 레벨 와일드 카드 유형 인 반면 List<? extends Number>
표준 와일드 카드 유형입니다.
와일드 카드 유형의 유효한 구체적인 인스턴스화 List<? extends Number>
에는 포함 Number
및 모든 하위 유형이 포함 Number
되는 반면 List<Pair<? extends Number>>
인수 인 경우 인수 유형의 유형 인수이고 일반 유형의 구체적인 인스턴스화가 있습니다.
제네릭은 변하지 않으므로 Pair<? extends Number>
와일드 카드 유형은에만 사용할 수 있습니다 Pair<? extends Number>>
. 내부 유형 ? extends Number
은 이미 공변량입니다. 공분산을 허용하려면 둘러싸는 유형을 공분산으로 만들어야합니다.
답변
일반적인 와일드 카드 문서, 특히 와일드 카드 사용 지침 을 살펴 보는 것이 좋습니다.
솔직히 방법을 말하기 #doesntCompile
static void doesntCompile(Map<Integer, List<? extends Number>> map) {}
그리고 같은 전화
doesntCompile(new HashMap<Integer, List<Integer>>());
근본적으로 부정확하다
합법적 인 구현을 추가합시다 :
static void doesntCompile(Map<Integer, List<? extends Number>> map) {
List<Double> list = new ArrayList<>();
list.add(0.);
map.put(0, list);
}
풋 그래서 더블 번호, 확장 때문에, 정말 괜찮 List<Double>
뿐만 아니라 절대적으로 괜찮 List<Integer>
오른쪽?
그러나 귀하의 모범에서 여기로 전달 하는 것이 여전히 합법적 이라고 가정 new HashMap<Integer, List<Integer>>()
하십니까?
컴파일러는 그렇게 생각하지 않으며 그러한 상황을 피하기 위해 최선을 다하고 있습니다.
메소드 #compile을 사용하여 동일한 구현을 시도하면 컴파일러는 이중 목록을 맵에 넣을 수 없습니다.
static <T extends Number> void compiles(Map<Integer, List<T>> map) {
List<Double> list = new ArrayList<>();
list.add(10.);
map.put(10, list); // does not compile
}
기본적으로 아무것도 넣을 수는 없지만 or 또는 or 또는으로 List<T>
해당 메소드를 호출하는 것이 안전합니다 .new HashMap<Integer, List<Integer>>()
new HashMap<Integer, List<Double>>()
new HashMap<Integer, List<Long>>()
new HashMap<Integer, List<Number>>()
간단히 말해, 컴파일러로 속이려고 노력하고 있으며 그러한 속임수에 대해 공정하게 방어합니다.
주의 : Maurice Perry 가 게시 한 답변 은 절대적으로 정확합니다. 나는 그것이 확실하지 않다는 것을 확신하지 못하므로 더 광범위한 게시물을 추가하려고 시도했다.