[java] Java 제네릭에는 언제 <가 필요합니까? <T> 대신 T>를 확장하고 스위칭의 단점이 있습니까?

다음 예에서 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)

그런 다음 컴파일이 작동합니다.

따라서 세 가지 질문이 있습니다.

  1. 현재 버전이 정확히 컴파일되지 않는 이유는 무엇입니까? 여기서 공분산 문제를 모호하게 이해했지만, 필요한 경우 설명 할 수 없었습니다.
  2. assertThat방법을 변경하면 단점 이 Matcher<? extends T>있습니까? 그렇게하면 깨질 다른 사례가 있습니까?
  3. assertThatJUnit 에서 메소드 의 일반화에 대한 요점이 있습니까? MatcherJUnit을 아무것도하지 않는 형태의 안전을 강제하기 위해 원하는 일반, 그냥 외모에 입력되지 않은 일치하는 메소드를 호출하기 때문에이 같은 클래스는, 그것을 필요로하지 않는 것 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정확히 MapStringDate일치하지 않는 클래스 객체 MapString어떤이의에 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()기대하기 때문에 중단 될 수 있습니다.DateLong


답변

와일드 카드를 이해하는 한 가지 방법은 와일드 카드가 제네릭 참조가 제공 할 수있는 가능한 객체의 유형을 지정하지 않고 호환 가능한 다른 제네릭 참조의 유형을 지정한다고 생각하는 것입니다. …) 따라서 첫 번째 대답은 말로 잘못 해석됩니다.

즉, 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가 있으면 TList는 확장하는 유형의 인스턴스를 포함 할 수 있습니다 T. 목록에 동물이 포함 된 경우 목록에는 개와 고양이 (동물 모두)가 모두 포함될 수 있습니다. 개에는 “woofVolume”속성이 있고 고양이에는 “meowVolume”속성이 있습니다. 의 하위 클래스에 따라 이러한 속성을 기준으로 정렬하고 T싶지만이 방법으로 어떻게 할 수 있습니까? Comparator의 한계는 하나의 유형 ( T) 만 두 가지만 비교할 수 있다는 것입니다 . 따라서 단순히 a를 요구하면 Comparator<T>이 방법을 사용할 수있게됩니다. 그러나이 방법을 만든 사람은 무언가가 T이면 수퍼 클래스의 인스턴스 라는 것을 인식했습니다 T. 따라서, 그는의 비교기 사용할 수있게 해준다 T또는 슈퍼 클래스 T즉, ? super T.


답변

당신이 사용하면 어떻게

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