Genéricos Java - por que "estende T" é permitido, mas não "implementa T"?

306

Gostaria de saber se existe uma razão especial em Java para usar sempre " extends" em vez de " implements" para definir limites de parâmetros de tipo.

Exemplo:

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

é proibido, mas

public class A<B extends C>{} 

está correto. Qual o motivo disso?

user120623
fonte
14
Não sei por que as pessoas pensam que a resposta de Tetsujin no Oni realmente responde à pergunta. Basicamente, reformula as observações do OP usando redação acadêmica, mas não fornece nenhum raciocínio. "Por que não existe implements?" - "Porque só existe extends".
ThomasR
1
ThomasR: isso é porque não é uma questão de "permitido", mas de significado: não há diferença em como você escreveria um genérico consumindo um tipo com uma restrição, seja a restrição de uma interface ou de um tipo ancestral.
Tetsujin no Oni
Adicionei uma resposta ( stackoverflow.com/a/56304595/4922375 ) com o meu raciocínio por implementsque não traria nada de novo e complicaria ainda mais as coisas. Espero que seja útil para você.
Andrew Tobilko

Respostas:

328

Não há diferença semântica na linguagem de restrição genérica entre se uma classe 'implementa' ou 'estende'. As possibilidades de restrição são 'extends' e 'super' - ou seja, é essa classe para operar com atribuível a essa outra (extends), ou é essa classe atribuível a essa (super).

Tetsujin no Oni
fonte
@KomodoDave (acho que o número ao lado da resposta o marca como correto, não tenho certeza se existe outra maneira de marcá-lo, às vezes outras respostas podem conter informações adicionais - por exemplo, eu tive um problema específico que não consegui resolver e Google envia-lhe aqui quando procurá-lo) @Tetsujin no Oni (seria possível usar algum código para esclarecer thanx :)).?
NTG
@ntg, este é um exemplo muito bom em uma pergunta que procura exemplos - eu o vinculo no comentário, em vez de incorporar a resposta neste momento. stackoverflow.com/a/6828257/93922
Tetsujin no Oni
1
Eu acho que o motivo é pelo menos eu gostaria de ter um genérico que possa ter um construtor e métodos que aceitem qualquer classe que estenda uma classe base e exiba uma interface e não apenas interfaces que estendem uma interface. Em seguida, instale o teste Genric para a presença de interfaces E tenha a classe real especificada como um parâmetro de tipo. Idealmente, eu gostaria que alguém class Generic<RenderableT extends Renderable implements Draggable, Droppable, ...> { Generic(RenderableT toDrag) { x = (Draggable)toDrag; } }queira compilar verificações de tempo.
peterk 5/05
1
@peterk E você obtém isso com o RenderableT estende o Renderable, Draggable, Droppable .... a menos que eu não esteja entendendo o que você deseja que os genéricos de apagamento façam por você que isso não fornece.
Tetsujin no Oni 05/05
@TetsujinnoOni você não quer o que eu quero é uma execução em tempo de compilação de aceitar apenas classes que implementam um determinado conjunto de interfaces e, em seguida, poder referenciar a classe OBJECT (que exibe essas interfaces) no genérico e saber que pelo menos no tempo de compilação (e de maneira desejável no tempo de execução), qualquer coisa atribuída ao genérico pode ser convertida com segurança para qualquer uma das interfaces especificadas. Este não é o caso da maneira como o java é implementado agora. Mas seria bom :)
peterk
77

A resposta está aqui  :

Para declarar um parâmetro do tipo delimitado, liste o nome do parâmetro do tipo, seguido pela extendspalavra - chave, seguida pelo seu limite superior […]. Observe que, nesse contexto, extends é usado em um sentido geral para significar extends(como nas classes) ou implements(como nas interfaces).

Então, aí está, é um pouco confuso, e a Oracle sabe disso.

