Java Generics拡張Comparableインターフェースで未チェックのキャストを避ける方法は?

How to avoid unchecked casts in a Java Generics extended Comparable interface?


質問 written by khelwood @2017-09-18 08:28:30Z

: 6 : 2 : 152

この汎用インターフェイスで安全でないキャスト(T)必要なのはなぜですか? Tがそれ自体に匹敵する場合、つまりExtendedComparable<super of T>を意味するExtendedComparable<T>実装する場合、型消去ではExtendedComparable<T>ExtendedComparable<T>にキャストする必要があるのはなぜですか?

/* @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;
    }
}
コメント 1

この投稿はあなたの助けになることができます: stackoverflow.com/a/25783345/4867374

written by Sourav Purakayastha @2017-09-18 08:33:25Z

回答 1 written by Eugene @2017-09-19 11:35:38Z
5

thisが実際にクラスTインスタンスであるか、それを拡張する保証さえないためです。

たとえば、これを考慮してください:

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

T0では、境界に準拠しているため、 T0 extends ExtendComparable<T0>に準拠しT0 extends ExtendComparable<T0>T0 extends ExtendComparable<T0> 、T0はT0のスーパーです。 この場合、 thisはここのT0インスタンスなので、問題ありません。 キャスト(T)this (したがって(T0)this )は理にかなっています。

T1では、 T1なしでT0境界が適用されるため、宣言も正しいですTT0置き換えられます。 ただし、 thisT1あり、 T1はスーパーでもT0子でもありません。 はい、どちらもExtendedCompatible<T0>実装していますが、兄弟間でキャストすることはできません。 たとえば、IntegerとDoubleはNumberを拡張しますが、 (Integer) new Double(0.0)失敗します。 (T0)変換されるキャスト(T) (T0)失敗します。

あなたがしている仮定は、 Tが宣言されているクラスと同じに設定され、現在これらのセマンティクスを強制する方法がないということです。 Java言語の将来のリリースのある時点でこれが変わることを願っていますが、おそらくJava言語の「タスクフォース」がそうすることを避けている実際の理由があります。

キャストを完全に回避する方法がありますが、 ExtendedCompatibleインターフェイスではなく抽象クラスにするとより効果的です。

クラスを拡張することにより、保護されたコンストラクターによって値が設定されるタイプT最終フィールドを宣言できます。

You can declare a final field of type T which value would be set by a protected constructor by extending class which in turn must pass this as its value:

あなたが支払う代償は、それ自体への愚かな参照を持つことの余分なメモリ消費と、 thisを親コンストラクタに渡すthisによる追加のコード/ CPUの負担です。

もう1つは、 T (this)を取得する抽象メソッドを宣言することです。

The price you pay is the extra memory consumption of having a silly reference to itself and the added code/CPU burden of passing this to the parent constructor.

コメント 1

それは非常に詳細で良い答えのIMOです。スタックオーバーフローは、トップレートのユーザーのみに授与されるという評判がありますが、これは本当に良いものです。+1

written by ユージン @2017-09-19 20:33:02Z

コメント 2

ありがとう。そうです。このインターフェイスを多くの列挙に使用することを意図しました。しかし、残念ながら、提案された回避策は列挙型には適していません。なぜなら、それらは継承をサポートしていないため、抽象クラスから派生できないためです。インターフェースの各メソッドはgetThiz()!= this(Java 9では、少なくともこれらすべての同一のチェックをプライベートインターフェースメソッドにリファクタリングできる)をチェックする必要があるため、2番目の回避策も実行不可能です。 getThiz()を実装する必要があります。

written by ms34449 @2017-09-20 07:02:02Z

コメント 3

@ ms34449列挙型はどうですか?宣言にボディを含めることで、定数ごとにメソッドをオーバーライドできると思います。ああ編集!つまり、定数全体ではなく列挙型全体を意味します。

written by バレンティンルアーノ @2017-09-20 07:28:13Z

回答 2 written by ms34449 @2017-09-19 11:05:27Z
0

ありがとう。 バレンティンは正しい。 両方のタイプが同じインターフェースを実装している場合でも、これらのタイプ間のキャストは機能せず、機能すべきではありません。 そして、はい、Javaには、宣言されているクラスと同じクラスをTに渡すことを強制するメカニズムはありません。

コメント 1

これは、バレンティンの回答に対するコメントである必要があります-そして、おそらく彼を受け入れる必要があります

written by ユージン @2017-09-19 11:50:38Z