2009-05-22 6 views
149

이 (Hamcrest 매처 (matcher)와 사용의 JUnit) 다음의 예를 감안할 때 :Java generics는 언제 <T> 대신 <? extends T>을 필요로하며 전환의 단점이 있습니까?

Map<String, Class<? extends Serializable>> expected = null; 
Map<String, Class<java.util.Date>> result = null; 
assertThat(result, is(expected)); 

이의 JUnit을 assertThat 메소드 서명과 함께 컴파일되지 않습니다 :

public static <T> void assertThat(T actual, Matcher<T> matcher) 

컴파일러 오류 메시지입니다 :

Error:Error:line (102)cannot find symbol method 
assertThat(java.util.Map<java.lang.String,java.lang.Class<java.util.Date>>, 
org.hamcrest.Matcher<java.util.Map<java.lang.String,java.lang.Class 
    <? extends java.io.Serializable>>>) 

그러나 assertThat 메서드 서명을 다음과 같이 변경하면

public static <T> void assertThat(T result, Matcher<? extends T> matcher) 

그러면 컴파일이 작동합니다.

그래서 세 가지 질문 :

  1. 이유는 정확히 현재 버전은 컴파일되지 않습니다? 내가 여기서 공분산 문제를 모호하게 이해하고 있지만 분명히 설명 할 수는 없습니다. Matcher<? extends T>assertThat 방법을 변경하는 어떤 단점은
  2. 있습니까? 당신이 그렇게했다면 어쩔 수없는 다른 경우가 있습니까?
  3. JUnit에서 assertThat 메서드를 제네릭 화하는 데 요점이 있습니까? JUnit을 어떤 일반적으로 입력되지 않은 일치 메서드를 호출, 그냥 아무것도하지 않는 형태의 안전을 강제하려는 시도처럼 보인다 때문에 Matcher 클래스는, 그것을 필요로하지 않는 것 Matcher처럼하지 않습니다 사실 일치하고, 테스트는 상관없이 실패합니다. 안전하지 않은 작업이 포함되어 있지 않습니다 (또는 그렇게 보입니다).

는 참고로, 여기 assertThat의 JUnit을 구현 한 것입니다 :

public static <T> void assertThat(T actual, Matcher<T> matcher) { 
    assertThat("", actual, matcher); 
} 

public static <T> void assertThat(String reason, T actual, Matcher<T> matcher) { 
    if (!matcher.matches(actual)) { 
     Description description = new StringDescription(); 
     description.appendText(reason); 
     description.appendText("\nExpected: "); 
     matcher.describeTo(description); 
     description 
      .appendText("\n  got: ") 
      .appendValue(actual) 
      .appendText("\n"); 

     throw new java.lang.AssertionError(description.toString()); 
    } 
} 
+0

링크가 매우 유용합니다 (제네릭, 상속 및 하위 유형). http://docs.oracle.com/javase/tutorial/java/generics/inheritance.html –

답변

107

먼저 - 나는 http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html로 안내해야 - 그녀는 놀라운 일을한다.

기본 개념

실제 매개 변수가 SomeClass 또는 그 부속 유형이 될 수있을 때 당신이

<T extends SomeClass> 

를 사용하는 것입니다. 당신의 예에서

,

Map<String, Class<? extends Serializable>> expected = null; 
Map<String, Class<java.util.Date>> result = null; 
assertThat(result, is(expected)); 

당신은 expectedSerializable를 구현하는 클래스를 나타내는 Class 객체를 포함 할 수 없다는 것입니다. 결과 맵에는 Date 클래스 개체 만 저장할 수 있다고 나와 있습니다.

당신이 결과를 전달

, 당신은 Serializable의 아무것도 StringMap 일치하지 않는 String Date에 클래스 객체, 정확히 MapT을 설정하고 있습니다.

한 가지 확인 - 당신이 Class<Date>하지 Date 하시겠습니까?assertThat genericizing에 관해서는

