Generici Java: perché è consentito "estende T" ma non "implementa T"?


306

Mi chiedo se c'è un motivo speciale in Java per usare sempre " extends" piuttosto che " implements" per definire i limiti dei typeparameters.

Esempio:

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

è proibito ma

public class A<B extends C>{} 

è corretto. Qual è la ragione?


14
Non so perché la gente pensi che la risposta di Tetsujin no Oni risponda davvero alla domanda. Fondamentalmente riformula le osservazioni di OP usando una formulazione accademica, ma non fornisce alcun ragionamento. "Perché non c'è implements?" - "Perché c'è solo extends".
ThomasR,

1
ThomasR: questo perché non è una questione di "permesso", ma di significato: non c'è differenza nel modo in cui si scriverebbe un generico che consuma un tipo con un vincolo se il vincolo proviene da un'interfaccia o da un tipo di antenato.
Tetsujin no Oni,

Aggiunta una risposta ( stackoverflow.com/a/56304595/4922375 ) con il mio ragionamento sul perché implementsnon porterebbe nulla di nuovo e complicherebbe ulteriormente le cose. Spero che ti sarà utile.
Andrew Tobilko,

Risposte:


328

Non vi è alcuna differenza semantica nel linguaggio del vincolo generico tra se una classe "implementa" o "estende". Le possibilità di vincolo sono 'extends' e 'super' - cioè, questa classe deve operare con assegnabile a quell'altra (estende), o è questa classe assegnabile da quella (super).


@KomodoDave (penso che il numero accanto alla risposta lo contrassegni come corretto, non sono sicuro che ci sia un altro modo per contrassegnarlo, a volte altre risposte possono contenere informazioni aggiuntive, ad esempio ho avuto un problema particolare che non sono riuscito a risolvere e google ti manda qui durante la ricerca.) @Tetsujin no Oni (Sarebbe possibile usare del codice per chiarire? Grazie) :)
ntg

@ntg, questo è un ottimo esempio su una domanda in cerca di esempi - lo collegherò nei commenti, piuttosto che incorporare la risposta a questo punto. stackoverflow.com/a/6828257/93922
Tetsujin no Oni

1
Penso che il motivo sia almeno che vorrei avere un generico che può avere un costruttore e metodi in grado di accettare qualsiasi classe che sia in aggiunta a una classe base ed esibisca un'interfaccia non solo interfacce che estendono un'interfaccia. Quindi avere l'istanza del test Genric per la presenza di interfacce E avere la classe effettiva specificata come parametro di tipo. Idealmente vorrei che class Generic<RenderableT extends Renderable implements Draggable, Droppable, ...> { Generic(RenderableT toDrag) { x = (Draggable)toDrag; } }One volesse compilare i controlli del tempo.
Peter

1
@peterk E ottieni che con RenderableT si estende Renderable, Draggable, Droppable .... a meno che non capisca cosa vuoi che i generici di cancellazione facciano per te che ciò non fornisce.
Tetsujin no Oni,

@TetsujinnoOni, ciò che non voglio è che per un tempo di compilazione si applichi solo l'accettazione di classi che implementano un certo set di interfacce, e quindi si può fare riferimento alla classe OBJECT (che mostra quelle interfacce) nel generico e quindi sapere che almeno in fase di compilazione (e desiderabilmente in fase di esecuzione) qualsiasi cosa assegnata al generico può essere trasmessa in modo sicuro a una qualsiasi delle interfacce specificate. Questo non è il modo in cui java è implementato ora. Ma sarebbe bello :)
Peter

77

La risposta è qui  :

Per dichiarare un parametro di tipo limitato, elencare il nome del parametro di tipo, seguito dalla extendsparola chiave, seguita dal suo limite superiore […]. Si noti che, in questo contesto, extends è usato in senso generale per indicare extends(come nelle classi) o implements(come nelle interfacce).

Quindi il gioco è fatto, è un po 'confuso e Oracle lo sa.


