2017-09-18 9 views
6

왜이 일반 인터페이스에 안전하지 않은 캐스팅이 필요합니까? (T)? T이 그 자체와 비교 될 경우, 즉 을 의미하는 ExtendedComparable<T>을 구현하는 경우 유형 삭제에서 ExtendedComparable<T>을 T로 전송해야하는 이유는 무엇입니까?Java Generics 확장 Comparable 인터페이스에서 체크되지 않은 캐스트를 피하는 방법?

/* @param <T> T must be comparable to itself or any of its superclass 
* (comparables are consumers, thus acc. to the PECS principle 
* = producer-extends,consumer-super we use the bounded wildcard type "super") 
*/ 
public interface ExtendedComparable<T extends ExtendedComparable<? super T>> { 
    Comparator<? super T> getComparator(); 
    default boolean greaterThen(T toCompare) { 
     return getComparator().compare((T) this, toCompare) > 0; 
    } 
} 
+0

이 게시물은 도움이 될 수 있습니다. https://stackoverflow.com/a/25783345/4867374 –

답변

5

this 실제로 클래스 T의 인스턴스 또는를 확장한다는 보장이 없기 때문에.

예를 들어이 사항을 고려하십시오 T0 extends ExtendComparable<T0> 및 T0는 T0의 슈퍼입니다 : 그것은 바운드 준수로 T0에서

public class T0 implements ExtendComparable<T0> {...} 
public class T1 implements ExtendComparable<T0> {...} 

잘 준수합니다. 이 경우 thisT0의 인스턴스이므로 여기에서 문제가 없습니다. 캐스트 (T)this (따라서 (T0)this)은 의미가 있습니다. 바인딩은 T0 않음 T1인가되므로

T1와 선언도 정확 TT0을 대입한다. 그러나 thisT1이고 T1T0의 슈퍼 또는 하위가 아닙니다. 예, 둘 다 ExtendedCompatible<T0>을 구현하지만 형제 사이에서는 전송할 수 없습니다. 예를 들어 Integer 및 Double extend Number는 있지만 (Integer) new Double(0.0)은 실패합니다. (T)(T0)로 변환하면 캐스팅도 실패합니다.

당신이 만들고있는 가정은 선언 된 클래스와 동일하게 설정 될 것이고 현재는 그러한 의미를 강제 할 방법이 없다는 것입니다. 이것이 Java 언어의 향후 릴리스에서 어느 시점에서 바뀌기를 희망하지만 Java 언어 "태스크 포스"가 그렇게하는 것을 피하는 실제 이유가있을 것입니다.

캐스팅을 모두 피할 수있는 방법이 있지만 ExtendedCompatible을 인터페이스가 아닌 추상 클래스로 만들면 더 좋습니다.

당신은 차례로 그 값으로 this을 통과해야합니다 값이 확장 클래스에 의해 보호되는 생성자에 의해 설정됩니다 유형 T의 최종 필드를 선언 할 수

:

public abstract class ExtendedCompatible<T extends ExtendedCompatible<? super T>> { 
    private final T thiz; 

    protected ExtendedCompatible(final T thiz) { 
    if (this != thiz) throw new IllegalArgumentException("you must pass yourself"); 
    this.thiz = thiz; 
    } 
    ... 

    public class MyExtendedCompatible extends ExtendedCompatible<MyExtendedCompatible> { 
    public MyExtendedCompatible() { 
      super(this); 
    } 
    } 

당신이 지불하는 가격은 여분의 메모리입니다 자체에 대한 어리석은 참조 및 부모 생성자에 this을 전달하는 코드/CPU 부담이 추가되었습니다.

또 다른는 T (이) 얻을 수있는 추상 메소드를 선언하는 것입니다 :

// Parent abstract class: 
    protected abstract T getThiz(); 
// Child class... for each class: 
    protected MyChildClass getThiz() { return this; } 
+1

매우 정확하고 좋은 답변입니다. 스택 오버플로는 가끔 최상위 등급 사용자에게만 수여되는 평판을 얻지 만 이는 실제로 좋은 것입니다. +1 – Eugene

+0

감사합니다. 그거 맞아.인터페이스가 많은 enum에 사용되도록했습니다. 그러나 유감스럽게도, 제안 된 작업은 상속을 지원하지 않으므로 추상 클래스에서 파생 될 수 없으므로 enum에 적합하지 않습니다. 두 번째 해결 방법은 인터페이스의 각 메소드가 getThiz()! = this (Java 9에서는 최소한 이러한 모든 동일한 검사가 개인 인터페이스 메소드로 리팩토링 될 수 있음)를 확인해야하므로 두 번째 해결 방법은 현실적으로 불가능합니다. getThiz()를 구현해야합니다. – ms34449

+0

@ ms34449 열거 형은 무엇입니까? 나는 선언에 몸을 포함 시키면 상수 당 메소드를 오버라이드 할 수 있다고 생각한다. 아 편집! 당신은 상수를 가로 지르지 않고 열거 형을 의미합니다. –

0

감사합니다. 발렌틴 말이 맞아. 두 유형 모두 동일한 인터페이스를 구현하는 경우에도 작동하도록 이들간에 캐스트를 만들지 않아야합니다. 그리고 네, 선언 된 클래스와 동일한 클래스에서 T를 전달하는 Java 메커니즘이 없습니다.

+2

이것은 Valentin 대답에 대한 응답으로 코멘트 여야합니다. – Eugene