, 아이디어는 방법은 Matcher 보장 할 수 있다는 것입니다 Class<Date>-String의지도는 일반적으로 대단히 유용 소리가 나지 않는다 (값보다는 Date의 인스턴스로 저장할 수있는 모든 Date.class이다) 그 결과 유형은. 전달 된 원래 코드가 컴파일되지 않습니다

+0

이 경우 예 수업. 내가 준 예제는 내 사용자 정의 클래스가 아닌 표준 JDK 클래스를 사용하도록 고안되었지만이 경우 클래스는 실제로 리플렉션을 통해 인스턴스화되고 키를 기반으로 사용됩니다. (클라이언트가 사용할 수있는 서버 클래스가없는 분산 응용 프로그램, 서버 쪽 작업을 수행하는 데 사용할 클래스의 핵심). – Yishai

+5

필자는 두뇌가 고착 된 곳에서 Date 형의 클래스를 포함하는 Map이 Serializable 유형의 클래스를 포함하는 Map 유형에 잘 들어 맞지 않는 이유가 무엇인지 추측합니다. 물론 Serializable 유형의 클래스는 다른 클래스 일 수도 있지만 확실히 Date 유형을 포함합니다. – Yishai

+0

assertThat에서 캐스트가 수행되었는지 확인하기 위해 matcher.matches() 메서드는 신경 쓰지 않으므로 T가 사용되지 않으므로 왜 사용합니까? (메서드 반환 유형은 void입니다.) – Yishai

8

이유는 <? extends Serializable>는 "직렬화 확장하는 모든 클래스"하지 평균을 않고 있다는 점이다 맞는 "직렬화 확장 알 수없는하지만 특정 클래스를."

예를 들어 코드를 작성한대로 new TreeMap<String, Long.class>()>expected에 할당하는 것은 전적으로 유효합니다. 컴파일러가 코드를 컴파일하도록 허용 한 경우 assertThat()은지도에서 찾은 Long 개 객체 대신 Date 개 객체를 기대하기 때문에 아마도 중단됩니다.

+1

나는 다음과 같지 않습니다 - "말은하지 않는다 ...하지만 ..."이라고 말할 때 : 차이점은 무엇입니까? (예를 들어, 이전 정의에 맞는 "알려진하지만 비특이적"클래스의 예제는 무엇입니까?) – poundifdef

+0

예, 약간 어색합니다. 그것을 더 잘 표현하는 방법을 모르겠다 ... " '?라고 말하는 것이 더 합리적입니까? 알 수없는 유형이 무엇이든 일치하는 유형이 아닙니까? " – erickson

+0

설명하는 데 도움이되는 것은 "Serializable을 확장하는 모든 클래스"에서 단순히 을 사용하는 것입니다. – c0der

5

와일드 카드를 이해하는 한 가지 방법은 와일드 카드가 일반적인 참조가 가질 수있는 가능한 객체의 유형을 지정하지 않고 있다고 생각하는 것입니다.하지만 다른 일반 참조의 유형은 (이것은 혼란스럽게 들릴지도 모릅니다 ...) 이와 같이, 첫 번째 대답은 문구가 매우 오도하는 것입니다.

다른 말로, List<? extends Serializable>은 해당 유형이 유형을 알 수없는 유형이거나 Serializable의 서브 클래스 인 다른 목록에 지정할 수 있음을 의미합니다. 한개의리스트가 Serializable의 서브 클래스를 가질 수 있다는 측면에서 생각하지 말라. 왜냐하면 그것은 잘못된 의미이고 Generics에 대한 오해를 불러 오기 때문이다.

+0

확실히 도움이되지만 "혼란 스러울 수 있습니다."는 "혼란스러운 소리"로 대체됩니다. 후속 조치로서, 왜이 설명에 따라, Matcher' '컴파일 방법이 있습니까? – Yishai

+0

List 로 정의하면 똑같은 일을합니까? 너의 2 번째 파라에 대해 말하고있다. 다형성은 그것을 다룰 것인가? –

