[java] 일반 반환 유형 상한-인터페이스와 클래스-놀랍도록 유효한 코드

이것은 타사 라이브러리 API의 실제 예이지만 단순화되었습니다.

Oracle JDK 8u72로 컴파일

다음 두 가지 방법을 고려하십시오.

<X extends CharSequence> X getCharSequence() {
    return (X) "hello";
}

<X extends String> X getString() {
    return (X) "hello";
}

둘 다 “체크되지 않은 캐스트”경고를보고합니다. 이유가 있습니다. 나를 방해하는 것은 왜 전화 할 수 있는지

Integer x = getCharSequence();

그리고 컴파일? 컴파일러는 Integer구현하지 않는 것을 알고 있어야합니다 CharSequence. 전화

Integer y = getString();

예상대로 오류를 제공합니다

incompatible types: inference variable X has incompatible upper bounds java.lang.Integer,java.lang.String

누군가 왜이 행동이 유효한 것으로 설명 할 수 있습니까? 어떻게 유용할까요?

클라이언트는이 호출이 안전하지 않다는 것을 모릅니다. 클라이언트 코드는 경고없이 컴파일됩니다. 컴파일에서 오류에 대해 경고하지 않는 이유는 무엇입니까?

또한이 예제와 어떻게 다른가요?

<X extends CharSequence> void doCharSequence(List<X> l) {
}

List<CharSequence> chsL = new ArrayList<>();
doCharSequence(chsL); // compiles

List<Integer> intL = new ArrayList<>();
doCharSequence(intL); // error

전달하려고하면 List<Integer>예상대로 오류가 발생합니다.

method doCharSequence in class generic.GenericTest cannot be applied to given types;
  required: java.util.List<X>
  found: java.util.List<java.lang.Integer>
  reason: inference variable X has incompatible bounds
    equality constraints: java.lang.Integer
    upper bounds: java.lang.CharSequence

이것이 오류로보고되면 왜 Integer x = getCharSequence();그렇지 않습니까?



답변

CharSequence입니다 interface. 따라서 SomeClass구현하지 않아도 CharSequence클래스를 완벽하게 만들 수 있습니다.

class SubClass extends SomeClass implements CharSequence

그러므로 당신은 쓸 수 있습니다

SomeClass c = getCharSequence();

유추 된 유형 X이 교차 유형 이기 때문 SomeClass & CharSequence입니다.

이것은 최종 Integer이기 때문에 조금 이상 Integer하지만 final이 규칙에서 아무런 역할을하지 않습니다. 예를 들어 쓸 수 있습니다

<T extends Integer & CharSequence>

반면에, String되지 interface는 연장 불가능하다, 그래서 SomeClass의 하위 유형을 얻기 위해 String자바 클래스를위한 다중 상속을 지원하지 않기 때문에.

List예에서는 제네릭이 공변량 또는 공변량이 아님을 기억해야합니다. 즉 , X의 하위 유형 인 경우 의 하위 유형도 수퍼 유형도 아닙니다 . 때문에 구현하지 않습니다 , 당신은 사용할 수 없습니다 에YList<X>List<Y>IntegerCharSequenceList<Integer>doCharSequence 메소드 .

그러나 이것을 컴파일 할 수 있습니다.

<T extends Integer & CharSequence> void foo(List<T> list) {
    doCharSequence(list);
}  

당신은 방법이있는 경우 반환List<T> 같은를 :

static <T extends CharSequence> List<T> foo() 

넌 할 수있어

List<? extends Integer> list = foo();

또한 유추 된 유형이이고이 유형 Integer & CharSequence이의 하위 유형이기 때문 입니다 Integer.

교차 경계 유형은 여러 경계를 지정하면 암시 적으로 발생합니다 (예 🙂 <T extends SomeClass & CharSequence>.

자세한 내용은 여기 타입이 일을 경계 어떻게 설명 JLS의 일부입니다. 여러 인터페이스를 포함 할 수 있습니다 (예 :

<T extends String & CharSequence & List & Comparator>

그러나 첫 번째 경계 만 비 인터페이스 일 수 있습니다.


답변

에 할당하기 전에 컴파일러에서 유추하는 유형 XInteger & CharSequence입니다. 이 유형 은 최종적 이기 때문에 이상하게 느껴지Integer 지만 Java에서는 완벽하게 유효한 유형입니다. 그런 다음로 캐스팅되어 Integer완벽하게 괜찮습니다.

Integer & CharSequence유형에 대해 정확히 하나의 가능한 값이 null있습니다.. 다음과 같은 구현으로 :

<X extends CharSequence> X getCharSequence() {
    return null;
}

다음 과제가 작동합니다.

Integer x = getCharSequence();

이 가능한 값으로 인해 할당이 명백히 쓸모없는 경우에도 할당이 잘못된 이유는 없습니다. 경고가 유용 할 것입니다.

실제 문제는 호출 사이트가 아닌 API입니다.

사실, 나는 최근 에이 API 디자인 안티 패턴에 대해 블로그를 작성했습니다 . 유추 된 유형이 전달 될 것이라고 (거의) 절대 보장 할 수 없기 때문에 임의 유형을 리턴하는 일반 메소드를 설계하지 않아야합니다. 예외는 Collections.emptyList()목록의 공허함과 일반적인 유형 삭제가 추론 <T>이 작동하는 이유 인 경우와 같습니다.

public static final <T> List<T> emptyList() {
    return (List<T>) EMPTY_LIST;
}


답변