1
Aggiungere alla confusione getFoo(List<? super Foo> fooList) funziona SOLO con la classe che letteralmente viene estesa da Foo like class Foo extends WildcardClass. In questo caso a List<WildcardClass>sarebbe un input accettabile. Tuttavia qualsiasi classe che Fooimplementa non funzionerebbe class Foo implements NonWorkingWildcardClassnon significa List<NonWorkingWildcardClass>che sarà valida in getFoo(List<? super Foo> fooList). Cristallino!
Ray,

19

Probabilmente perché per entrambi i lati (B e C) è rilevante solo il tipo, non l'implementazione. Nel tuo esempio

public class A<B extends C>{}

B può anche essere un'interfaccia. "extends" viene utilizzato per definire interfacce secondarie e sottoclassi.

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

Di solito penso a 'Sub estende Super' come ' Sub è come Super , ma con capacità aggiuntive', e 'Clz implementa Intf' come ' Clz è una realizzazione di Intf '. Nel tuo esempio, ciò corrisponderebbe: B è come C , ma con funzionalità aggiuntive. Le capacità sono rilevanti qui, non la realizzazione.


10
Considera <B estende D & E>. E <caps> non deve </caps> essere una classe.
Tom Hawtin - tackline il

7

È possibile che il tipo di base sia un parametro generico, quindi il tipo effettivo potrebbe essere un'interfaccia di una classe. Tener conto di:

class MyGen<T, U extends T> {

Anche dal punto di vista del codice client le interfacce sono quasi indistinguibili dalle classi, mentre per il sottotipo è importante.


7

Ecco un esempio più coinvolto di dove è consentito l'estensione e possibilmente di ciò che si desidera:

public class A<T1 extends Comparable<T1>>


5

È una sorta di arbitrario quale dei termini usare. Sarebbe potuto essere in entrambi i modi. Forse i progettisti del linguaggio hanno pensato a "estendere" come il termine più fondamentale, e "implementa" come il caso speciale delle interfacce.

Ma penso che implementsavrebbe leggermente più senso. Penso che comunichi più che i tipi di parametro non devono essere in una relazione di ereditarietà, possono essere in qualsiasi tipo di relazione di sottotipo.

Il Glossario Java esprime una visione simile .


3

Siamo abituati

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

e ogni leggera deviazione da queste regole ci confonde notevolmente.

La sintassi di un tipo associato è definita come

TypeBound:
    extends TypeVariable 
    extends ClassOrInterfaceType {AdditionalBound}

( JLS 12> 4.4. Digitare variabili>TypeBound )

Se dovessimo cambiarlo, aggiungeremmo sicuramente il implementscaso

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

e finiscono con due clausole elaborate in modo identico

ClassOrInterfaceType:
    ClassType 
    InterfaceType

( JLS 12> 4.3. Tipi e valori di riferimento>ClassOrInterfaceType )

eccetto che avremmo anche bisogno di prenderci cura di noi implements, il che complicherebbe ulteriormente le cose.

Credo che sia il motivo principale per cui extends ClassOrInterfaceTypeviene utilizzato al posto di extends ClassTypee implements InterfaceType- per mantenere le cose semplici all'interno del concetto complicato. Il problema è che non abbiamo la parola giusta per coprire entrambi extendse implementssicuramente non vogliamo introdurne uno.

<T is ClassTypeA>
<T is InterfaceTypeA>

Anche se extendsporta un po 'di confusione quando si accompagna a un'interfaccia, è un termine più ampio e può essere usato per descrivere entrambi i casi. Cerca di sintonizzare la tua mente sul concetto di estendere un tipo (non estendere una classe , non implementare un'interfaccia ). Limiti un parametro di tipo con un altro tipo e non importa quale sia effettivamente quel tipo. Importa solo che è il limite superiore ed è il suo supertipo .


-1

In effetti, quando si utilizza l'interfaccia generica, la parola chiave viene estesa . Ecco l'esempio di codice:

Esistono 2 classi che implementano l'interfaccia di saluto:

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

E il codice di prova:

@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();
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.