Genéricos de Java: ¿por qué está permitido "extiende T" pero no "implementa T"?

306

Me pregunto si hay una razón especial en Java para usar siempre " extends" en lugar de " implements" para definir los límites de los parámetros de tipo.

Ejemplo:

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

está prohibido pero

public class A<B extends C>{} 

es correcto. ¿Cuál es la razón para eso?

usuario120623
fuente
14
No sé por qué la gente piensa que la respuesta de Tetsujin no Oni realmente responde la pregunta. Básicamente, reformula las observaciones de OP utilizando un lenguaje académico, pero no da ningún razonamiento. "¿Por qué no hay implements?" - "Porque solo hay extends".
ThomasR
1
ThomasR: eso se debe a que no se trata de "permitido", sino de significado: no hay diferencia en cómo escribiría un genérico que consume un tipo con una restricción si la restricción proviene de una interfaz o de un tipo ancestro.
Tetsujin no Oni
Agregué una respuesta ( stackoverflow.com/a/56304595/4922375 ) con mi razonamiento de por implementsqué no traería nada nuevo y complicaría aún más las cosas. Espero que te sea útil.
Andrew Tobilko

Respuestas:

328

No existe una diferencia semántica en el lenguaje de restricción genérico entre si una clase 'implementa' o 'extiende'. Las posibilidades de restricción son 'extendidas' y 'súper', es decir, si esta clase funciona con asignable a la otra (extiende), o es esta clase asignable desde esa (súper).

Tetsujin no Oni
fuente
@KomodoDave (Creo que el número al lado de la respuesta lo marca como correcto, no estoy seguro de si hay otra forma de marcarlo, a veces otras respuestas pueden contener información adicional, por ejemplo, tuve un problema particular que no pude resolver y google lo envía aquí cuando lo busca.) @Tetsujin no Oni (¿Sería posible usar algún código para aclararlo? gracias :))
ntg
@ntg, este es un muy buen ejemplo de una pregunta en busca de ejemplos: en este punto lo vincularé en un comentario, en lugar de incluirlo en la respuesta. stackoverflow.com/a/6828257/93922
Tetsujin no Oni
1
Creo que la razón es que al menos me gustaría tener un genérico que pueda tener un constructor y métodos que puedan aceptar cualquier clase que tenga una clase base y exhiba una interfaz, no solo interfaces que extiendan una interfaz. Luego tenga la instanciación de la prueba Genric para la presencia de interfaces Y tenga la clase real especificada como un parámetro de tipo. Lo ideal sería que class Generic<RenderableT extends Renderable implements Draggable, Droppable, ...> { Generic(RenderableT toDrag) { x = (Draggable)toDrag; } }uno quisiera verificaciones de tiempo de compilación.
Peter
1
@peterk Y obtienes eso con RenderableT extiende Renderable, Draggable, Droppable ... a menos que no entienda lo que quieres que los genéricos de borrado hagan por ti que esto no proporciona.
Tetsujin no Oni
@TetsujinnoOni, no lo que quiero es para una aplicación de tiempo de compilación de solo aceptar clases que implementan un cierto conjunto de interfaces, y luego poder hacer referencia a la clase OBJECT (que exhibe esas interfaces) en el genérico y luego saber que al menos en tiempo de compilación (y deseablemente en tiempo de ejecución) cualquier cosa asignada al genérico se puede transmitir de forma segura a cualquiera de las interfaces especificadas. Este no es el caso de la forma en que se implementa Java ahora. Pero sería bueno :)
Peter
77

La respuesta está aquí  :

Para declarar un parámetro de tipo acotado, enumere el nombre del parámetro de tipo, seguido de la extendspalabra clave, seguido de su límite superior [...]. Tenga en cuenta que, en este contexto, extend se usa en un sentido general para significar extends(como en las clases) o implements(como en las interfaces).

Así que ahí lo tienes, es un poco confuso, y Oracle lo sabe.

