Definisi Java Enum

151

Saya pikir saya memahami generik Java dengan cukup baik, tetapi kemudian saya menemukan yang berikut di java.lang.Enum:

class Enum<E extends Enum<E>>

Bisakah seseorang menjelaskan bagaimana menafsirkan parameter tipe ini? Poin bonus untuk memberikan contoh lain di mana parameter tipe yang sama dapat digunakan.

Dónal
sumber
9
Inilah penjelasan yang paling saya sukai: Groking Enum (alias Enum & lt; E extends Enum & lt; E >>)
Alan Moore
Pertanyaan ini memiliki jawaban yang lebih baik: stackoverflow.com/a/3068001/2413303
EpicPandaForce

Jawaban:

105

Ini berarti bahwa argumen tipe untuk enum harus berasal dari enum yang itu sendiri memiliki argumen tipe yang sama. Bagaimana ini bisa terjadi? Dengan membuat argumen type maka tipe baru itu sendiri. Jadi jika saya punya enum yang disebut StatusCode, itu akan setara dengan:

public class StatusCode extends Enum<StatusCode>

Sekarang jika Anda memeriksa kendala, kami punya Enum<StatusCode>- jadi E=StatusCode. Mari kita periksa: apakah Ememperpanjang Enum<StatusCode>? Iya! Kami baik-baik saja

Anda mungkin bertanya pada diri sendiri apa gunanya ini :) Yah, itu berarti bahwa API untuk Enum dapat merujuk pada dirinya sendiri - misalnya, bisa mengatakan Enum<E>implementasinya Comparable<E>. Kelas dasar dapat melakukan perbandingan (dalam hal enum) tetapi dapat memastikan bahwa itu hanya membandingkan jenis enum yang tepat satu sama lain. (EDIT: Ya, hampir - lihat hasil edit di bagian bawah.)

Saya telah menggunakan sesuatu yang serupa di porta C # dari ProtocolBuffers. Ada "pesan" (tidak berubah) dan "pembangun" (bisa berubah, digunakan untuk membangun pesan) - dan mereka datang sebagai pasangan jenis. Antarmuka yang terlibat adalah:

public interface IBuilder<TMessage, TBuilder>
  where TMessage : IMessage<TMessage, TBuilder> 
  where TBuilder : IBuilder<TMessage, TBuilder>

public interface IMessage<TMessage, TBuilder>
  where TMessage : IMessage<TMessage, TBuilder> 
  where TBuilder : IBuilder<TMessage, TBuilder>

Ini berarti bahwa dari pesan Anda bisa mendapatkan pembangun yang sesuai (misalnya untuk mengambil salinan pesan dan mengubah beberapa bit) dan dari pembangun Anda bisa mendapatkan pesan yang sesuai ketika Anda selesai membangunnya. Ini adalah pekerjaan yang baik, pengguna API tidak perlu benar-benar peduli tentang ini - ini sangat rumit, dan butuh beberapa iterasi untuk sampai ke tempatnya.

EDIT: Perhatikan bahwa ini tidak menghentikan Anda dari membuat tipe aneh yang menggunakan argumen tipe yang itu sendiri tidak apa-apa, tetapi yang bukan tipe yang sama. Tujuannya untuk memberi manfaat dengan benar kasus yang daripada melindungi Anda dari kasus yang salah .

Jadi jika Enum tidak ditangani "khusus" di Java, Anda dapat (seperti tercantum dalam komentar) membuat jenis berikut:

public class First extends Enum<First> {}
public class Second extends Enum<First> {}

Secondakan menerapkan Comparable<First>daripada Comparable<Second>... tapi Firstitu sendiri akan baik-baik saja.

