これは、サードパーティライブラリAPIの実際の例ですが、簡略化されています。
Oracle JDK 8u72でコンパイル
次の2つの方法を検討してください。
<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
ではないため、Javaはクラスの多重継承をサポートしていないため、SomeClass
を拡張してString
のサブタイプを取得することはできません。
List
の例では、ジェネリックは共変でも反変でもないことを覚えておく必要があります。つまり、X
がY
のサブタイプである場合、List<X>
はList<Y>
のサブタイプでもスーパータイプでもありません。 Integer
はCharSequence
を実装しないため、doCharSequence
メソッドでList<Integer>
を使用することはできません。
ただし、これをコンパイルすることはできます
<T extends Integer & CharSequence> void foo(List<T> list) {
doCharSequence(list);
}
returnsというメソッドがある場合、次のようなList<T>
を使用します。
static <T extends CharSequence> List<T> foo()
できるよ
List<? extends Integer> list = foo();
繰り返しますが、これは推論された型がInteger & CharSequence
であり、これがInteger
のサブタイプであるためです。
交差タイプは、複数の境界を指定すると暗黙的に発生します(例:<T extends SomeClass & CharSequence>
)。
詳細については、 here はJLSの一部であり、型の境界がどのように機能するかを説明しています。複数のインターフェースを含めることができます。
<T extends String & CharSequence & List & Comparator>
ただし、最初の境界のみが非インターフェイスになります。
X
の割り当ての前にコンパイラーによって推論される型はInteger & CharSequence
です。 Integer
はfinalなので、このタイプfeelsは奇妙ですが、Javaでは完全に有効なタイプです。その後、Integer
にキャストされますが、これで問題ありません。
Integer & CharSequence
タイプには、null
という値が1つだけあります。次の実装で:
<X extends CharSequence> X getCharSequence() {
return null;
}
次の割り当てが機能します。
Integer x = getCharSequence();
この可能な値のため、明らかに役に立たない場合でも、割り当てが間違っている理由はありません。警告が役立つでしょう。
実際、私は最近このことについてブログに書きました API design anti pattern 。推論された型が配信されることを(ほとんど)保証できないため、(ほとんど)任意の型を返すジェネリックメソッドを設計しないでください。例外はCollections.emptyList()
のようなメソッドです。この場合、リストの空(およびジェネリック型の消去)が<T>
の推論が機能する理由です。
public static final <T> List<T> emptyList() {
return (List<T>) EMPTY_LIST;
}