Mengapa Java enum literals seharusnya tidak dapat memiliki parameter tipe generik?

148

Enum Jawa sangat bagus. Begitu juga obat generik. Tentu saja kita semua tahu keterbatasan dari yang terakhir karena jenis penghapusan. Tetapi ada satu hal yang saya tidak mengerti, Mengapa saya tidak bisa membuat enum seperti ini:

public enum MyEnum<T> {
    LITERAL1<String>,
    LITERAL2<Integer>,
    LITERAL3<Object>;
}

Parameter tipe generik ini <T>pada gilirannya dapat berguna di berbagai tempat. Bayangkan parameter tipe umum ke suatu metode:

public <T> T getValue(MyEnum<T> param);

Atau bahkan di kelas enum itu sendiri:

public T convert(Object o);

Contoh lebih konkret # 1

Karena contoh di atas mungkin tampak terlalu abstrak bagi sebagian orang, inilah contoh kehidupan nyata mengapa saya ingin melakukan ini. Dalam contoh ini saya ingin menggunakan

  • Enum, karena dengan begitu saya dapat menyebutkan satu set kunci properti yang terbatas
  • Generik, karena dengan begitu saya dapat memiliki metode-level-safety untuk menyimpan properti
public interface MyProperties {
     public <T> void put(MyEnum<T> key, T value);
     public <T> T get(MyEnum<T> key);
}

Contoh lebih konkret # 2

Saya memiliki enumerasi tipe data:

public interface DataType<T> {}

public enum SQLDataType<T> implements DataType<T> {
    TINYINT<Byte>,
    SMALLINT<Short>,
    INT<Integer>,
    BIGINT<Long>,
    CLOB<String>,
    VARCHAR<String>,
    ...
}

Setiap enum literal jelas akan memiliki properti tambahan berdasarkan tipe generik <T>, sementara pada saat yang sama, menjadi enum (tidak berubah, tunggal, enumerable, dll.)

Pertanyaan:

Apakah tidak ada yang memikirkan hal ini? Apakah ini keterbatasan terkait kompiler? Mempertimbangkan fakta, bahwa kata kunci " enum " diimplementasikan sebagai gula sintaksis, mewakili kode yang dihasilkan ke JVM, saya tidak mengerti batasan ini.

