Generics Java - dlaczego „rozszerza T” jest dozwolone, a nie „implementuje T”?

306

Zastanawiam się, czy jest jakiś szczególny powód w Javie, aby zawsze używać „ extends” zamiast „ implements” do definiowania granic parametrów.

Przykład:

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

jest zabronione, ale

public class A<B extends C>{} 

jest poprawne. Jaki jest tego powód?

użytkownik120623
źródło
14
Nie wiem, dlaczego ludzie myślą, że odpowiedź Tetsujin no Oni naprawdę odpowiada na pytanie. Zasadniczo przeformułowuje obserwacje OP przy użyciu akademickiego sformułowania, ale nie podaje żadnego uzasadnienia. „Dlaczego nie ma implements?” - „Ponieważ jest tylko extends”.
ThomasR
1
ThomasR: to dlatego, że nie jest to kwestia „dozwolona”, ale znaczenie: nie ma różnicy w tym, jak napisałbyś rodzajowy konsumujący typ z ograniczeniem, czy ograniczenie jest z interfejsu czy typu przodka.
Tetsujin no Oni
Dodałem odpowiedź ( stackoverflow.com/a/56304595/4922375 ) z moim uzasadnieniem, dlaczego implementsnie wniósłbym niczego nowego i skomplikowałoby to jeszcze bardziej. Mam nadzieję, że ci się przyda.
Andrew Tobilko,

Odpowiedzi:

328

Nie ma semantycznej różnicy w ogólnym języku ograniczeń między tym, czy klasa „implementuje”, czy „rozszerza”. Możliwościami ograniczeń są „rozszerzenia” i „super” - to znaczy, czy ta klasa może działać z możliwością przypisania do tej drugiej (rozszerzenie), czy też ta klasa może być przypisana z tej (super).

Tetsujin no Oni
źródło
@KomodoDave (myślę, że liczba obok odpowiedzi oznacza ją jako właściwą, nie jestem pewien, czy istnieje inny sposób na oznaczenie, czasami inne odpowiedzi mogą zawierać dodatkowe informacje - np. Miałem konkretny problem, którego nie mogłem rozwiązać i Google wysyła cię tutaj podczas wyszukiwania.) @Tetsujin no Oni (Czy można by użyć kodu do wyjaśnienia? thanx :))
ntg
@ntg, jest to bardzo dobry przykład pytania szukającego przykładów - połączę go w komentarzu, zamiast osadzać w odpowiedzi w tym miejscu. stackoverflow.com/a/6828257/93922
Tetsujin no Oni
1
Myślę, że powodem jest przynajmniej to, że chciałbym mieć ogólny, który może mieć konstruktor i metody, które mogą zaakceptować dowolną klasę, która zarówno ustawia klasę podstawową, jak i wykazuje interfejs nie tylko interfejsy rozszerzające interfejs. Następnie przygotuj instancję testu Genric dla obecności interfejsów ORAZ podaj rzeczywistą klasę jako parametr typu. Idealnie chciałbym, żeby class Generic<RenderableT extends Renderable implements Draggable, Droppable, ...> { Generic(RenderableT toDrag) { x = (Draggable)toDrag; } }ktoś chciał skompilować kontrole czasu.
peterk
1
@peterk I dostajesz to z RenderableT rozszerza Renderable, Draggable, Droppable .... chyba że nie rozumiem, co chcesz zrobić dla generics kasowania, czego to nie zapewnia.
Tetsujin no Oni
@ TetsujinnoOni nie w tym, czego chcę, jest wymuszanie w czasie kompilacji tylko akceptowania klas, które implementują pewien zestaw interfejsów, a następnie być w stanie odwoływać się do klasy OBJECT (która wyświetla te interfejsy) w formie ogólnej, a następnie wiedzieć, że przynajmniej w czasie kompilacji (a najlepiej w czasie wykonywania) wszystko przypisane do ogólnego można bezpiecznie rzutować na dowolny z określonych interfejsów. To nie jest tak, jak teraz Java jest wdrażana. Ale byłoby miło :)
peterk
77

Odpowiedź znajduje się tutaj  :

Aby zadeklarować parametr typu ograniczonego, należy podać nazwę parametru typu, a następnie extendssłowo kluczowe, a następnie jego górną granicę […]. Zauważ, że w tym kontekście rozszerzenie jest używane w ogólnym znaczeniu, aby oznaczać albo extends(jak w klasach) albo implements(jak w interfejsach).

Więc masz, to trochę mylące i Oracle o tym wie.