Jon Skeet
sumber
1
@ Arcrc: Saya tidak ingat mengapa begitu saja harus generik di kedua pembangun dan pesan. Saya cukup yakin saya tidak akan turun rute itu jika saya tidak membutuhkannya :)
Jon Skeet
1
@SayemAhmed: Ya, itu tidak mencegah yang aspek mencampur jenis. Saya akan menambahkan catatan tentang ini.
Jon Skeet
1
"Saya telah menggunakan sesuatu yang serupa di porta C # dari ProtocolBuffers." Tapi itu berbeda karena pembangun memiliki metode instan yang mengembalikan tipe parameter tipe. Enumtidak memiliki metode instan yang mengembalikan tipe parameter tipe.
Newacct
1
@ JonSkeet: Mengingat bahwa kelas enum selalu dibuat otomatis, saya mengklaim itu class Enum<E>sudah cukup dalam semua kasus. Dan di Generics Anda hanya boleh menggunakan ikatan yang lebih ketat jika memang benar-benar diperlukan untuk memastikan keamanan jenis.
Newacct
1
@JonSkeet: Juga, jika Enumsubclass tidak selalu software otomatis, satu-satunya alasan Anda akan perlu class Enum<E extends Enum<?>>lebih class Enum<E>adalah kemampuan untuk akses ordinaluntuk compareTo(). Namun, jika Anda memikirkannya, tidak masuk akal dari sudut pandang bahasa untuk memungkinkan Anda membandingkan dua jenis enum melalui tata cara mereka. Oleh karena itu, implementasi Enum.compareTo()yang menggunakan ordinalhanya masuk akal dalam konteks Enumsubclass yang di-autogenerasi. Jika Anda dapat secara manual subkelas Enum, compareTomungkin harus abstract.
newacct
27

Berikut ini adalah versi penjelasan yang dimodifikasi dari buku Java Generics and Collections : Kami telah Enummendeklarasikannya

enum Season { WINTER, SPRING, SUMMER, FALL }

yang akan diperluas ke kelas

final class Season extends ...

di mana ...menjadi kelas dasar entah bagaimana parameter untuk Enums. Mari kita cari tahu apa yang harus terjadi. Nah, salah satu syaratnya Seasonadalah harus diterapkan Comparable<Season>. Jadi kita perlu

Season extends ... implements Comparable<Season>

Apa yang bisa Anda gunakan untuk ...memungkinkan ini berfungsi? Mengingat bahwa itu harus menjadi parameterisasi Enum, satu-satunya pilihan adalah Enum<Season>, sehingga Anda dapat memiliki:

Season extends Enum<Season>
Enum<Season> implements Comparable<Season>

Jadi Enumparameterisasi pada jenis suka Season. Abstrak dari Seasondan Anda mendapatkan bahwa parameter Enumadalah jenis apa pun yang memuaskan

 E extends Enum<E>

Maurice Naftalin (penulis bersama, Java Generics and Collections)

Maurice Naftalin
sumber
1
@newacct OK, saya mengerti sekarang: Anda ingin semua enum menjadi instance dari Enum <E>, kan? (Karena jika itu adalah contoh dari subtipe Enum, argumen di atas berlaku.) Tapi kemudian Anda tidak akan memiliki enum yang aman jenis lagi, jadi kehilangan titik memiliki enum sama sekali.
Maurice Naftalin
1
@newacct Apakah Anda tidak ingin Seasonmenerapkan itu Comparable<Season>?
Maurice Naftalin
2
@newacct Lihatlah definisi Enum. Untuk membandingkan satu contoh dengan yang lainnya, ia harus membandingkan tata cara mereka. Jadi argumen compareTometode harus dideklarasikan sebagai Enumsubtipe, atau kompiler akan (dengan benar) mengatakan bahwa itu tidak memiliki ordinal.
Maurice Naftalin
2
@MauriceNaftalin: Jika Java tidak melarang subclassing secara manual Enum, maka akan mungkin untuk memilikinya class OneEnum extends Enum<AnotherEnum>{}, bahkan dengan cara Enumyang dinyatakan sekarang. Ini tidak akan membuat banyak akal untuk dapat membandingkan satu jenis enum dengan yang lain, sehingga kemudian Enum's compareTotidak masuk akal sebagaimana dinyatakan pula. Batas tidak memberikan bantuan apa pun untuk ini.
Newacct
2
@MauriceNaftalin: Jika ordinal adalah alasannya, maka public class Enum<E extends Enum<?>>itu juga sudah cukup.
Newacct
6

