Java-Generika - warum ist "erweitert T" erlaubt, aber nicht "implementiert T"?


306

Ich frage mich, ob es in Java einen besonderen Grund gibt, immer " extends" statt " implements" für die Definition von Grenzen von Typparametern zu verwenden.

Beispiel:

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

ist aber verboten

public class A<B extends C>{} 

ist richtig. Was ist der Grund dafür?


14
Ich weiß nicht, warum die Leute denken, dass die Antwort von Tetsujin no Oni die Frage wirklich beantwortet. Grundsätzlich werden die Beobachtungen von OP mit akademischen Formulierungen umformuliert, es werden jedoch keine Gründe angegeben. "Warum gibt es keine implements?" - "Weil es nur gibt extends".
ThomasR

1
ThomasR: Das liegt daran, dass es nicht um "erlaubt" geht, sondern um die Bedeutung: Es gibt keinen Unterschied, wie Sie ein Generikum schreiben würden, das einen Typ mit einer Einschränkung verbraucht, unabhängig davon, ob die Einschränkung von einer Schnittstelle oder einem Ahnen-Typ stammt.
Tetsujin no Oni

Ich habe eine Antwort ( stackoverflow.com/a/56304595/4922375 ) mit meiner Überlegung hinzugefügt , warum ich implementsnichts Neues bringen und die Dinge weiter komplizieren würde. Ich hoffe es wird hilfreich für Sie sein.
Andrew Tobilko

Antworten:


328

Es gibt keinen semantischen Unterschied in der generischen Einschränkungssprache zwischen der Implementierung oder Erweiterung einer Klasse. Die Einschränkungsmöglichkeiten sind 'erweitert' und 'super' - das heißt, diese Klasse kann mit der anderen zuweisbar (erweitert) arbeiten, oder ist diese Klasse von dieser zuweisbar (super).


@KomodoDave (Ich denke, die Zahl neben der Antwort markiert sie als richtig. Ich bin mir nicht sicher, ob es eine andere Möglichkeit gibt, sie zu markieren. Manchmal enthalten andere Antworten zusätzliche Informationen - z. B. hatte ich ein bestimmtes Problem, das ich nicht lösen konnte, und Google schickt Sie hierher, wenn Sie danach suchen.) @Tetsujin no Oni (Wäre es möglich, einen Code zur Klärung zu verwenden? Danke :))
ntg

@ntg, dies ist ein sehr gutes Beispiel für eine Frage, die nach Beispielen sucht. Ich werde es in einem Kommentar verlinken, anstatt es an dieser Stelle in die Antwort einzubetten. stackoverflow.com/a/6828257/93922
Tetsujin no Oni

1
Ich denke, der Grund ist zumindest, dass ich ein Generikum haben möchte, das einen Konstruktor und Methoden haben kann, die jede Klasse akzeptieren können, die sowohl eine Basisklasse erweitert als auch eine Schnittstelle aufweist, nicht nur Schnittstellen, die eine Schnittstelle erweitern. Lassen Sie dann den Genric-Test für das Vorhandensein von Schnittstellen instanziieren UND die tatsächliche Klasse als Typparameter angeben. Idealerweise möchte ich, dass class Generic<RenderableT extends Renderable implements Draggable, Droppable, ...> { Generic(RenderableT toDrag) { x = (Draggable)toDrag; } }man Zeitprüfungen kompiliert.
Peterk

1
@peterk Und Sie bekommen das mit RenderableT erweitert Renderable, Draggable, Droppable ... es sei denn, ich verstehe nicht, was die Löschgenerika für Sie tun sollen, was dies nicht bietet.
Tetsujin no Oni

@TetsujinnoOni Sie wollen nicht in der Kompilierungszeit nur Klassen akzeptieren, die einen bestimmten Satz von Schnittstellen implementieren, und dann in der Lage sein, auf die OBJECT-Klasse (die diese Schnittstellen aufweist) im Generikum zu verweisen und das dann zu wissen Zumindest zur Kompilierungszeit (und wünschenswerterweise zur Laufzeit) kann alles, was dem Generikum zugewiesen ist, sicher auf eine der angegebenen Schnittstellen übertragen werden. Dies ist nicht der Fall, wie Java jetzt implementiert ist. Aber es wäre schön :)
Peterk

77

Die Antwort ist hier  :

Um einen begrenzten Typparameter zu deklarieren, listen Sie den Namen des Typparameters auf, gefolgt vom extendsSchlüsselwort und der oberen Grenze […]. Beachten Sie, dass in diesem Zusammenhang "Erweitern" im Allgemeinen entweder extends(wie in Klassen) oder implements(wie in Schnittstellen) verwendet wird.