Siapa yang bisa menjelaskan hal ini kepada saya? Sebelum Anda menjawab, pertimbangkan ini:

  • Saya tahu jenis generik dihapus :-)
  • Saya tahu ada solusi menggunakan objek Class. Mereka sedang mencari solusi.
  • Jenis generik menghasilkan cast yang dihasilkan oleh kompiler dimanapun berlaku (misalnya saat memanggil metode convert ()
  • Jenis generik <T> akan berada di enum. Karena itu ia diikat oleh masing-masing literal enum. Oleh karena itu kompiler akan tahu, tipe apa yang diterapkan ketika menulis sesuatu sepertiString string = LITERAL1.convert(myObject); Integer integer = LITERAL2.convert(myObject);
  • Hal yang sama berlaku untuk parameter tipe generik dalam T getvalue()metode ini. Kompiler dapat menerapkan tipe casting saat memanggilString string = someClass.getValue(LITERAL1)
Lukas Eder
sumber
3
Saya juga tidak mengerti batasan ini. Saya menemukan ini baru-baru ini di mana enum saya berisi berbagai jenis "Sebanding", dan dengan obat generik, hanya jenis Sebanding dari jenis yang sama dapat dibandingkan tanpa peringatan yang perlu ditekan (meskipun pada saat runtime yang tepat akan dibandingkan). Saya bisa menyingkirkan peringatan ini dengan menggunakan jenis yang terikat di enum untuk menentukan jenis yang sebanding yang didukung, tetapi sebaliknya saya harus menambahkan anotasi SuppressWarnings - tidak ada jalan lain! Karena compareTo memang melemparkan pengecualian cast kelas, tidak apa-apa kurasa, tapi masih ...
MetroidFan2002
5
(+1) Saya baru saja mencoba untuk menutup celah keamanan jenis dalam proyek saya, berhenti mati oleh pembatasan sewenang-wenang ini. Coba pertimbangkan ini: enumubah idiom menjadi "typesafe enum" yang kami gunakan sebelum Java 1.5. Tiba-tiba, Anda dapat membuat parameter anggota enum Anda. Mungkin itulah yang akan saya lakukan sekarang.
Marko Topolnik
1
@ EdwinDalorzo: Memperbarui pertanyaan dengan contoh nyata dari jOOQ , di mana ini akan sangat berguna di masa lalu.
Lukas Eder
2
@LukasEder Saya mengerti maksud Anda sekarang. Sepertinya fitur baru yang keren. Mungkin Anda harus menyarankannya di milis koin proyek saya melihat proposal menarik lainnya ada pada enum, tetapi tidak seperti proposal Anda.
Edwin Dalorzo
1
Setuju. Enum tanpa obat generik lumpuh. Kasing Anda # 1 juga milikku. Jika saya memerlukan enum generik, saya menyerah JDK5 dan mengimplementasikannya dalam gaya Java 1.4. Pendekatan ini juga memiliki manfaat tambahan : Saya tidak dipaksa untuk memiliki semua konstanta dalam satu kelas atau bahkan paket. Dengan demikian, gaya paket demi fitur dicapai jauh lebih baik. Ini sempurna hanya untuk "enums" seperti konfigurasi - konstanta disebarkan dalam paket sesuai dengan makna logisnya (dan jika saya ingin melihat semuanya, saya memiliki tipe hieararchy yang diperlihatkan).
Tomáš Záluský

Jawaban:

50

Ini sekarang sedang dibahas pada JEP-301 Enhanced Enums . Contoh yang diberikan dalam JEP adalah, yang persis apa yang saya cari:

enum Argument<X> { // declares generic enum
   STRING<String>(String.class), 
   INTEGER<Integer>(Integer.class), ... ;

   Class<X> clazz;

   Argument(Class<X> clazz) { this.clazz = clazz; }

   Class<X> getClazz() { return clazz; }
}

Class<String> cs = Argument.STRING.getClazz(); //uses sharper typing of enum constant

Sayangnya, JEP masih berjuang dengan masalah signifikan: http://mail.openjdk.java.net/pipermail/amber-spec-experts/2017-May/000041.html

Lukas Eder
sumber
2
Pada Desember 2018, ada beberapa tanda-tanda kehidupan di sekitar JEP 301 lagi , tetapi membaca sekilas diskusi itu menjelaskan bahwa masalahnya masih jauh dari terpecahkan.
Pont
11

Jawabannya ada di pertanyaan:

karena tipe erasure

Tidak satu pun dari kedua metode ini yang mungkin, karena tipe argumen dihapus.

public <T> T getValue(MyEnum<T> param);
public T convert(Object);

Namun, untuk merealisasikan metode tersebut, Anda dapat membuat enum sebagai:

public enum MyEnum {
    LITERAL1(String.class),
    LITERAL2(Integer.class),
    LITERAL3(Object.class);

    private Class<?> clazz;

    private MyEnum(Class<?> clazz) {
      this.clazz = clazz;
    }

    ...

}
Martin Algesten
sumber
2
Ya, penghapusan terjadi pada waktu kompilasi. Tetapi kompiler dapat menggunakan informasi tipe umum untuk pemeriksaan tipe. Dan kemudian "mengubah" tipe generik untuk mengetik gips. Saya akan ulangi pertanyaannya
Lukas Eder
2
Tidak yakin saya sepenuhnya mengikuti. Ambil public T convert(Object);. Saya menduga metode ini misalnya dapat mempersempit sekelompok tipe yang berbeda menjadi <T>, yang misalnya adalah sebuah String. Konstruksi objek String adalah runtime - tidak ada yang dikompilasi oleh kompiler. Karenanya Anda perlu mengetahui tipe runtime, seperti String.class. Atau apakah saya melewatkan sesuatu?
Martin Algesten
6
Saya pikir Anda melewatkan fakta bahwa tipe generik <T> ada di enum (atau kelas yang dihasilkannya). Satu-satunya contoh enum adalah literalnya, yang semuanya menyediakan jenis generik yang konstan. Oleh karena itu tidak ada ambiguitas tentang T di T konversi publik (Object);
Lukas Eder
2
Aha! Mengerti. Anda lebih pintar dari saya :)
Martin Algesten
1
Saya kira itu turun ke kelas untuk enum dievaluasi dengan cara yang mirip dengan yang lainnya. Di java, meskipun hal tampak konstan, mereka tidak. Pertimbangkan public final static String FOO;dengan blok statis static { FOO = "bar"; }- konstanta juga dievaluasi bahkan ketika diketahui pada waktu kompilasi.
Martin Algesten
5

Karena kamu tidak bisa. Serius. Itu bisa ditambahkan ke spesifikasi bahasa. Belum. Ini akan menambah kompleksitas. Manfaat biaya itu berarti itu bukan prioritas tinggi.

Pembaruan: Saat ini sedang ditambahkan ke bahasa di bawah JEP 301: Enhanced Enums .

