Java generics - mengapa "extends T" diizinkan tetapi tidak "mengimplementasikan T"?

306

Saya bertanya-tanya apakah ada alasan khusus di Jawa untuk selalu menggunakan " extends" daripada " implements" untuk mendefinisikan batasan typeparameter.

Contoh:

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

dilarang tapi

public class A<B extends C>{} 

benar. Apa alasan untuk itu?

pengguna120623
sumber
14
Saya tidak tahu mengapa orang berpikir jawaban oleh Tetsujin no Oni benar-benar menjawab pertanyaan itu. Ini pada dasarnya mengulangi pengamatan OP menggunakan kata-kata akademis, tetapi tidak memberikan alasan apapun. "Kenapa tidak ada implements?" - "Karena hanya ada extends".
ThomasR
1
ThomasR: itu karena itu bukan pertanyaan "diizinkan", tetapi makna: tidak ada perbedaan dalam bagaimana Anda akan menulis jenis generik dengan batasan apakah kendala berasal dari antarmuka atau tipe leluhur.
Tetsujin no Oni
Menambahkan jawaban ( stackoverflow.com/a/56304595/4922375 ) dengan alasan saya mengapa implementstidak membawa sesuatu yang baru dan akan memperumit masalah lebih lanjut. Saya harap ini akan membantu Anda.
Andrew Tobilko

Jawaban:

328

Tidak ada perbedaan semantik dalam bahasa kendala generik antara apakah suatu kelas 'mengimplementasikan' atau 'meluas'. Kemungkinan kendala adalah 'extends' dan 'super' - yaitu, apakah kelas ini dapat beroperasi dengan ditugaskan ke yang lain (meluas), atau apakah kelas ini dapat ditugaskan dari yang itu (super).

Tetsujin no Oni
sumber
@ KomodoDave (saya pikir angka di sebelah jawaban menandainya sebagai benar, saya tidak yakin apakah ada cara lain untuk menandainya, kadang-kadang jawaban lain mungkin berisi info tambahan - misalnya saya memiliki masalah tertentu yang tidak dapat saya pecahkan dan google mengirim Anda ke sini ketika mencarinya.) @Tetsujin no Oni (Apakah mungkin menggunakan beberapa kode untuk menjelaskan? thanx :))
ntg
@ntg, ini adalah contoh yang sangat bagus untuk pertanyaan yang mencari contoh - saya akan menautkannya dalam komentar, daripada menyematkan jawaban pada saat ini. stackoverflow.com/a/6828257/93922
Tetsujin no Oni
1
Saya pikir alasannya adalah setidaknya saya ingin memiliki generik yang dapat memiliki konstruktor dan metode yang dapat menerima semua kelas yang keduanya menggunakan kelas dasar dan menunjukkan antarmuka bukan hanya antarmuka yang memperluas antarmuka. Kemudian miliki instantiation dari tes Genric untuk presense dari antarmuka DAN kelas yang sebenarnya ditentukan sebagai parameter tipe. Idealnya saya ingin class Generic<RenderableT extends Renderable implements Draggable, Droppable, ...> { Generic(RenderableT toDrag) { x = (Draggable)toDrag; } }Seseorang ingin mengkompilasi cek waktu.
peterk
1
@peterk Dan Anda mendapatkannya dengan RenderableT extends Renderable, Draggable, Droppable .... kecuali saya tidak mengerti apa yang ingin dilakukan penghapus generik untuk Anda yang tidak disediakan oleh ini.
Tetsujin no Oni
@TetsujinnoOni Anda tidak dalam apa yang saya inginkan adalah untuk penegakan waktu kompilasi hanya menerima kelas yang mengimplementasikan seperangkat antarmuka tertentu, dan kemudian dapat referensi kelas OBJECT (yang menunjukkan antarmuka itu) dalam generik dan kemudian tahu bahwa setidaknya pada waktu kompilasi (dan diinginkan pada saat run time) apa pun yang ditugaskan ke generik dapat dengan aman dilemparkan ke salah satu antarmuka yang ditentukan. Ini bukan kasus cara java diimplementasikan sekarang. Tapi itu akan menyenangkan :)
peterk
77

Jawabannya ada di sini  :

Untuk mendeklarasikan parameter tipe terikat, daftarkan nama parameter tipe, diikuti oleh extendskata kunci, diikuti oleh batas atas [...]. Perhatikan bahwa, dalam konteks ini, extends digunakan dalam arti umum yang berarti extends(seperti dalam kelas) atau implements(seperti dalam antarmuka).

Jadi begitulah, agak membingungkan, dan Oracle tahu itu.

