Javaでのジェネリック型の設計は失敗したと何度も聞いています。 クレームの大部分は、プリミティブ型(追加予定)のサポートの欠如と型の消去、具体的には、実行時にパラメーターの実際の型を取得できないことを主張しています。 個人的には、Genericの設計が悪いように、型の消去を問題とは考えていません。 しかし、私をかなりイライラさせる瞬間がありますが、あまり頻繁に言及されていません。
1
たとえば、 Class#getAnnotation
パラメーター化され、次のシグネチャを持つことがわかっていますClass#getAnnotation
public <A extends Annotation> A getAnnotation(Class<A> annotationClass)
。 したがって、次のコードを記述できます。
Deprecated d = Object.class.getAnnotation(Deprecated.class);
次に、 Object.class
を別の変数に入れると、コードのコンパイルが停止します。
Class clazz = Object.class;
私はどこが間違っていますか?
clazz
変数の型をパラメーター化していないという点で誤解されていました。 Class
型を消去すると、そのすべてのメソッドで型も消去されることがわかりました! なぜそれが行われたのですか?私にはわかりません。 コードに最小限の修正を加えるだけで、すべてが正常に機能します。
Class<Object> clazz = Object.class; Deprecated d = clazz.getAnnotation(Deprecated.class);
2
2番目の例は、少し難解ですが、指標です。 このような単純なクラスがあると想像してください:
class Ref<T> { private T value = null; public T getValue() { return value; } public void setValue(T value) { this.value = value; } }
変数ref
があるため、このようなコードを書くことにしました。 彼にどんな悪いことが起こりますか?
ref.setValue(ref.getValue());
常にコンパイルすると想定するのが賢明ですが、そうではありません! Ref<?>
型のref
変数を宣言するだけで、 incompatible types: java.lang.Object cannot be converted to capture#1 of ?
エラーが発生しincompatible types: java.lang.Object cannot be converted to capture#1 of ?
つまり、ゲッターのワイルドカードはObject
自動的に展開されます。 私たちの表現で彼が彼自身と等しくなるという事実に頼ることはできません。 これで足元を撃つのは難しいですが、もちろん可能です。
3
次に、このような非常に単純なクラス階層があるとします。
class HasList<T extends List> { protected final T list; public HasList(T list) { this.list = list; } public T getList() { return list; } } class HasArrayList<T extends ArrayList> extends HasList<T> { public HasArrayList(T list) { super(list); } }
次のコードを記述します。
HasArrayList<?> h = new HasArrayList<>(new ArrayList<>()); ArrayList list = h.getList();
HasArrayList
クラスのT
パラメーターには、 ArrayList
と等しい上限があります。これは、型を消去するときに、コードをコンパイルする必要があることを意味します。
HasArrayList h = new HasArrayList<>(new ArrayList<>());
まあ、それは再び機能しません。 さて、何が悪いのでしょうか?
getList
メソッドのシグネチャではgetList
戻り値の型はList
であり、コンパイラは明示的な型変換を設定するのが面倒です。 すべてが非常に簡単に修正されます-サブクラスでこのメソッドをオーバーライドする必要があります。
class HasArrayList<T extends ArrayList> extends HasList<T> { public HasArrayList(T list) { super(list); } @Override public T getList() { return super.getList(); } }
この場合、コンパイラーはArrayList
を返す合成ブリッジメソッドを生成し、呼び出されます。 明らかに...
一般に、クラスにパラメータータイプがある場合、コードでそれを無視しない方が良い<?>
。極端な場合、 <?>
指定できます。 そうでなければ、コンパイラはそのようなナンセンスを行い、それから私たちは緊張します。
4
後者の状況は最もcです。 私は同様のクラスを書くことにしました:
class MyArrayList<T extends Cloneable & BooleanSupplier> extends ArrayList<T> { public void removeTrueElements() { this.removeIf(BooleanSupplier::getAsBoolean); } }
このメソッドを呼び出すときに問題があると思いますか?
new MyArrayList<>().removeTrueElements();
ばかげているように見えるかもしれませんが、このコードは起動時に例外をスローします:
Exception in thread "main" java.lang.BootstrapMethodError: call site initialization exception ... Caused by: java.lang.invoke.LambdaConversionException: Invalid receiver type interface java.lang.Cloneable; not a subtype of implementation type interface java.util.function.BooleanSupplier ...
いいえ、私はあなたを欺いた。 このコードは、JDK9からコンパイルすれば機能しますが、JDK8コンパイラーはエラーを出します。
はい、すべてがまさにそうです。これはランタイムエラーではなく、コンパイラです。 このラムダの生成が落ちることをコンパイル時に警告することが不可能な理由は、私にはわかりません。
Java 9に切り替えずにコードを修正する方法は? このように:
public void removeTrueElements() { this.removeIf(t -> t.getAsBoolean()); }
もちろん、コメントして、同僚がすべてをメソッド参照に戻さないようにしてください。
結論の代わりに
ジェネリック医薬品の動作が不可解な状況にも多くの人が遭遇していると確信しています。 コメントで共有し、議論する。