MikaelF
źródło
1
Aby dodać zamieszanie getFoo(List<? super Foo> fooList) TYLKO działa z klasą, która dosłownie jest rozszerzana przez Foo jak class Foo extends WildcardClass. W takim przypadku List<WildcardClass>akceptowalne byłoby wejście. Jednak każda klasa, która Fooimplementuje nie działa class Foo implements NonWorkingWildcardClass, nie oznacza, List<NonWorkingWildcardClass>że będzie poprawna w getFoo(List<? super Foo> fooList). Krystalicznie czyste!
Ray
19

Prawdopodobnie dlatego, że dla obu stron (B i C) istotny jest tylko typ, a nie implementacja. W twoim przykładzie

public class A<B extends C>{}

B może być również interfejsem. „extends” służy do definiowania pod-interfejsów oraz podklas.

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

Zwykle myślę o „Sub rozszerza Super”, ponieważ „ Sub jest jak Super , ale z dodatkowymi możliwościami”, a „Clz implementuje Intf”, ponieważ „ Clz jest realizacją Intf ”. W twoim przykładzie byłoby to zgodne: B jest jak C , ale z dodatkowymi możliwościami. Możliwości są tutaj istotne, a nie realizacja.

beetstra
źródło
10
Rozważ <B rozszerza D i E>. E <caps> nie może być </caps> klasą.
Tom Hawtin - tackline
7

Może się zdarzyć, że typ podstawowy jest parametrem ogólnym, więc rzeczywisty typ może być interfejsem klasy. Rozważać:

class MyGen<T, U extends T> {

Również z perspektywy kodu klienta interfejsy są prawie nie do odróżnienia od klas, podczas gdy dla podtypu jest to ważne.

Tom Hawtin - tackline
źródło
7

Oto bardziej zaangażowany przykład tego, gdzie rozszerzenie jest dozwolone i ewentualnie czego chcesz:

public class A<T1 extends Comparable<T1>>

ntg
źródło
5

To rodzaj arbitralnie używanego terminu. Tak mogło być w obu przypadkach. Być może projektanci języków uważali „rozszerzenie” za najbardziej podstawowy termin i „implementuje” jako specjalny przypadek interfejsów.

Ale myślę, że implementsmiałoby to nieco więcej sensu. Myślę, że to przekazuje więcej, że typy parametrów nie muszą być w relacji dziedziczenia, mogą być w jakiejkolwiek relacji podtypu.

Glosariusz Java wyraża podobny pogląd .

Lii
źródło
3

Jesteśmy przyzwyczajeni

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

a wszelkie niewielkie odstępstwa od tych zasad bardzo nas dezorientują.

Składnia powiązanego typu jest zdefiniowana jako

TypeBound:
    extends TypeVariable 
    extends ClassOrInterfaceType {AdditionalBound}

( JLS 12> 4.4. Zmienne typu>TypeBound )

Gdybyśmy to zmienili, z pewnością dodalibyśmy tę implementssprawę

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

i kończą się dwiema identycznie przetworzonymi klauzulami

ClassOrInterfaceType:
    ClassType 
    InterfaceType

( JLS 12> 4.3. Typy referencyjne i wartości>ClassOrInterfaceType )

z wyjątkiem tego, że musielibyśmy się również zająć implements, co jeszcze bardziej skomplikowałoby sprawę.

Wierzę, że jest to główny powód, dla którego extends ClassOrInterfaceTypestosuje się zamiast extends ClassTypei implements InterfaceType- aby zachować rzeczy proste w skomplikowanym pojęciem. Problem polega na tym, że nie mamy odpowiedniego słowa, aby opisać jedno extendsi drugie i na implementspewno nie chcemy go przedstawić.

<T is ClassTypeA>
<T is InterfaceTypeA>

Chociaż extendswprowadza pewien bałagan, gdy idzie w parze z interfejsem, jest to szerszy termin i można go użyć do opisania obu przypadków. Spróbuj dostroić umysł do koncepcji rozszerzenia typu (nie rozszerzania klasy , nie implementowania interfejsu ). Ograniczasz parametr typu innym typem i nie ma znaczenia, co to właściwie jest. Liczy się tylko to, że jest to górna granica i jego nadtyp .

Andrew Tobilko
źródło
-1

W rzeczywistości, gdy używa się ogólnego interfejsu, słowo kluczowe również się rozszerza . Oto przykład kodu:

Istnieją 2 klasy, które implementują interfejs powitania:

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

I kod testowy:

@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
źródło