10

이 아래로 비등 :

당신은 분명히 (주어진 시간에 기본 객체가 List<Long>을 수 있었다 때문에) 클래스 참조 (C1)는 긴 인스턴스를 포함 할 수 있습니다 볼 수 있지만,

는 캐스트 할 수없는

Class<? extends Serializable> c1 = null; 
Class<java.util.Date> d1 = null; 
c1 = d1; // compiles 
d1 = c1; // wont compile - would require cast to Date 
"unknown"클래스가 Date라는 보장이 없으므로 날짜. typsesafe가 아니므로 컴파일러는이를 허용하지 않습니다. 우리가 다른 객체를 도입하는 경우

그러나 목록 (귀하의 예제에서이 개체입니다 Matcher를가), 다음이 참이 말 :

List<Class<? extends Serializable>> l1 = null; 
List<Class<java.util.Date>> l2 = null; 
l1 = l2; // wont compile 
l2 = l1; // wont compile 

... 그러나, 목록의 유형이 될 경우 ? 대신 T의 T ....

List<? extends Class<? extends Serializable>> l1 = null; 
List<? extends Class<java.util.Date>> l2 = null; 
l1 = l2; // compiles 
l2 = l1; // won't compile 

내가 생각이 Matcher<T> to Matcher<? extends T>을 변경하여 확장, 당신은 기본적으로 L1 = L2 할당과 비슷한 시나리오를 소개하고,

중첩 된 와일드 카드를 사용하는 것은 여전히 ​​혼란 스럽지만 잘 알려진 바와 같이 일반적인 참조를 서로 할당 할 수있는 방법을 살펴봄으로써 왜 제네릭을 이해하는지 이해하는 데 도움이됩니다. 컴파일러가 함수 호출을 할 때 컴파일러가 T 타입을 유추하기 때문에 더 혼란 스럽습니다 (명시 적으로 T가 아니라고 말하지는 않습니다). 당신이 질문에 대답 모든 사람에게

Map<String, ? extends Class<? extends Serializable>> expected = null; 
1

, 그것은 정말 나를 위해 일을 명확히 도왔다. 결국 Scott Stanchfield의 대답은 내가 이해하는 방법에 가장 가깝지만, 처음 쓴 때를 이해하지 못했기 때문에 문제를 다시 말하면서 다른 사람에게 도움이되기를 바랍니다.

목록에 관한 질문을 다시 말하지만, 일반적인 매개 변수가 하나뿐이므로 이해하기 쉽습니다.

매개 화 클래스의 목적 강제하는 것입니다 (예 : 목록 <Date>로 또는 예에서와 같이 <K, V>지도) 풀이 죽은이가 (아무 런타임 예외) 안전하다는 것을 컴파일러 보증을합니다.

목록의 경우를 고려하십시오.

List<java.util.Date> dateList = new ArrayList<java.util.Date>(); 
Serilizable s = new String(); 
addGeneric(s, dateList); 

.... 
private <T> void addGeneric(T element, List<T> list) { 
    list.add(element); 
} 

것은이 컴파일되지 않습니다 : 내 질문의 본질은 더 T.에 비해 상속의 체인에 무언가의 목록을 허용하지 않습니다 타입 T와 목록을 소요하는 방법이 고안 한 예제를 생각해 이유 왜냐하면 list 매개 변수는 문자열 목록이 아닌 날짜 목록이기 때문입니다. generics는 이것이 컴파일 된 경우별로 유용하지 않습니다.

지도에 똑같은 내용이 적용됩니다. <String, Class<? extends Serializable>>지도 <String, Class<java.util.Date>>과 같은 것은 아닙니다. 내가 날짜 클래스를 포함하는지도에서 값을 가지고 직렬화 요소를 포함지도에 넣어 싶어 그래서 만약 그들이, 공변되지 않은, 즉 괜찮지 만, 메소드 서명은 말한다 :

private <T> void genericAdd(T value, List<T> list) 

