다음 예에서 Hamcrest 매처와 함께 JUnit 사용 :
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)
그런 다음 컴파일이 작동합니다.
따라서 세 가지 질문이 있습니다.
- 현재 버전이 정확히 컴파일되지 않는 이유는 무엇입니까? 여기서 공분산 문제를 모호하게 이해했지만, 필요한 경우 설명 할 수 없었습니다.
assertThat
방법을 변경하면 단점 이Matcher<? extends T>
있습니까? 그렇게하면 깨질 다른 사례가 있습니까?assertThat
JUnit 에서 메소드 의 일반화에 대한 요점이 있습니까?Matcher
JUnit을 아무것도하지 않는 형태의 안전을 강제하기 위해 원하는 일반, 그냥 외모에 입력되지 않은 일치하는 메소드를 호출하기 때문에이 같은 클래스는, 그것을 필요로하지 않는 것Matcher
사실 만하지 않습니다 일치하면 테스트가 실패합니다. 안전하지 않은 작업이 필요하지 않습니다 (또는 그렇게 보입니다).
참고로 다음은 JUnit 구현입니다 assertThat
.
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());
}
}
답변
먼저 http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html 로 안내해야합니다 . 그녀는 놀라운 일을합니다.
기본 아이디어는 당신이 사용하는 것입니다
<T extends SomeClass>
실제 매개 변수가 될 수 SomeClass
있거나 하위 유형일 수 있습니다 .
귀하의 예에서
Map<String, Class<? extends Serializable>> expected = null;
Map<String, Class<java.util.Date>> result = null;
assertThat(result, is(expected));
당신은 expected
을 구현하는 모든 클래스를 나타내는 Class 객체를 포함 할 수 있다고 말하고 있습니다 Serializable
. 결과 맵은 Date
클래스 객체 만 보유 할 수 있다고 말합니다 .
당신이 결과를 전달하면있는 거 설정 T
정확히 Map
의 String
에 Date
일치하지 않는 클래스 객체 Map
의 String
어떤이의에 Serializable
.
한 가지 확인해야 할 사항-확실 Class<Date>
하지 Date
않습니까? 의지도 String
로는 Class<Date>
일반적으로 대단히 유용 소리가 나지 않는다 (이 보유 할 수있는 모든입니다 Date.class
값이 아닌 인스턴스로 Date
)
일반화 assertThat
와 관련 하여이 메소드는 메소드가 Matcher
결과 유형에 맞는를 전달 하도록 보장 할 수 있습니다 .
답변
질문에 답변 한 모든 사람들에게 감사의 말을 전하는 것이 정말 도움이되었습니다. 결국 Scott Stanchfield의 답변은 내가 그것을 이해하는 방법에 가장 가깝습니다. 그러나 그가 처음 썼을 때 그를 이해하지 못했기 때문에 문제를 다시 언급하여 다른 누군가가 혜택을 볼 수 있도록 노력하고 있습니다.
하나의 일반적인 매개 변수 만 가지고 있으므로 이해하기 쉽기 때문에 List 측면에서 질문을 다시 설명하겠습니다.
매개 변수화 된 클래스 (예 : 목록 <Date>
또는 맵 과 같은)의 목적은 다운 캐스트<K, V>
를 강제 실행 하고 컴파일러가 이것이 안전한지 (런타임 예외 없음) 보장하도록하는 것입니다.
List의 경우를 고려하십시오. 내 질문의 본질은 유형 T와 목록을 취하는 메소드가 T보다 상속 체인보다 더 아래에있는 목록을 허용하지 않는 이유입니다.이 고안된 예를 고려하십시오.
List<java.util.Date> dateList = new ArrayList<java.util.Date>();
Serializable s = new String();
addGeneric(s, dateList);
....
private <T> void addGeneric(T element, List<T> list) {
list.add(element);
}
list 매개 변수는 문자열 목록이 아닌 날짜 목록이므로 컴파일되지 않습니다. 이것이 컴파일되면 제네릭은별로 유용하지 않습니다.
동일한 내용이지도에 적용됩니다.지도 <String, Class<? extends Serializable>>
와 같은 것은 아닙니다 <String, Class<java.util.Date>>
. 그들은 공변량이 아니므로 날짜 클래스가 포함 된 맵에서 값을 가져 와서 직렬화 가능한 요소가 포함 된 맵에 넣으려면 괜찮지 만 메소드 서명은 다음과 같습니다.
private <T> void genericAdd(T value, List<T> list)
둘 다 할 수 있기를 원합니다.
T x = list.get(0);
과
list.add(value);
이 경우 junit 메소드는 실제로 이러한 사항을 신경 쓰지 않지만 메소드 서명에는 공분산이 필요하므로 가져 오지 않습니다. 따라서 컴파일되지 않습니다.
두 번째 질문에서
Matcher<? extends T>
T가 API 의도가 아닌 객체 일 때 실제로 아무것도 받아들이지 않는 단점이 있습니다. 의도는 매 처가 실제 오브젝트와 일치하는지 정적으로 확인하는 것이며 해당 계산에서 오브젝트를 제외 할 방법이 없습니다.
세 번째 질문에 대한 답변은 확인되지 않은 기능면에서 아무것도 손실되지 않을 것입니다 (이 방법이 일반화되지 않은 경우 JUnit API 내에 안전하지 않은 유형 캐스팅이 없음). 그러나 다른 것을 달성하려고합니다. 두 매개 변수가 일치 할 가능성이 있습니다.
편집 (추가 숙고 및 경험 후) :
assertThat 메소드 서명의 큰 문제 중 하나는 변수 T를 일반 매개 변수 T와 동일시하려는 시도입니다. 공변량이 아니기 때문에 작동하지 않습니다. 따라서 예를 들어 T는 List<String>
있지만 컴파일러가 수행하는 일치 항목을 전달할 수 Matcher<ArrayList<T>>
있습니다. 타입 매개 변수가 아니라면 List와 ArrayList는 공변량이기 때문에 문제가 없지만 컴파일러가 ArrayList를 요구하는 한 Generics 때문에 List가 허용되지 않기 때문에 분명한 이유가 있습니다. 위에서.
답변
그것은 다음과 같이 요약됩니다 :
Class<? extends Serializable> c1 = null;
Class<java.util.Date> d1 = null;
c1 = d1; // compiles
d1 = c1; // wont compile - would require cast to Date
클래스 참조 c1이 Long 인스턴스를 포함 할 수 있음을 알 수 있습니다 (주어진 시간에 기본 오브젝트가 List<Long>
있었기 때문에). “알 수없는”클래스가 Date라는 보장이 없기 때문에 분명히 Date로 캐스트 할 수 없습니다. 일반적으로 안전하지 않으므로 컴파일러에서 허용하지 않습니다.
그러나 List와 같은 다른 객체를 소개하면 (예 :이 객체는 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라고 말하지는 않습니다).
답변
원래 코드는 컴파일하지 않는 이유는 즉 <? extends Serializable>
수행 하지 , “직렬화를 확장하는 모든 클래스,”하지만, “직렬화를 확장 알 수없는하지만 특정 클래스.”평균
예를 들어 작성된 코드가 지정된 경우에 할당하는 것이 완벽하게 유효 new TreeMap<String, Long.class>()>
합니다 expected
. 컴파일러가 코드 컴파일을 허용 한 경우 맵에서 찾은 객체 대신 객체를 assertThat()
기대하기 때문에 중단 될 수 있습니다.Date
Long
답변
와일드 카드를 이해하는 한 가지 방법은 와일드 카드가 제네릭 참조가 제공 할 수있는 가능한 객체의 유형을 지정하지 않고 호환 가능한 다른 제네릭 참조의 유형을 지정한다고 생각하는 것입니다. …) 따라서 첫 번째 대답은 말로 잘못 해석됩니다.
즉, List<? extends Serializable>
유형이 알 수없는 유형이거나 Serializable의 하위 클래스 인 다른 목록에 해당 참조를 할당 할 수 있음을 의미합니다. SINGLE LIST가 Serializable의 서브 클래스를 보유 할 수 있다고 생각하지 마십시오.
답변
나는 이것이 오래된 질문이라는 것을 알고 있지만 경계 와일드 카드를 잘 설명한다고 생각되는 예를 공유하고 싶습니다. java.util.Collections
이 방법을 제공합니다 :
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
List가 있으면 T
List는 확장하는 유형의 인스턴스를 포함 할 수 있습니다 T
. 목록에 동물이 포함 된 경우 목록에는 개와 고양이 (동물 모두)가 모두 포함될 수 있습니다. 개에는 “woofVolume”속성이 있고 고양이에는 “meowVolume”속성이 있습니다. 의 하위 클래스에 따라 이러한 속성을 기준으로 정렬하고 T
싶지만이 방법으로 어떻게 할 수 있습니까? Comparator의 한계는 하나의 유형 ( T
) 만 두 가지만 비교할 수 있다는 것입니다 . 따라서 단순히 a를 요구하면 Comparator<T>
이 방법을 사용할 수있게됩니다. 그러나이 방법을 만든 사람은 무언가가 T
이면 수퍼 클래스의 인스턴스 라는 것을 인식했습니다 T
. 따라서, 그는의 비교기 사용할 수있게 해준다 T
또는 슈퍼 클래스 T
즉, ? super T
.
답변
당신이 사용하면 어떻게
Map<String, ? extends Class<? extends Serializable>> expected = null;