MikaelF
sumber
1
Untuk menambah kebingungan, getFoo(List<? super Foo> fooList) HANYA berfungsi dengan kelas yang benar-benar diperluas oleh Foo class Foo extends WildcardClass. Dalam hal ini List<WildcardClass>input yang dapat diterima. Namun setiap kelas yang Foomengimplementasikan tidak akan berhasil class Foo implements NonWorkingWildcardClasstidak berarti List<NonWorkingWildcardClass>akan valid dalam getFoo(List<? super Foo> fooList). Jelas!
Ray
19

Mungkin karena untuk kedua belah pihak (B dan C) hanya tipe yang relevan, bukan implementasinya. Dalam contoh Anda

public class A<B extends C>{}

B bisa menjadi antarmuka juga. "extends" digunakan untuk mendefinisikan sub-antarmuka dan juga sub-kelas.

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

Saya biasanya menganggap 'Sub extends Super' sebagai ' Sub adalah seperti Super , tetapi dengan kemampuan tambahan', dan 'Clz mengimplementasikan Intf' karena ' Clz adalah realisasi dari Intf '. Dalam contoh Anda, ini akan cocok: B seperti C , tetapi dengan kemampuan tambahan. Kemampuan itu relevan di sini, bukan realisasinya.

beetstra
sumber
10
Pertimbangkan <B extends D&E>. E <caps> tidak boleh </caps> menjadi kelas.
Tom Hawtin - tackline
7

Mungkin tipe dasar adalah parameter generik, jadi tipe aktual mungkin merupakan antarmuka kelas. Mempertimbangkan:

class MyGen<T, U extends T> {

Juga dari antarmuka perspektif kode klien hampir tidak bisa dibedakan dari kelas, sedangkan untuk subtipe itu penting.

Tom Hawtin - tackline
sumber
7

Berikut adalah contoh yang lebih terlibat tentang di mana ekstensi diizinkan dan mungkin apa yang Anda inginkan:

public class A<T1 extends Comparable<T1>>

ntg
sumber
5

Ini semacam arbitrer yang mana dari ketentuan yang digunakan. Bisa jadi sebaliknya. Mungkin para perancang bahasa menganggap "meluas" sebagai istilah yang paling mendasar, dan "mengimplementasikan" sebagai kasus khusus untuk antarmuka.

Tapi saya pikir implementsakan lebih masuk akal. Saya pikir lebih banyak berkomunikasi bahwa tipe parameter tidak harus berada dalam hubungan pewarisan, mereka bisa dalam segala jenis hubungan subtipe.

Java Glossary mengekspresikan pandangan yang serupa .

Lii
sumber
3

Kita terbiasa

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

dan sedikit penyimpangan dari aturan ini sangat membingungkan kami.

Sintaks tipe terikat didefinisikan sebagai

TypeBound:
    extends TypeVariable 
    extends ClassOrInterfaceType {AdditionalBound}

( JLS 12> 4.4. Jenis Variabel>TypeBound )

Jika kami mengubahnya, kami pasti akan menambahkan implementskasus ini

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

dan berakhir dengan dua klausa yang diproses secara identik

ClassOrInterfaceType:
    ClassType 
    InterfaceType

( JLS 12> 4.3. Jenis dan Nilai Referensi>ClassOrInterfaceType )

kecuali kita juga perlu diurus implements, yang akan memperumit masalah.

Saya percaya itu adalah alasan utama mengapa extends ClassOrInterfaceTypedigunakan sebagai pengganti extends ClassTypedan implements InterfaceType- untuk menjaga hal-hal sederhana dalam konsep rumit. Masalahnya adalah kita tidak memiliki kata yang tepat untuk membahas keduanya extendsdan implementsdan kita pasti tidak ingin memperkenalkannya.

<T is ClassTypeA>
<T is InterfaceTypeA>

Meskipun extendsmembawa beberapa kekacauan ketika berjalan seiring dengan antarmuka, itu adalah istilah yang lebih luas dan dapat digunakan untuk menggambarkan kedua kasus. Cobalah untuk menyesuaikan pikiran Anda dengan konsep memperluas jenis (tidak memperluas kelas , tidak mengimplementasikan antarmuka ). Anda membatasi parameter tipe oleh tipe lain dan tidak masalah apa tipe itu sebenarnya. Yang penting hanyalah batas atasnya dan supertipe .

Andrew Tobilko
sumber
-1

Bahkan, saat menggunakan generik pada antarmuka, kata kunci juga diperluas . Berikut ini contoh kode:

Ada 2 kelas yang mengimplementasikan antarmuka Ucapan:

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

Dan kode tes:

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