Ini dapat diilustrasikan dengan contoh sederhana dan teknik yang dapat digunakan untuk mengimplementasikan panggilan metode berantai untuk sub-kelas. Dalam contoh di bawah ini, setNamepengembalian Noderantai tidak akan berfungsi untuk City:

class Node {
    String name;

    Node setName(String name) {
        this.name = name;
        return this;
    }
}

class City extends Node {
    int square;

    City setSquare(int square) {
        this.square = square;
        return this;
    }
}

public static void main(String[] args) {
    City city = new City()
        .setName("LA")
        .setSquare(100);    // won't compile, setName() returns Node
}

Jadi kita bisa mereferensikan sub-kelas dalam deklarasi generik, sehingga Citysekarang mengembalikan tipe yang benar:

abstract class Node<SELF extends Node<SELF>>{
    String name;

    SELF setName(String name) {
        this.name = name;
        return self();
    }

    protected abstract SELF self();
}

class City extends Node<City> {
    int square;

    City setSquare(int square) {
        this.square = square;
        return self();
    }

    @Override
    protected City self() {
        return this;
    }

    public static void main(String[] args) {
       City city = new City()
            .setName("LA")
            .setSquare(100);                 // ok!
    }
}
Andrey Chaschev
sumber
Solusi Anda memiliki pemain yang tidak dicentang: return (CHILD) this;Pertimbangkan untuk menambahkan metode getThis (): protected CHILD getThis() { return this; } Lihat: angelikalanger.com/GenericsFAQ/FAQSections/…
Roland
@Roland terima kasih atas tautannya, saya sudah meminjam ide darinya. Bisakah Anda menjelaskan kepada saya atau mengarahkan ke sebuah artikel yang menjelaskan mengapa ini merupakan praktik yang buruk dalam kasus khusus ini? Metode dalam tautan membutuhkan lebih banyak pengetikan dan ini adalah argumen utama mengapa saya menghindari ini. Saya belum pernah melihat kesalahan pemeran dalam kasus ini + Saya tahu bahwa ada beberapa kesalahan pemeran yang tak terhindarkan - yaitu ketika seseorang menyimpan objek dari berbagai jenis ke koleksi yang sama. Jadi jika gips yang tidak dicentang tidak kritis dan desainnya agak rumit ( Node<T>bukan itu masalahnya), saya mengabaikannya untuk menghemat waktu.
Andrey Chaschev
Suntingan Anda tidak jauh berbeda dari sebelumnya selain menambahkan sedikit gula sintaksis, pertimbangkan bahwa kode berikut ini akan benar-benar mengkompilasi tetapi membuang kesalahan run time: `Node <City> node = new Node <City> () .setName (" node "). setSquare (1); `Jika Anda melihat kode byte java Anda akan melihat bahwa karena untuk menghapus kode pernyataan return (SELF) this;dikompilasi ke dalam return this;, jadi Anda bisa tinggalkan saja.
Roland
@Roland terima kasih, ini yang saya butuhkan - akan memperbarui contoh ketika saya bebas.
Andrey Chaschev
Tautan berikut juga baik: angelikalanger.com/GenericsFAQ/FAQSections/…
Roland
3

Anda bukan satu-satunya yang bertanya-tanya apa artinya itu; lihat blog Java Chaotic .

"Jika kelas memperluas kelas ini, itu harus melewati parameter E. Batas E parameter adalah untuk kelas yang memperluas kelas ini dengan parameter E yang sama".

kpirkkal
sumber
1

Posting ini benar-benar menjelaskan kepada saya masalah 'tipe generik rekursif' ini. Saya hanya ingin menambahkan kasus lain di mana struktur khusus ini diperlukan.

Misalkan Anda memiliki node generik dalam grafik generik:

public abstract class Node<T extends Node<T>>
{
    public void addNeighbor(T);

