Javaジェネリックス-「Tを拡張する」が許可されているが、「Tを実装する」ことができないのはなぜですか?


306

型パラメーターの境界を定義するために常に " extends"ではなく" " を使用するJavaには特別な理由があるimplementsのでしょうか。

例:

public interface C {}
public class A<B implements C>{} 

禁止されていますが

public class A<B extends C>{} 

正しい。その理由は何ですか?


14
鉄人の鬼の答えが本当に答えだと思う理由がわかりません。それは基本的に学術的表現を使用してOPの観察を言い換えますが、いかなる理由も与えません。「なんでないのimplements?」-「あるだけだからextends」。
ThomasR 2016

1
ThomasR:それは「許可」の問題ではなく、意味の問題です。つまり、制約がインターフェースからであっても、祖先型からであっても、制約付きの型を消費するジェネリックの記述方法に違いはありません。
鉄人の鬼

回答が追加されました(stackoverflow.com/a/56304595/4922375)。理由implementsは何も新しいものではなく、事態をさらに複雑にする理由を説明します。お役に立てれば幸いです。
Andrew Tobilko、

回答:


328

クラスが「実装」するか「拡張」するかにかかわらず、一般的な制約言語に意味上の違いはありません。制約の可能性は「拡張」と「スーパー」です。つまり、このクラスは、他のクラスに割り当て可能で動作する(拡張)か、このクラスから割り当て可能(スーパー)です。


@KomodoDave(私は答えの横の数字が正しいとマークしていると思います、それをマークする他の方法があるかどうかわかりません、他の答えには追加の情報が含まれている場合があります-たとえば、解決できない特定の問題があり、グーグルはそれを検索するときここにあなたを送ります。)@鉄人の鬼(明確にするためにいくつかのコードを使用することは可能ですか?thanx :))
ntg

@ntg、これは例を探す質問の非常に良い例です-この時点で回答に埋​​め込むのではなく、コメントでリンクします。stackoverflow.com/a/6828257/93922
鉄人の鬼

1
その理由は、少なくとも、コンストラクタを持つことができるジェネリックと、基本クラスに属し、インターフェイスを拡張するインターフェイスだけでなく、インターフェイスを示す任意のクラスを受け入れることができるメソッドが欲しいと思うからです。次に、インターフェースの存在に関するGenricテストのインスタンス化を行い、実際のクラスを型パラメーターとして指定します。理想的には、 class Generic<RenderableT extends Renderable implements Draggable, Droppable, ...> { Generic(RenderableT toDrag) { x = (Draggable)toDrag; } }コンパイル時間のチェックが必要です。
peterk 2016

1
@peterkそして、RenderableTを使用すると、Renderable、Draggable、Droppableを拡張できます。これは、私が消去ジェネリックスに何をしてほしいか理解していない場合を除きます。
鉄人の鬼2016

@TetsujinnoOniあなたが私が望んでいないのは、特定のインターフェイスのセットを実装するクラスのみを受け入れるコンパイル時の強制であり、その後、ジェネリックでOBJECTクラス(これらのインターフェイスを示す)を参照し、それを知ることができます少なくともコンパイル時(望ましくは実行時)に、ジェネリックに割り当てられたものはすべて、指定されたインターフェースのいずれかに安全にキャストできます。これは、Javaが現在実装されている方法には当てはまりません。しかし、それはいいでしょう:)
peterk 2016年

77

答えはここにあります  :

有界型パラメーターを宣言するには、型パラメーターの名前、extendsキーワード、上限 […]の順にリストします。この文脈では、拡張は一般的な意味でextends(クラスのように)またはimplements(インターフェースのように)を意味するために使用されることに注意してください。

だから、あなたはそれを持っています、それは少し混乱しています、そしてOracleはそれを知っています。


1
混乱を増すにはgetFoo(List<? super Foo> fooList) 、Fooのように文字通り拡張されたクラスでのみ機能しclass Foo extends WildcardClassます。この場合、List<WildcardClass>は許容できる入力です。ただし、Foo実装するクラスが機能class Foo implements NonWorkingWildcardClassしないList<NonWorkingWildcardClass>場合でも、で有効になるわけではありませんgetFoo(List<? super Foo> fooList)。クリスタルクリア!
レイ

