Обобщения Java - почему «расширяет T» разрешено, но не «реализует T»?

306

Интересно, есть ли в Java особая причина использовать всегда " extends", а не " implements" для определения границ параметров типа.

Пример:

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

запрещено, но

public class A<B extends C>{} 

правильно. В чем причина этого?

user120623
источник
14
Я не знаю, почему люди думают, что ответ Тетсуджин но Они действительно отвечает на этот вопрос. Это в основном перефразирует наблюдения OP, используя академическую формулировку, но не дает никаких аргументов. "Почему нет implements?" - «Потому что есть только extends».
ThomasR
1
ThomasR: это потому, что речь идет не о «разрешенном», а о смысле: нет разницы в том, как вы будете писать обобщенный тип, потребляющий тип с ограничением, независимо от того, является ли ограничение интерфейсом или типом-предком.
Тецуджин но Они
Добавил ответ ( stackoverflow.com/a/56304595/4922375 ) с моими рассуждениями, почему implementsбы не принести ничего нового и еще больше усложнить ситуацию. Я надеюсь, что это будет полезно для вас.
Андрей Тобилко

Ответы:

328

В родовом языке ограничений нет семантической разницы между «реализацией» или «расширением» класса. Возможны следующие ограничения: «расширяет» и «супер» - то есть, может ли этот класс работать с присваиваемым этому другому (расширяется) или этот класс назначается из этого (супер).

Тетсуджин но Они
источник
@KomodoDave (Я думаю, что число рядом с ответом помечает его как правильное, я не уверен, есть ли другой способ пометить его, иногда другие ответы могут содержать дополнительную информацию - например, у меня была конкретная проблема, которую я не мог решить, и Google отправляет вас сюда при поиске.) @Tetsujin no Oni (можно было бы использовать какой-то код для уточнения? спасибо :))
ntg
@ntg, это очень хороший пример для вопроса, ищущего примеры - я сошлюсь на него в комментарии, а не встраиваю ответ в данный момент. stackoverflow.com/a/6828257/93922
Tetsujin no Oni
1
Я думаю, что причина, по крайней мере, в том, что я хотел бы иметь универсальный шаблон, который может иметь конструктор и методы, которые могут принимать любой класс, который поддерживает базовый класс и имеет интерфейс, а не только интерфейсы, расширяющие интерфейс. Затем создайте экземпляр теста Genric для присутствия интерфейсов И имейте фактический класс, указанный в качестве параметра типа. В идеале я хотел бы, чтобы кто- class Generic<RenderableT extends Renderable implements Draggable, Droppable, ...> { Generic(RenderableT toDrag) { x = (Draggable)toDrag; } }то хотел проверять время компиляции.
peterk
1
@peterk И вы получаете это с RenderableT, расширяет Renderable, Draggable, Droppable .... если я не понимаю, что вы хотите, чтобы генерики стирания делали для вас, что это не обеспечивает.
Тетсуджин но Они
@ TetsujinnoOni, что вы не делаете, я хочу, чтобы во время компиляции принималось только принятие классов, которые реализуют определенный набор интерфейсов, и затем возможность ссылаться на класс OBJECT (который демонстрирует эти интерфейсы) в универсальном, а затем знать, что по крайней мере, во время компиляции (и желательно во время выполнения) все, что назначено универсальному, можно безопасно привести к любому из указанных интерфейсов. Это не тот способ, которым реализована Java. Но было бы неплохо :)
peterk
77

Ответ здесь  :

Чтобы объявить параметр ограниченного типа, укажите имя параметра типа, затем extendsключевое слово и верхнюю границу […]. Обратите внимание, что в этом контексте extends используется в общем смысле для обозначения extends(как в классах) или implements(как в интерфейсах).

Итак, у вас это есть, это немного сбивает с толку, и Oracle это знает.

MikaelF
источник
1
Чтобы добавить к путанице, getFoo(List<? super Foo> fooList) ТОЛЬКО работает с классом, который буквально расширяется как Foo class Foo extends WildcardClass. В этом случае List<WildcardClass>будет приемлемым вход. Однако любой класс, который Fooреализует не будет работать class Foo implements NonWorkingWildcardClass, не означает, List<NonWorkingWildcardClass>что будет действительным в getFoo(List<? super Foo> fooList). Кристально чистый!
Рэй
19

Вероятно, потому что для обеих сторон (B и C) важен только тип, а не реализация. В вашем примере

public class A<B extends C>{}

B также может быть интерфейсом. «extends» используется для определения подинтерфейсов, а также подклассов.

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

Я обычно думаю о «Sub extends Super» как « Sub похож на Super , но с дополнительными возможностями», а «Clz реализует Intf», так как « Clz - реализация Intf ». В вашем примере это будет соответствовать: B похож на C , но с дополнительными возможностями. Здесь важны возможности, а не реализация.

beetstra
источник
10
Рассмотрим <B расширяет D & E>. E <caps> не должен </ caps> быть классом.
Том Хотин -
7

Может случиться так, что базовый тип является универсальным параметром, поэтому фактический тип может быть интерфейсом класса. Рассматривать:

class MyGen<T, U extends T> {

Также с точки зрения клиентского кода интерфейсы практически неотличимы от классов, тогда как для подтипа это важно.

Том Хотин - Tackline
источник
7

Вот более сложный пример того, где можно использовать extends и, возможно, что вы хотите:

public class A<T1 extends Comparable<T1>>

NTG
источник
5

Это своего рода произвольно, какой из терминов использовать. Это могло быть так или иначе. Возможно, разработчики языка думали, что «расширяет» как самый фундаментальный термин, и «внедряет» как особый случай для интерфейсов.

Но я думаю, что implementsбыло бы немного больше смысла. Я думаю, это больше говорит о том, что типы параметров не должны быть в отношениях наследования, они могут быть в любом виде отношений подтипа.

Глоссарий Java выражает аналогичную точку зрения .

Lii
источник
3

Мы привыкли к

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

и любое небольшое отклонение от этих правил очень смущает нас.

Синтаксис привязки типа определяется как

TypeBound:
    extends TypeVariable 
    extends ClassOrInterfaceType {AdditionalBound}

( JLS 12> 4.4. Типовые переменные>TypeBound )

Если бы мы изменили это, мы обязательно добавили бы implementsслучай

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

и в конечном итоге с двумя одинаково обработанными предложениями

ClassOrInterfaceType:
    ClassType 
    InterfaceType

( JLS 12> 4.3. Типы ссылок и значения>ClassOrInterfaceType )

кроме нас также нужно было бы позаботиться о том implements, что еще больше усложнит ситуацию.

Я полагаю, что это главная причина, почему extends ClassOrInterfaceTypeиспользуется вместо extends ClassTypeи implements InterfaceType- чтобы все было просто в рамках сложной концепции. Проблема в том , что мы не имеем право слова , чтобы охватить как extendsи implementsмы определенно не хотим , чтобы ввести одну.

<T is ClassTypeA>
<T is InterfaceTypeA>

Хотя это extendsвносит некоторую путаницу, когда речь идет о интерфейсе, это более широкий термин, и его можно использовать для описания обоих случаев. Попробуйте настроить свой разум на идею расширения типа (не расширения класса , не реализации интерфейса ). Вы ограничиваете параметр типа другим типом, и не имеет значения, что это за тип на самом деле. Имеет значение только то, что это его верхняя граница и его супертип .

Андрей Тобилко
источник
-1

Фактически, при использовании универсального интерфейса, ключевое слово также расширяется . Вот пример кода:

Есть 2 класса, которые реализуют интерфейс приветствия:

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

И тестовый код:

@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
источник