Génériques Java - pourquoi «étend T» est-il autorisé mais pas «implémente T»?


306

Je me demande s'il y a une raison particulière en Java pour utiliser toujours " extends" plutôt que " implements" pour définir les limites des paramètres.

Exemple:

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

est interdit mais

public class A<B extends C>{} 

est correct. Quelle est la raison de ceci?


14
Je ne sais pas pourquoi les gens pensent que la réponse de Tetsujin no Oni répond vraiment à la question. Il reformule essentiellement les observations d'OP en utilisant une formulation académique, mais ne donne aucun raisonnement. "Pourquoi n'y en a-t-il pas implements?" - "Parce qu'il n'y a que extends".
ThomasR

1
ThomasR: c'est parce que ce n'est pas une question de "permis", mais de sens: il n'y a pas de différence dans la façon dont vous écririez un générique consommant un type avec une contrainte que la contrainte provienne d'une interface ou d'un type ancêtre.
Tetsujin no Oni

Ajout d'une réponse ( stackoverflow.com/a/56304595/4922375 ) avec mon raisonnement pourquoi implementsn'apporterait rien de nouveau et compliquerait les choses. J'espère que cela vous sera utile.
Andrew Tobilko

Réponses:


328

Il n'y a pas de différence sémantique dans le langage de contrainte générique entre le fait qu'une classe «implémente» ou «étend». Les possibilités de contraintes sont «étend» et «super» - c'est-à-dire, est-ce que cette classe fonctionne avec assignable à cette autre (étend), ou est-ce que cette classe peut être assignée à partir de celle-là (super).