MikaelF
fuente
1
Para agregar a la confusión, getFoo(List<? super Foo> fooList) SOLO funciona con la clase que literalmente se extiende por Foo like class Foo extends WildcardClass. En este caso, a List<WildcardClass>sería una entrada aceptable. Sin embargo, cualquier clase que Fooimplemente no funcionaría class Foo implements NonWorkingWildcardClassno significa List<NonWorkingWildcardClass>que será válida en getFoo(List<? super Foo> fooList). ¡Claro como el cristal!
Ray
19

Probablemente porque para ambos lados (B y C) solo el tipo es relevante, no la implementación. En tu ejemplo

public class A<B extends C>{}

B puede ser una interfaz también. "extend" se utiliza para definir subinterfaces y subclases.

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

Normalmente pienso en 'Sub extiende Super' como ' Sub es como Super , pero con capacidades adicionales', y 'Clz implementa Intf' como ' Clz es una realización de Intf '. En su ejemplo, esto coincidiría: B es como C , pero con capacidades adicionales. Las capacidades son relevantes aquí, no la realización.

Beetstra
fuente
10
Considere <B extiende D y E>. E <caps> no debe </caps> ser una clase.
Tom Hawtin - tackline
7

Puede ser que el tipo base sea un parámetro genérico, por lo que el tipo real puede ser una interfaz de una clase. Considerar:

class MyGen<T, U extends T> {

Además, desde la perspectiva del código del cliente, las interfaces son casi indistinguibles de las clases, mientras que para el subtipo es importante.

Tom Hawtin - tackline
fuente
7

Aquí hay un ejemplo más complicado de dónde se permite extender y posiblemente lo que desea:

public class A<T1 extends Comparable<T1>>

ntg
fuente
5

Es algo arbitrario cuál de los términos usar. Podría haber sido de cualquier manera. Quizás los diseñadores de lenguaje pensaron que "se extiende" como el término más fundamental, e "implementa" como el caso especial para las interfaces.

Pero creo que implementstendría un poco más de sentido. Creo que eso comunica más que los tipos de parámetros no tienen que estar en una relación de herencia, pueden estar en cualquier tipo de relación de subtipo.

El Glosario de Java expresa una opinión similar .

Lii
fuente
3

Estamos acostumbrados a

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

y cualquier ligera desviación de estas reglas nos confunde enormemente.

La sintaxis de un tipo enlazado se define como

TypeBound:
    extends TypeVariable 
    extends ClassOrInterfaceType {AdditionalBound}

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

Si tuviéramos que cambiarlo, seguramente agregaríamos el implementscaso

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

y terminan con dos cláusulas procesadas idénticamente

ClassOrInterfaceType:
    ClassType 
    InterfaceType

( JLS 12> 4.3. Tipos de referencia y valores>ClassOrInterfaceType )

excepto que también tendríamos que cuidarnos implements, lo que complicaría aún más las cosas.

Creo que es la razón principal por la que extends ClassOrInterfaceTypese usa en lugar de extends ClassTypey implements InterfaceTypepara mantener las cosas simples dentro del complicado concepto. El problema es que no tenemos la palabra correcta para cubrir ambos extendsy implementsdefinitivamente no queremos introducir una.

<T is ClassTypeA>
<T is InterfaceTypeA>

Aunque extendstrae algo de desorden cuando va junto con una interfaz, es un término más amplio y puede usarse para describir ambos casos. Intente sintonizar su mente con el concepto de extender un tipo (no extender una clase , no implementar una interfaz ). Usted restringe un parámetro de tipo por otro tipo y no importa cuál sea ese tipo en realidad. Solo importa que sea su límite superior y su supertipo .

Andrew Tobilko
fuente
-1

De hecho, cuando se usa genérico en la interfaz, la palabra clave también se extiende . Aquí está el ejemplo de código:

Hay 2 clases que implementan la interfaz de saludo:

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

Y el código de prueba:

@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();
}
zhangde
fuente