Tom Hawtin - tackline
sumber
Bisakah Anda menguraikan komplikasinya?
Mr_and_Mrs_D
4
Saya telah memikirkan hal ini selama beberapa hari sekarang, dan saya masih tidak melihat adanya komplikasi.
Marko Topolnik
4

Ada metode lain di ENUM yang tidak akan berfungsi. Apa yang akanMyEnum.values() kembali?

Bagaimana dengan MyEnum.valueOf(String name) ?

Untuk valueOf jika Anda berpikir bahwa kompiler dapat membuat metode generik seperti

public static MyEnum valueOf (Nama string);

untuk menyebutnya seperti MyEnum<String> myStringEnum = MyEnum.value("some string property"), itu tidak akan berhasil. Misalnya bagaimana jika Anda menelepon MyEnum<Int> myIntEnum = MyEnum.<Int>value("some string property")? Tidak mungkin untuk mengimplementasikan metode itu agar berfungsi dengan benar, misalnya untuk melempar pengecualian atau mengembalikan nol ketika Anda memanggilnya MyEnum.<Int>value("some double property")karena jenis penghapusan.

pengguna1944408
sumber
2
Kenapa mereka tidak bekerja? Mereka cukup menggunakan wildcard ...: MyEnum<?>[] values()danMyEnum<?> valueOf(...)
Lukas Eder
1
Tetapi kemudian Anda tidak dapat melakukan tugas seperti ini MyEnum<Int> blabla = valueOf("some double property");karena jenisnya tidak kompatibel. Juga Anda ingin dalam hal itu menjadi nol karena Anda ingin mengembalikan MyEnum <Int> yang tidak ada untuk nama properti ganda dan Anda tidak dapat membuat metode itu berfungsi dengan baik karena penghapusan.
user1944408
Juga jika Anda mengulang nilai-nilai () Anda harus menggunakan MyEnum <?> Yang bukan yang Anda inginkan biasanya karena misalnya Anda tidak dapat mengulang hanya melalui properti Int Anda. Anda juga perlu melakukan banyak casting yang ingin Anda hindari, saya sarankan untuk membuat enum yang berbeda untuk setiap tipe atau membuat kelas Anda sendiri dengan instance ...
user1944408
Yah, saya kira Anda tidak dapat memiliki keduanya. Biasanya, saya menggunakan implementasi "enum" saya sendiri. Hanya saja Enummemiliki banyak fitur berguna lainnya, dan yang ini akan opsional dan juga sangat berguna ...
Lukas Eder
0

Terus terang ini sepertinya lebih dari solusi dalam mencari masalah daripada apa pun.

Seluruh tujuan java enum adalah untuk memodelkan enumerasi instance type yang berbagi properti serupa dengan cara yang memberikan konsistensi dan kekayaan di luar representasi String atau Integer yang sebanding.

Ambil contoh enum buku teks. Ini tidak terlalu berguna atau konsisten:

public enum Planet<T>{
    Earth<Planet>,
    Venus<String>,
    Mars<Long>
    ...etc.
}

Mengapa saya ingin planet saya yang berbeda memiliki konversi tipe generik yang berbeda? Masalah apa yang dipecahkannya? Apakah itu membenarkan rumitnya semantik bahasa? Jika saya perlu perilaku ini merupakan alat terbaik untuk mencapainya?

Selain itu, bagaimana Anda mengelola konversi yang rumit?

misalnya

public enum BadIdea<T>{
   INSTANCE1<Long>,
   INSTANCE2<MyComplexClass>;
}

Cukup mudah dengan String Integermemasukkan nama atau urutan. Tetapi obat generik memungkinkan Anda memasok jenis apa pun. Bagaimana Anda mengelola konversi MyComplexClass? Sekarang Anda membangun dua konstruksi dengan memaksa kompiler untuk mengetahui bahwa ada subset terbatas dari tipe yang dapat dipasok ke enum generik dan memperkenalkan kebingungan tambahan pada konsep (Generics) yang sudah tampak menghindari banyak programmer.

