一般的な戻り値の型の上限-インターフェイスとクラス-驚くほど有効なコード


171

これは、サードパーティのライブラリ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();はないのですか?


15
面白い!LHSでのキャストInteger x = getCharSequence();はコンパイルされますが、RHSでのキャストはコンパイルにInteger x = (Integer) getCharSequence();失敗します
フレーク

どのバージョンのJavaコンパイラを使用していますか?質問でこの情報を指定してください。
フェデリコペラルタシャフナー

@FedericoPeraltaSchaffnerは、それが重要である理由を理解できません。これは、JLSに関する直接の質問です。
Boris the Spider

@BoristheSpider java8の型推論メカニズムが変更されたため
Federico Peralta Schaffner

1
@FedericoPeraltaSchaffner-質問に[java-8]のタグを付けましたが、今はコンパイラバージョンを投稿に追加しました。
Adam Michalik 16

回答:


184

CharSequenceですinterface。したがって、SomeClassが実装されCharSequenceていなくても、クラスを作成することは完全に可能です。

class SubClass extends SomeClass implements CharSequence

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

SomeClass c = getCharSequence();

推論された型Xが交差型だからSomeClass & CharSequenceです。

これは、の場合には少し奇妙であるIntegerため、Integer最終的なものではなく、finalこれらのルールのいずれかの役割を果たしていません。たとえば、あなたは書くことができます

<T extends Integer & CharSequence>

一方、Stringはでないため、javaはクラスの多重継承をサポートしていないため、interfaceを拡張SomeClassしてのサブタイプを取得することは不可能Stringです。

このList例では、ジェネリックは共変でも反変でもないことを覚えておく必要があります。これは、Xがのサブタイプである場合YList<X>がのサブタイプでもスーパータイプでもないことを意味しList<Y>ます。以来Integer実装されていないCharSequence、あなたが使用することはできませんList<Integer>あなたにdoCharSequence方法。

ただし、これをコンパイルすることはできます

<T extends Integer & CharSequence> void foo(List<T> list) {
    doCharSequence(list);
}  

あなたは方法がある場合は返しますList<T>。このように:

static <T extends CharSequence> List<T> foo() 

できるよ

List<? extends Integer> list = foo();

繰り返しますが、これは推論された型がでInteger & CharSequenceあり、これがのサブタイプであるためIntegerです。

複数の境界を指定すると、交差タイプが暗黙的に発生します(例:)<T extends SomeClass & CharSequence>

詳細については、ここに JLSの一部があり、型の境界がどのように機能するかを説明しています。たとえば、複数のインターフェースを含めることができます

<T extends String & CharSequence & List & Comparator>

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


62
あなたが&一般的な定義に入れることができるとは思いもしませんでした。+1
フレーク

13
@flkes複数指定できますが、最初の引数のみを非インターフェイスにすることができます。<T extends String & List & Comparator>はインターフェースではない<T extends String & Integer>ため、問題Integerありませんが問題があります。
Paul Boddington、2016

7
@PaulBoddingtonこれらのメソッドにはいくつかの実用的な用途があります。たとえば、タイプが実際に格納されたデータに使用されていない場合。この例はCollections.emptyList()同様Optional.empty()です。これらは、ジェネリックインターフェイスの実装を返しますが、何も格納しません。
Stefan Dollase 2016

6
そしてfinal、コンパイル時に存在するクラスがfinal実行時になると誰も言っていません。
Holger

7
@Federico Peralta Schaffner:ここでのポイントは、メソッドは呼び出し元が必要とするgetCharSequence()ものを返すことを約束しXます。これには、呼び出し元が必要IntegerとするCharSequence場合に拡張および実装する型を返すことも含まれます。この約束のもとでは、結果をに割り当てることができIntegerます。それgetCharSequence()は約束を守らないので壊れているメソッドですが、それはコンパイラのせいではありません。
Holger

59

の割り当ての前にコンパイラによって推論される型XInteger & CharSequenceです。このタイプは最終的なものであるため、奇妙に感じIntegerられますが、Javaでは完全に有効なタイプです。次に、にキャストされますがInteger、これはまったく問題ありません。

Integer & CharSequenceタイプに使用できる値は1つだけですnull。次の実装では:

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

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

Integer x = getCharSequence();

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

実際の問題は、呼び出しサイトではなくAPIです

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

public static final <T> List<T> emptyList() {
    return (List<T>) EMPTY_LIST;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.