Java generics - чому дозволено "розширює T", але не "реалізує T"?


306

Цікаво, чи є в Java особлива причина того, що завжди використовувати " extends", а не " implements" для визначення меж типу параметрів.

Приклад:

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

заборонено, але

public class A<B extends C>{} 

правильно. У чому причина цього?


14
Я не знаю, чому люди думають, що відповідь Тецуджіна ні Они справді не відповідає на питання. Це в основному перефразовує спостереження ОП з використанням академічних формулювань, але не дає жодних міркувань. "Чому його немає implements?" - "Тому що є тільки extends".
ThomasR

1
ThomasR: це тому, що це не питання "дозволеного", а сенсу: немає різниці в тому, як ви б написали загальне споживання типу з обмеженням, чи є обмеження від інтерфейсу чи типу предка.
Tetsujin no Oni

Додав відповідь ( stackoverflow.com/a/56304595/4922375 ) з моїми міркуваннями, чому implementsб не принести нічого нового і ускладнить все далі. Я сподіваюся, що вам це стане в нагоді.
Андрій Тобілко

Відповіді:


328

Немає семантичної різниці в мові загального обмеження між тим, чи клас "реалізує", або "розширює". Можливості обмеження є "extends" і "super" - тобто це клас для роботи з присвоюваним до іншого класу (розширюється), або цей клас можна віднести до цього класу (супер).


@KomodoDave (я думаю, що число поруч з відповіддю позначає це як правильне. Я не впевнений, чи є якийсь інший спосіб позначити це, іноді інші відповіді можуть містити додаткову інформацію - наприклад, у мене була певна проблема, яку я не міг вирішити і google надсилає вас сюди під час пошуку.) @Tetsujin no Oni (Чи можна було б використовувати якийсь код для уточнення? ніжx :))
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; } }скласти перевірки часу.
петерк

1
@peterk І ви розумієте, що програма RenderableT розширює програму Renderable, Draggable, Droppable .... якщо я не розумію, що ви хочете зробити для вас дженериками стирання, що це не надає.
Tetsujin no Oni

@TetsujinnoOni ви не в тому, що я хочу, - це виконання часу компіляції лише прийняття класів, які реалізують певний набір інтерфейсів, а потім зможете посилатись на клас OBJECT (який демонструє ці інтерфейси) в загальному, а потім знаєте, що принаймні під час компіляції (а також бажано під час виконання) будь-що, призначене для загального, може бути безпечно передано на будь-який із зазначених інтерфейсів. Це не той спосіб, яким реалізується java зараз. Але було б добре :)
peterk

77

Відповідь тут  :

Щоб оголосити параметр обмеженого типу, перелічіть ім'я параметра типу, а потім extendsключове слово, а потім його верхню межу […]. Зауважимо, що в цьому контексті розширення вживаються в загальному сенсі для позначення або extends(як у класах), або implements(як в інтерфейсах).

Так що у вас це є, це трохи заплутано, і Oracle це знає.


1
Щоб додати плутанину, getFoo(List<? super Foo> fooList) ТОЛЬКО працює з класом, який буквально розширюється як Foo class Foo extends WildcardClass. У цьому випадку a 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 розширює Super" як " Sub - це як супер , але з додатковими можливостями", а "Clz реалізує Intf", як " Clz є реалізацією Intf ". У вашому прикладі це відповідало б: B схоже на C , але з додатковими можливостями. Тут є актуальні можливості, а не реалізація.


10
Розглянемо <B розширює D & E>. E <caps> не повинен </caps> бути класом.
Том Хотін - тайклін

7

Можливо, базовий тип є загальним параметром, тому фактичний тип може бути інтерфейсом класу. Поміркуйте:

class MyGen<T, U extends T> {

Також з точки зору клієнтського коду інтерфейси майже не відрізняються від класів, тоді як для підтипу це важливо.


7

Ось більш прикладний приклад того, де розширення дозволено і можливо, що ви хочете:

public class A<T1 extends Comparable<T1>>


5

Якийсь довільний, який із термінів використовувати. Це могло бути і в будь-якому випадку. Можливо, мовні дизайнери вважали, що "розширюється" як найбільш фундаментальний термін, а "реалізується" як особливий випадок інтерфейсів.

Але я думаю, implementsце мало б трохи більше сенсу. Я думаю, що більше повідомляється про те, що типи параметрів не повинні знаходитись у відносинах спадкування, вони можуть бути у будь-якому виді підтипу.

Словник Java виражає аналогічну точку зору .


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

Насправді, при використанні загального в інтерфейсі ключове слово також розширюється . Ось приклад коду:

Є два класи, які реалізують інтерфейс привітання:

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();
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.