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


171

이것은 타사 라이브러리 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();그렇지 않습니까?


15
흥미로운! LHS에서의 캐스팅 Integer x = getCharSequence();은 컴파일되지만 RHS에서의 캐스팅은 컴파일 Integer x = (Integer) getCharSequence();실패
플레이크

어떤 버전의 Java 컴파일러를 사용하고 있습니까? 질문에이 정보를 지정하십시오.
Federico Peralta Schaffner

@FedericoPeraltaSchaffner는 왜 중요한지 알 수 없습니다. 이것은 JLS에 대한 직접적인 질문입니다.
거미 보리스

@BoristheSpider java8의 타입 추론 메커니즘이 변경 되었기 때문에
Federico Peralta Schaffner

1
@FedericoPeraltaSchaffner-질문에 [java-8]로 이미 태그를 달았지만 지금은 게시물에 컴파일러 버전을 추가했습니다.
Adam Michalik

답변:


184

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>

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


62
나는 당신이 &일반적인 정의에 넣을 수 있다는 것을 몰랐습니다 . +1
플레이크

13
@flkes 둘 이상을 넣을 수 있지만 첫 번째 인수 만 비 인터페이스 일 수 있습니다. 인터페이스가 아니기 때문에 <T extends String & List & Comparator>괜찮습니다 . <T extends String & Integer>Integer
Paul Boddington

7
@PaulBoddington이 방법에는 실용적으로 사용됩니다. 예를 들어 유형이 실제로 저장된 데이터에 사용되지 않는 경우입니다. 이에 대한 예는 다음과 Collections.emptyList()같습니다 Optional.empty(). 이것들은 일반 인터페이스의 구현을 반환하지만 아무것도 저장하지 않습니다.
Stefan Dollase

6
그리고 아무도 final컴파일 타임에 클래스가 final런타임에 있다고 말하지 않습니다 .
Holger

7
@Federico Peralta Schaffner : 여기서 요점은이 메소드 getCharSequence()X호출자가 필요로하는 것을 반환 할 것을 약속한다는 것입니다. 여기 에는 호출자가 필요로하는 경우 확장 Integer및 구현 유형을 반환하는 것이 포함 CharSequence됩니다 Integer. 그것은 getCharSequence()약속을 지키지 않기 때문에 깨지는 방법 이지만 컴파일러의 결함이 아닙니다.
Holger

59

에 할당하기 전에 컴파일러에서 유추하는 유형 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;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.