가되고 싶어

T x = list.get(0); 

과의 JUnit 방법은 실제로 이러한 것들에 대해 신경 쓰지 않는다하더라도이 경우

list.add(value); 

는, 메소드 서명이 필요합니다 둘 다 할 수 공분산은 얻지 못하기 때문에 컴파일되지 않습니다. 두 번째 질문에

,
Matcher<? extends T> 

은 T는 API를 의도하지 않은 개체 때 정말 아무것도 수용의 단점을 것입니다. Matcher가 실제 객체와 일치하는지 정적으로 확인하는 것이 목적이므로 해당 계산에서 Object를 제외 할 방법이 없습니다.

세 번째 질문에 대한 답은 확인되지 않은 기능 (이 메소드가 제네릭 화되지 않은 경우 JUnit API 내에서 안전하지 않은 typecasting이 없을 것입니다)에서 손실 될 것이 없지만 다른 것을 달성하려고합니다. 두 매개 변수가 일치 할 가능성이 있음을 정적으로 확인합니다. 시도가 작동하지 않습니다 T.의 일반적인 매개 변수와 변수 T를 동일시하는 것입니다 assertThat 방법 서명 큰 문제

하나, 그들이 있기 때문에 :

EDIT (더 사색과 경험 후) 공변량이 아닙니다. 예를 들어, List<String> 인 T를 가질 수도 있지만 컴파일러가 Matcher<ArrayList<T>>으로 일치하는 항목을 전달하십시오. List와 ArrayList는 공 변하기 때문에 generic이 되었기 때문에 컴파일러가 ArrayList를 필요로하는 한 명확한 희망을 이유로 List를 용인 할 수 없습니다 위에서.

+0

네, 이건 내 위의 대답이 몰고 다니던 것과 같습니다. – GreenieMeanie

+0

아니, 그 상황을, 적어도 내가 시도한 방식으로 도움이되지 않습니다. – Yishai

21

감사를 사용하는 경우 어떤

+0

나는 아직도 내가 왜 상륙 할 수 없는지 모르겠다. 왜 날짜 목록을 직렬화 가능 목록으로 변환 할 수 없습니까? –

+0

@ThomasAhle 왜냐하면 날짜 목록에 있다고 생각하는 참조는 문자열이나 다른 serializables를 찾을 때 캐스팅 오류가 발생하기 때문입니다. – Yishai

+0

필자가 볼 수 있듯이'List ' 타입의 메소드에서'List '을 반환 한 것처럼 오래된 참조를 어떻게 없앨 수 있습니까? Java에서 허용하지 않더라도 안전해야합니다. –

2

이 질문은 오래된 질문이지만, 경계 된 와일드 카드를 잘 설명하는 예제를 공유하고자합니다.우리가 T의 목록이있는 경우

public static <T> void sort(List<T> list, Comparator<? super T> c) { 
    list.sort(c); 
} 

이 목록은 물론, T을 확장 형식의 인스턴스를 포함 할 수 있습니다 : java.util.Collections이 방법을 제공합니다. 목록에 동물이 포함되어 있으면 목록에 개와 고양이 (동물 모두)가 포함될 수 있습니다. 개에는 "woofVolume"속성이 있고 고양이에는 "meowVolume"속성이 있습니다. T의 하위 클래스에 해당하는 속성을 기반으로 정렬 할 수도 있지만이 방법을 사용하려면 어떻게해야할까요? Comparator의 한계는 단 하나의 유형 (T)의 두 가지만을 비교할 수 있다는 것입니다. 따라서 단순히 Comparator<T>을 요구하면이 방법을 사용할 수 있습니다. 그러나이 메소드의 작성자는 무언가가 T 인 경우 T의 수퍼 클래스 인스턴스이기도하다는 것을 인식했습니다. 따라서 그는 T의 비교기 또는 T의 수퍼 클래스 (예 : ? super T)를 사용할 수 있습니다.

관련 문제