    public void addNeighbors(Collection<? extends T> nodes);

    public Collection<T> getNeighbor();
}

Maka Anda dapat memiliki grafik jenis khusus:

public class City extends Node<City>
{
    public void addNeighbor(City){...}

    public void addNeighbors(Collection<? extends City> nodes){...}

    public Collection<City> getNeighbor(){...}
}
nozebacle
sumber
Itu masih memungkinkan saya untuk membuat class Foo extends Node<City>tempat Foo tidak terkait dengan City.
Newacct
1
Tentu, dan apakah itu salah? Saya kira tidak. Kontrak dasar yang disediakan oleh Node <City> masih dihormati, hanya subkelas Foo Anda yang kurang berguna sejak Anda mulai bekerja dengan Foos tetapi mengeluarkan Kota dari ADT. Mungkin ada kasus penggunaan untuk ini, tetapi kemungkinan besar lebih sederhana dan lebih bermanfaat untuk hanya membuat parameter generik sama dengan subkelas. Tapi bagaimanapun, perancang memiliki pilihan itu.
mdma
@mdma: Saya setuju. Jadi apa gunanya menyediakan terikat, lebih dari sekadar class Node<T>?
Newacct
1
@nozebacle: Contoh Anda tidak menunjukkan bahwa "struktur khusus ini diperlukan". class Node<T>sepenuhnya konsisten dengan contoh Anda.
berita baru
1

Jika Anda melihat Enumkode sumber, ia memiliki yang berikut ini:

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {

    public final int compareTo(E o) {
        Enum<?> other = (Enum<?>)o;
        Enum<E> self = this;
        if (self.getClass() != other.getClass() && // optimization
            self.getDeclaringClass() != other.getDeclaringClass())
            throw new ClassCastException();
        return self.ordinal - other.ordinal;
    }

    @SuppressWarnings("unchecked")
    public final Class<E> getDeclaringClass() {
        Class<?> clazz = getClass();
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
    }

    public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    } 
}

Hal pertama yang pertama, apa E extends Enum<E>artinya? Itu berarti parameter tipe adalah sesuatu yang memanjang dari Enum, dan tidak diparametisasi dengan tipe mentah (itu diparametisasi dengan sendirinya).

Ini relevan jika Anda memiliki enum

public enum MyEnum {
    THING1,
    THING2;
}

yang, jika saya tahu benar, diterjemahkan ke

public final class MyEnum extends Enum<MyEnum> {
    public static final MyEnum THING1 = new MyEnum();
    public static final MyEnum THING2 = new MyEnum();
}

Jadi ini berarti bahwa MyEnum menerima metode berikut:

public final int compareTo(MyEnum o) {
    Enum<?> other = (Enum<?>)o;
    Enum<MyEnum> self = this;
    if (self.getClass() != other.getClass() && // optimization
        self.getDeclaringClass() != other.getDeclaringClass())
        throw new ClassCastException();
    return self.ordinal - other.ordinal;
}

Dan yang lebih penting,

    @SuppressWarnings("unchecked")
    public final Class<MyEnum> getDeclaringClass() {
        Class<?> clazz = getClass();
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class<MyEnum>)clazz : (Class<MyEnum>)zuper;
    }

Ini membuat getDeclaringClass()gips ke Class<T>objek yang tepat .

Contoh cara yang lebih jelas adalah yang saya jawab pada pertanyaan ini di mana Anda tidak dapat menghindari konstruk ini jika Anda ingin menentukan batasan umum.

EpicPandaForce
sumber
Tidak ada yang Anda perlihatkan compareToatau getDeclaringClassmembutuhkan extends Enum<E>batasan.
Newacct
0

Menurut wikipedia, pola ini disebut pola templat berulang yang menarik . Pada dasarnya, dengan menggunakan pola CRTP, kita dapat dengan mudah merujuk ke tipe subclass tanpa tipe casting, yang berarti dengan menggunakan pola, kita dapat meniru fungsi virtual.

Hanzhou Tang
sumber