nsfyn55
sumber
17
Memikirkan beberapa contoh di mana itu tidak akan berguna adalah argumen yang mengerikan bahwa itu tidak akan pernah berguna.
Elias Vasylenko
1
Contoh-contohnya mendukung intinya. Contoh enum adalah subclass dari jenis (bagus dan sederhana) dan yang termasuk obat generik adalah kaleng cacing itu syarat kompleksitas untuk manfaat yang sangat jelas. Jika Anda akan menurunkan saya, maka Anda juga harus menurunkan Tom Hawtin di bawah ini yang mengatakan hal yang sama dengan tidak banyak kata
nsfyn55
1
@ nsfyn55 Enum adalah salah satu fitur bahasa Jawa yang paling rumit dan paling ajaib. Tidak ada fitur bahasa lain yang menghasilkan metode statis secara otomatis, misalnya. Plus, setiap tipe enum sudah merupakan turunan dari tipe generik.
Marko Topolnik
1
@ nsfyn55 Tujuan desain adalah untuk membuat anggota enum kuat dan fleksibel, itulah sebabnya mereka mendukung fitur-fitur canggih seperti metode instance kustom dan bahkan variabel instance yang dapat diubah. Mereka dirancang untuk memiliki perilaku yang dikhususkan untuk setiap anggota secara individual sehingga mereka dapat berpartisipasi sebagai kolaborator aktif dalam skenario penggunaan yang kompleks. Yang terpenting , Java enum dirancang untuk membuat idiom tipe enumerasi umum aman , tetapi tidak adanya parameter tipe sayangnya membuatnya kurang dari tujuan yang paling dihargai. Saya cukup yakin bahwa ada alasan yang sangat spesifik untuk itu.
Marko Topolnik
1
Saya tidak memperhatikan penyebutan contravariance Anda ... Java memang mendukungnya, hanya itu menggunakan situs, yang membuatnya agak berat. interface Converter<IN, OUT> { OUT convert(IN in); } <E> Set<E> convertListToSet(List<E> in, Converter<? super List<E>, ? extends Set<E>> converter) { return converter.convert(in); }Kita harus bekerja secara manual setiap kali jenis yang dikonsumsi dan yang diproduksi, dan menentukan batas sesuai.
Marko Topolnik
-2

Karena "enum" adalah singkatan untuk enumerasi. Itu hanya satu set konstanta bernama yang berdiri di tempat nomor urut untuk membuat kode lebih mudah dibaca.

Saya tidak melihat apa arti yang dimaksud dari konstanta tipe-parameter bisa.

Ingo
sumber
1
Dalam java.lang.String: public static final Comparator<String> CASE_INSENSITIVE_ORDER. Kamu lihat sekarang :-)
Lukas Eder
4
Anda bilang Anda tidak melihat apa arti dari konstanta type-parameterised. Jadi saya menunjukkan Anda konstanta type-parameterised (bukan pointer fungsi).
Lukas Eder
Tentu saja tidak, karena bahasa java tidak tahu apa itu pointer fungsi. Namun, bukan konstanta tipe parameter, tetapi tipe. Dan parameter tipe itu sendiri konstan, bukan variabel tipe.
Ingo
Tetapi sintaks enum hanyalah gula sintaksis. Di bawahnya, mereka persis seperti CASE_INSENSITIVE_ORDER... Jika Comparator<T>itu enum, mengapa tidak memiliki literal dengan ikatan terkait <T>?
Lukas Eder
Jika Pembanding <T> adalah enum, maka memang kita bisa memiliki segala macam hal aneh. Mungkin sesuatu seperti Integer <T>. Tapi ternyata tidak.
Ingo
-3

Saya pikir karena pada dasarnya Enums tidak dapat dipasang

Di mana Anda mengatur kelas T, jika JVM mengizinkan Anda untuk melakukannya?

Enumerasi adalah data yang seharusnya selalu sama, atau setidaknya, bahwa itu tidak akan berubah secara dinamis.

MyEnum baru <> ()?

Tetap saja pendekatan berikut ini mungkin bermanfaat

public enum MyEnum{

    LITERAL1("s"),
    LITERAL2("a"),
    LITERAL3(2);

    private Object o;

    private MyEnum(Object o) {
        this.o = o;
    }

    public Object getO() {
        return o;
    }

    public void setO(Object o) {
        this.o = o;
    }   
}
capsula
sumber
8
Tidak yakin saya suka setO()metode ini pada enum. Saya menganggap konstanta enum dan bagi saya itu menyiratkan kekekalan . Jadi, bahkan jika itu mungkin, saya tidak akan melakukannya.
Martin Algesten
2
Enum di-instanciated dalam kode yang dihasilkan oleh kompiler. Setiap literal sebenarnya dibuat dengan memanggil konstruktor pribadi. Oleh karena itu, akan ada kesempatan untuk meneruskan tipe generik ke konstruktor pada waktu kompilasi.
Lukas Eder
2
Seter pada enum sepenuhnya normal. Terutama jika Anda menggunakan enum untuk membuat singleton (Item 3 Effective Java oleh Joshua Bloch)
Preston