@KomodoDave (je pense que le numéro à côté de la réponse le marque comme étant correct, je ne sais pas s'il existe un autre moyen de le marquer, parfois d'autres réponses peuvent contenir des informations supplémentaires - par exemple, j'ai eu un problème particulier que je n'ai pas pu résoudre et Google vous envoie ici lorsque vous le recherchez.) @Tetsujin no Oni (Serait-il possible d'utiliser du code pour clarifier? merci :))
ntg

@ntg, ceci est un très bon exemple d'une question à la recherche d'exemples - je vais le lier en commentaire, plutôt que de l'intégrer dans la réponse à ce stade. stackoverflow.com/a/6828257/93922
Tetsujin no Oni

1
Je pense que la raison est au moins que j'aimerais avoir un générique qui peut avoir un constructeur et des méthodes qui peuvent accepter n'importe quelle classe qui à la fois étend une classe de base et présente une interface et pas seulement des interfaces qui étendent une interface. Ensuite, instanciez le test Genric pour la présence d'interfaces ET spécifiez la classe réelle en tant que paramètre de type. Idéalement, je voudrais que l'on class Generic<RenderableT extends Renderable implements Draggable, Droppable, ...> { Generic(RenderableT toDrag) { x = (Draggable)toDrag; } }veuille compiler des vérifications de temps.
peterk

1
@peterk Et vous obtenez cela avec RenderableT étend Renderable, Draggable, Droppable .... à moins que je ne comprenne pas ce que vous voulez que les génériques d'effacement fassent pour vous que cela ne fournit pas.
Tetsujin no Oni

@TetsujinnoOni vous ne voulez pas dans une application de temps de compilation de n'accepter que les classes qui implémentent un certain ensemble d'interfaces, puis de pouvoir référencer la classe OBJECT (qui présente ces interfaces) dans le générique et puis savoir que au moins au moment de la compilation (et de manière souhaitable au moment de l'exécution), tout ce qui est attribué au générique peut être converti en toute sécurité vers l'une des interfaces spécifiées. Ce n'est pas le cas de la façon dont java est implémenté maintenant. Mais ce serait bien :)
peterk

77

La réponse est ici  :

Pour déclarer un paramètre de type délimité, répertoriez le nom du paramètre de type, suivi du extendsmot - clé, suivi de sa limite supérieure […]. Notez que, dans ce contexte, étend est utilisé dans un sens général pour signifier soit extends(comme dans les classes) ou implements(comme dans les interfaces).

Alors voilà, c'est un peu déroutant, et Oracle le sait.


1
Pour ajouter à la confusion, getFoo(List<? super Foo> fooList) fonctionne UNIQUEMENT avec la classe qui est littéralement étendue par Foo like class Foo extends WildcardClass. Dans ce cas, une List<WildcardClass>entrée serait acceptable. Cependant, toute classe qui Fooimplémente ne fonctionnerait class Foo implements NonWorkingWildcardClasspas ne signifie pas List<NonWorkingWildcardClass>qu'elle sera valide dans le getFoo(List<? super Foo> fooList). Clair comme de l'eau de roche!
Ray

19

Probablement parce que pour les deux côtés (B et C), seul le type est pertinent, pas la mise en œuvre. Dans votre exemple

public class A<B extends C>{}

B peut également être une interface. "extend" est utilisé pour définir les sous-interfaces ainsi que les sous-classes.

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

Je pense généralement à «Sub étend Super» comme « Sub est comme Super , mais avec des capacités supplémentaires», et «Clz implémente Intf» car « Clz est une réalisation d' Intf ». Dans votre exemple, cela correspondrait à: B est comme C , mais avec des capacités supplémentaires. Les capacités sont pertinentes ici, pas la réalisation.


10
Considérez <B étend D & E>. E <caps> ne doit pas </caps> être une classe.
Tom Hawtin - tackline

7

Il se peut que le type de base soit un paramètre générique, donc le type réel peut être une interface d'une classe. Considérer:

class MyGen<T, U extends T> {

Du point de vue du code client, les interfaces sont presque indiscernables des classes, alors que pour le sous-type, c'est important.


7

Voici un exemple plus complexe où les extensions sont autorisées et éventuellement ce que vous voulez:

public class A<T1 extends Comparable<T1>>


5

C'est une sorte d'arbitraire parmi les termes à utiliser. Cela aurait pu être de toute façon. Peut-être que les concepteurs de langage pensaient que «s'étend» comme terme le plus fondamental et «implémente» comme cas particulier pour les interfaces.

Mais je pense que ce implementsserait un peu plus logique. Je pense que cela communique davantage que les types de paramètres ne doivent pas nécessairement être dans une relation d'héritage, ils peuvent être dans n'importe quel type de relation de sous-type.

Le glossaire Java exprime une vue similaire .


3

Nous avons l'habitude de

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

et toute légère déviation de ces règles nous déroute beaucoup.

La syntaxe d'une limite de type est définie comme

TypeBound:
    extends TypeVariable 
    extends ClassOrInterfaceType {AdditionalBound}

( JLS 12> 4.4. Variables de type>TypeBound )

Si nous devions le changer, nous ajouterions sûrement le implementscas

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

et se retrouver avec deux clauses traitées de manière identique

ClassOrInterfaceType:
    ClassType 
    InterfaceType

( JLS 12> 4.3. Types et valeurs de référence>ClassOrInterfaceType )

sauf qu'il faudrait aussi s'en occuper implements, ce qui compliquerait encore les choses.

Je crois que c'est la principale raison pour laquelle extends ClassOrInterfaceTypeon utilise au lieu de extends ClassTypeet implements InterfaceType- pour garder les choses simples dans le concept compliqué. Le problème est que nous n'avons pas le mot juste pour couvrir à la fois extendset implementsnous ne voulons certainement pas introduire un.

<T is ClassTypeA>
<T is InterfaceTypeA>

Bien qu'il extendsapporte un peu de désordre lorsqu'il accompagne une interface, c'est un terme plus large et il peut être utilisé pour décrire les deux cas. Essayez de régler votre esprit sur le concept d' extension d'un type (pas d' extension d'une classe , pas d' implémentation d'une interface ). Vous limitez un paramètre de type par un autre type et peu importe ce que ce type est réellement. Il importe seulement que ce soit sa limite supérieure et son super-type .


-1

En fait, lorsque vous utilisez une interface générique, le mot clé est également étendu . Voici l'exemple de code:

Il existe 2 classes qui implémentent l'interface Greeting:

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

Et le code de test:

@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();
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.