web-dev-qa-db-ja.com

ジェネリックリターンタイプの上限-インターフェイスとクラス-驚くほど有効なコード

これは、サードパーティライブラリAPIの実際の例ですが、簡略化されています。

Oracle JDK 8u72でコンパイル

次の2つの方法を検討してください。

<X extends CharSequence> X getCharSequence() {
    return (X) "hello";
}

<X extends String> X getString() {
    return (X) "hello";
}

どちらも「未チェックのキャスト」警告を報告します-理由はわかります。私を困惑させるのは、なぜ私が電話できるのか

Integer x = getCharSequence();

コンパイルしますか?コンパイラは、IntegerCharSequenceを実装していないことを知っている必要があります。への呼び出し

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();がそうではないのですか?

170
Adam Michalik

CharSequenceinterfaceです。したがって、SomeClassCharSequenceを実装していない場合でも、クラスを作成することは完全に可能です。

class SubClass extends SomeClass implements CharSequence

したがって、あなたは書くことができます

SomeClass c = getCharSequence();

推定型Xは交差型SomeClass & CharSequenceであるためです。

Integerが最終的なので、これはIntegerの場合は少し奇妙ですが、finalはこれらのルールでは何の役割も果たしません。たとえば、次のように書くことができます

<T extends Integer & CharSequence>

一方、Stringinterfaceではないため、Javaはクラスの多重継承をサポートしていないため、SomeClassを拡張してStringのサブタイプを取得することはできません。

Listの例では、ジェネリックは共変でも反変でもないことを覚えておく必要があります。つまり、XYのサブタイプである場合、List<X>List<Y>のサブタイプでもスーパータイプでもありません。 IntegerCharSequenceを実装しないため、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>

ただし、最初の境界のみが非インターフェイスになります。

183
Paul Boddington

Xの割り当ての前にコンパイラーによって推論される型はInteger & CharSequenceです。 Integerはfinalなので、このタイプfeelsは奇妙ですが、Javaでは完全に有効なタイプです。その後、Integerにキャストされますが、これで問題ありません。

Integer & CharSequenceタイプには、nullという値が1つだけあります。次の実装で:

<X extends CharSequence> X getCharSequence() {
    return null;
}

次の割り当てが機能します。

Integer x = getCharSequence();

この可能な値のため、明らかに役に立たない場合でも、割り当てが間違っている理由はありません。警告が役立つでしょう。

本当の問題は呼び出しサイトではなくAPIです

実際、私は最近このことについてブログに書きました API design anti pattern 。推論された型が配信されることを(ほとんど)保証できないため、(ほとんど)任意の型を返すジェネリックメソッドを設計しないでください。例外はCollections.emptyList()のようなメソッドです。この場合、リストの空(およびジェネリック型の消去)が<T>の推論が機能する理由です。

public static final <T> List<T> emptyList() {
    return (List<T>) EMPTY_LIST;
}
59
Lukas Eder