Da haben Sie es also, es ist ein bisschen verwirrend und Oracle weiß es.


1
Um die Verwirrung zu getFoo(List<? super Foo> fooList) vergrößern, funktioniert NUR mit der Klasse, die buchstäblich von Foo wie erweitert wird class Foo extends WildcardClass. In diesem Fall List<WildcardClass>wäre eine Eingabe akzeptabel. Eine Klasse, Foodie nicht funktioniert, class Foo implements NonWorkingWildcardClassbedeutet jedoch nicht, List<NonWorkingWildcardClass>dass sie in der gültig ist getFoo(List<? super Foo> fooList). Kristallklar!
Ray

19

Wahrscheinlich, weil für beide Seiten (B und C) nur der Typ relevant ist, nicht die Implementierung. In deinem Beispiel

public class A<B extends C>{}

B kann auch eine Schnittstelle sein. "erweitert" wird verwendet, um Unterschnittstellen sowie Unterklassen zu definieren.

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

Normalerweise denke ich an 'Sub erweitert Super' als ' Sub ist wie Super , aber mit zusätzlichen Fähigkeiten' und 'Clz implementiert Intf' als ' Clz ist eine Realisierung von Intf '. In Ihrem Beispiel würde dies übereinstimmen: B ist wie C , jedoch mit zusätzlichen Funktionen. Die Fähigkeiten sind hier relevant, nicht die Realisierung.


10
Betrachten Sie <B erweitert D & E>. E <caps> darf keine </ caps> Klasse sein.
Tom Hawtin - Tackline

7

Es kann sein, dass der Basistyp ein generischer Parameter ist, sodass der tatsächliche Typ eine Schnittstelle einer Klasse sein kann. Erwägen:

class MyGen<T, U extends T> {

Auch aus Sicht des Client-Codes sind Schnittstellen kaum von Klassen zu unterscheiden, während dies für den Subtyp wichtig ist.


7

Hier ist ein ausführlicheres Beispiel dafür, wo Erweiterungen zulässig sind und möglicherweise was Sie möchten:

public class A<T1 extends Comparable<T1>>


5

Es ist irgendwie willkürlich, welche der Begriffe verwendet werden sollen. Es hätte so oder so sein können. Vielleicht dachten die Sprachdesigner, dass "erweitert" der grundlegendste Begriff ist und "implementiert" als Sonderfall für Schnittstellen.

Aber ich denke, das implementswäre etwas sinnvoller. Ich denke, das kommuniziert mehr, dass die Parametertypen nicht in einer Vererbungsbeziehung sein müssen, sie können in jeder Art von Subtypbeziehung sein.

Das Java-Glossar vertritt eine ähnliche Ansicht .


3

Wir sind gewohnt an

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

und jede geringfügige Abweichung von diesen Regeln verwirrt uns sehr.

Die Syntax eines gebundenen Typs ist definiert als

TypeBound:
    extends TypeVariable 
    extends ClassOrInterfaceType {AdditionalBound}

( JLS 12> 4.4. Typvariablen>TypeBound )

Wenn wir es ändern würden, würden wir den implementsFall sicherlich hinzufügen

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

und am Ende zwei identisch verarbeitete Klauseln

ClassOrInterfaceType:
    ClassType 
    InterfaceType

( JLS 12> 4.3. Referenztypen und Werte>ClassOrInterfaceType )

außer wir müssten uns auch darum kümmern implements, was die Sache noch komplizierter machen würde.

Ich glaube, es ist der Hauptgrund, warum extends ClassOrInterfaceTypeanstelle von extends ClassTypeund implements InterfaceType- verwendet wird, um die Dinge innerhalb des komplizierten Konzepts einfach zu halten. Das Problem ist , dass wir nicht das richtige Wort sowohl zur Deckung haben extendsund implementswir definitiv nicht wollen , eine einzuführen.

<T is ClassTypeA>
<T is InterfaceTypeA>

Obwohl extendses mit einer Schnittstelle etwas Chaos bringt, ist es ein weiter gefasster Begriff und kann verwendet werden, um beide Fälle zu beschreiben. Versuchen Sie, sich auf das Konzept der Erweiterung eines Typs einzustellen (keine Klasse erweitern , keine Schnittstelle implementieren ). Sie beschränken einen Typparameter auf einen anderen Typ, und es spielt keine Rolle, um welchen Typ es sich tatsächlich handelt. Es ist nur wichtig, dass es seine Obergrenze und sein Supertyp ist .


-1

Bei Verwendung von generic on interface wird das Schlüsselwort sogar erweitert . Hier ist das Codebeispiel:

Es gibt zwei Klassen, die die Begrüßungsschnittstelle implementieren:

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 ");
    }
}

Und der Testcode:

@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();
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.