MikaelF
fonte
1
Para adicionar à confusão, getFoo(List<? super Foo> fooList) SOMENTE funciona com a classe que literalmente é estendida por Foo like class Foo extends WildcardClass. Nesse caso, a List<WildcardClass>seria uma entrada aceitável. No entanto, qualquer classe que Fooimplementa não funcionaria class Foo implements NonWorkingWildcardClassnão significa List<NonWorkingWildcardClass>que será válida no getFoo(List<? super Foo> fooList). Claro como cristal!
Raio
19

Provavelmente porque para ambos os lados (B e C) apenas o tipo é relevante, não a implementação. No seu exemplo

public class A<B extends C>{}

B também pode ser uma interface. "extends" é usado para definir subinterfaces e subclasses.

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

Geralmente penso em 'Sub estende Super' como ' Sub é como Super , mas com recursos adicionais', e 'Clz implementa Intf' como ' Clz é uma realização de Intf '. No seu exemplo, isso corresponderia: B é como C , mas com recursos adicionais. Os recursos são relevantes aqui, não a realização.

beetstra
fonte
10
Considere <B estende D & E>. E <caps> não deve </caps> ser uma classe.
Tom Hawtin - tackline 19/06/09
7

Pode ser que o tipo base seja um parâmetro genérico, portanto o tipo real pode ser uma interface de uma classe. Considerar:

class MyGen<T, U extends T> {

Também na perspectiva do código do cliente, as interfaces são quase indistinguíveis das classes, enquanto que para o subtipo é importante.

Tom Hawtin - linha de orientação
fonte
7

Aqui está um exemplo mais envolvido de onde estender é permitido e, possivelmente, o que você deseja:

public class A<T1 extends Comparable<T1>>

ntg
fonte
5

É meio arbitrário qual dos termos usar. Poderia ter sido de qualquer maneira. Talvez os projetistas da linguagem pensassem em "estender" como o termo mais fundamental e "implementarem" como o caso especial de interfaces.

Mas acho implementsque faria um pouco mais de sentido. Eu acho que isso comunica mais que os tipos de parâmetro não precisam estar em um relacionamento de herança, eles podem estar em qualquer tipo de relacionamento de subtipo.

O Java Glossary expressa uma visão semelhante .

Lii
fonte
3

Estamos acostumados

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

e qualquer pequeno desvio dessas regras nos confunde muito.

A sintaxe de um tipo ligado é definida como

TypeBound:
    extends TypeVariable 
    extends ClassOrInterfaceType {AdditionalBound}

( JLS 12> 4.4. Variáveis ​​de tipo>TypeBound )

Se mudássemos, certamente adicionaríamos o implementscaso

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

e termine com duas cláusulas processadas de forma idêntica

ClassOrInterfaceType:
    ClassType 
    InterfaceType

( JLS 12> 4.3. Tipos e valores de referência>ClassOrInterfaceType )

exceto que também precisaríamos cuidar implements, o que complicaria ainda mais as coisas.

Eu acredito que é a principal razão pela qual extends ClassOrInterfaceTypeé usado em vez de extends ClassTypee implements InterfaceType- para manter as coisas simples dentro do conceito complicado. O problema é que não temos a palavra certa para cobrir tanto extendse implementse nós definitivamente não queremos introduzir um.

<T is ClassTypeA>
<T is InterfaceTypeA>

Embora extendstraga alguma confusão quando acompanha uma interface, é um termo mais amplo e pode ser usado para descrever os dois casos. Tente ajustar sua mente ao conceito de estender um tipo (não estender uma classe , não implementar uma interface ). Você restringe um parâmetro de tipo por outro tipo e não importa qual seja esse tipo. Só importa que seja seu limite superior e seu supertipo .

Andrew Tobilko
fonte
-1

De fato, ao usar genérico na interface, a palavra-chave também é estendida . Aqui está o exemplo de código:

Existem 2 classes que implementam a 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 ");
    }
}

E o código de teste:

@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
fonte