19

おそらく両方(BとC)に関連するのは、実装ではなくタイプのみです。あなたの例では

public class A<B extends C>{}

Bもインターフェイスにすることができます。"extends"は、サブクラスと同様にサブインターフェースを定義するために使用されます。

interface IntfSub extends IntfSuper {}
class ClzSub extends ClzSuper {}

私は普通「と「サブスーパーを拡張」を考えるサブが似ているスーパーが、追加機能で」、および「CLZ用具Intfにより」「としてCLZがの実現であるIntfにより」。あなたの例では、これは一致します:BCに似ていますが、追加の機能があります。機能は、実現ではなく、ここに関連しています。


10
<B extends D&E>を考えます。Eはクラスであってはいけません。
トム・ホーティン-09

7

基本タイプが総称パラメーターである可能性があるため、実際のタイプはクラスのインターフェースである可能性があります。考慮してください:

class MyGen<T, U extends T> {

また、クライアントコードの観点から見ると、インターフェイスはクラスとほとんど区別がつきませんが、サブタイプの場合は重要です。


7

以下は、拡張が許可される場所と、おそらく必要なもののより複雑な例です。

public class A<T1 extends Comparable<T1>>


5

どの用語を使用するかは、恣意的です。それはどちらの方法だったかもしれません。おそらく言語設計者は、「拡張」を最も基本的な用語として、「実装」をインターフェースの特殊なケースとして考えていました。

しかし、私はimplementsもう少し理にかなっていると思います。これは、パラメーター型が継承関係にある必要はなく、あらゆる種類のサブタイプ関係にあることができることをより伝えています。

Java用語集も同様の見解を示しています


3

に慣れている

class ClassTypeA implements InterfaceTypeA {}
class ClassTypeB extends ClassTypeA {}

そして、これらの規則から少しでも逸脱すると、私たちを非常に混乱させます。

型バインドの構文は次のように定義されます

TypeBound:
    extends TypeVariable 
    extends ClassOrInterfaceType {AdditionalBound}

JLS 12> 4.4。タイプ変数>TypeBound

変更したimplements場合は必ずケースを追加します

TypeBound:
    extends TypeVariable 
    extends ClassType {AdditionalBound}
    implements InterfaceType {AdditionalBound}

同じように処理される2つの句で終わる

ClassOrInterfaceType:
    ClassType 
    InterfaceType

JLS 12> 4.3。参照タイプと値>ClassOrInterfaceType

ただし、を処理する必要があるためimplements、事態はさらに複雑になります。

それextends ClassOrInterfaceTypeextends ClassTypeandの代わりに使用される主な理由だと思いますimplements InterfaceType-複雑な概念の中で物事をシンプルに保つために。問題は、両方をカバーする適切な言葉がないことです。extendsまたimplements、どちらかを紹介したくありません。

<T is ClassTypeA>
<T is InterfaceTypeA>

けれどもextends、それはインターフェースと一緒になったときに、いくつかの混乱をもたらし、それはより広範な用語だと両方のケースを記述するために使用することができます。拡張クラスの拡張ではなく、インターフェースの実装ではない)の概念に心を向けましょう。型パラメーターを別の型で制限し、その型が実際に何であるかは関係ありません。それがその上限であり、それがそのスーパータイプであることだけが重要です。


-1

実際、ジェネリックonインターフェースを使用する場合、キーワードもextendsです。次にコード例を示します。

Greetingインターフェースを実装する2つのクラスがあります。

interface Greeting {
    void sayHello();
}

class Dog implements Greeting {
    @Override
    public void sayHello() {
        System.out.println("Greeting from Dog: Hello ");
    }
}

class Cat implements Greeting {
    @Override
    public void sayHello() {
        System.out.println("Greeting from Cat: Hello ");
    }
}

そしてテストコード:

@Test
public void testGeneric() {
    Collection<? extends Greeting> animals;

    List<Dog> dogs = Arrays.asList(new Dog(), new Dog(), new Dog());
    List<Cat> cats = Arrays.asList(new Cat(), new Cat(), new Cat());

    animals = dogs;
    for(Greeting g: animals) g.sayHello();

    animals = cats;
    for(Greeting g: animals) g.sayHello();
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.