Java sabit değerleri neden genel tür parametrelerine sahip olmamalı?


152

Java numaralandırmaları harika. Jenerikler de öyle. Elbette hepimiz tür silme nedeniyle ikincisinin sınırlamalarını biliyoruz. Ama anlamadığım bir şey var, Neden böyle bir sıralama oluşturamıyorum:

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

Bu genel tip parametresi <T>daha sonra çeşitli yerlerde yararlı olabilir. Bir yöntem için genel bir tür parametresi düşünün:

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

Veya enum sınıfının kendisinde bile:

public T convert(Object o);

Daha somut örnek 1

Yukarıdaki örnek bazıları için fazla soyut görünebileceğinden, işte bunu neden yapmak istediğime dair daha gerçek hayattan bir örnek. Bu örnekte kullanmak istiyorum

  • Numaralandırmalar, çünkü o zaman sonlu bir özellik anahtarı kümesini numaralandırabilirim
  • Jenerikler, çünkü o zaman özellikleri depolamak için yöntem düzeyinde tür güvenliğine sahip olabilirim
public interface MyProperties {
     public <T> void put(MyEnum<T> key, T value);
     public <T> T get(MyEnum<T> key);
}

Daha somut örnek 2

Veri türleri numaram var:

public interface DataType<T> {}

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

Her bir enum değişmezi, açık bir şekilde genel türe göre ek özelliklere sahip olacaktır. <T> sahipken, aynı zamanda bir enum (değişmez, tekli, numaralandırılabilir vb.)

Soru:

Bunu kimse düşünmedi mi? Bu derleyiciyle ilgili bir sınırlama mı? " Enum " anahtar kelimesinin " , JVM için üretilen kodu temsil eden sözdizimsel şeker olarak uygulandığı bu sınırlamayı anlamıyorum.

Bunu bana kim açıklayabilir? Cevap vermeden önce şunu düşünün:

  • Genel türlerin silindiğini biliyorum :-)
  • Class nesnelerini kullanan geçici çözümler olduğunu biliyorum. Bunlar geçici çözümlerdir.
  • Genel türler, uygun olan her yerde derleyici tarafından üretilen tür yayınlarına neden olur (örneğin, convert () yöntemini çağırırken
  • <T> genel türü, numaralandırmada olacaktır. Bu nedenle, numaralandırmanın değişmez değerlerinin her birine bağlıdır. Dolayısıyla derleyici, aşağıdaki gibi bir şey yazarken hangi türün uygulanacağını bilirdi.String string = LITERAL1.convert(myObject); Integer integer = LITERAL2.convert(myObject);
  • Aynısı T getvalue()yöntemdeki genel tür parametresi için de geçerlidir . Derleyici, çağrı yaparken tür atama uygulayabilirString string = someClass.getValue(LITERAL1)

3
Ben de bu sınırlamayı anlamıyorum. Son zamanlarda, numaramın farklı "Karşılaştırılabilir" türler içerdiği ve jeneriklerle, bastırılması gereken uyarılar olmadan yalnızca aynı türlerin Karşılaştırılabilir türleri karşılaştırılabilir (çalışma zamanında uygun olanlar karşılaştırılsa bile). Hangi karşılaştırılabilir türün desteklendiğini belirtmek için numaralandırmada bir tür sınırı kullanarak bu uyarılardan kurtulabilirdim, ancak bunun yerine SuppressWarnings ek açıklamasını eklemek zorunda kaldım - bunun yolu yok!
CompareTo

5
(+1) Projemde bir tür güvenlik açığını kapatmaya çalışmanın tam ortasındayım, bu keyfi sınırlama yüzünden tamamen durdum. enumŞunu bir düşünün: Java 1.5'ten önce kullandığımız bir "typesafe enum" deyimine dönüştürün. Aniden, numaralandırma üyelerinizin parametrelendirilmesini sağlayabilirsiniz. Muhtemelen şimdi yapacağım şey bu.
Marko Topolnik

1
@EdwinDalorzo: Bir somut örnekle soru Güncelleme jOOQ bu geçmişte gayet kullanışlı olurdu.
Lukas Eder

2
@LukasEder Ne demek istediğini şimdi anlıyorum. Harika bir yeni özellik gibi görünüyor. Belki de bunu proje madeni para posta listesinde önermelisiniz. Numaralandırmalarda başka ilginç öneriler de görüyorum ama sizinki gibi değil.
Edwin Dalorzo

1
Tamamen katılıyorum. Jenerik olmayan Enum sakatlanır. 1 numaralı davanız da benim. Genel numaralandırmaya ihtiyacım olursa, JDK5'ten vazgeçerim ve onu eski Java 1.4 stilinde uygularım. Bu yaklaşımın ek bir faydası da var: Tüm sabitleri tek bir sınıfta veya hatta bir pakette bulundurmak zorunda değilim. Böylece, özellik bazında paket tarzı çok daha iyi başarılır. Bu, konfigürasyon benzeri "numaralandırmalar" için mükemmeldir - sabitler, mantıksal anlamlarına göre paketlere yayılır (ve hepsini görmek istersem, gösterilen tipte hieararchy var).
Tomáš Záluský

Yanıtlar:


51

Bu şimdi JEP-301 Enhanced Enums olarak tartışılmaktadır . JEP'de verilen örnek tam olarak aradığım şey:

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

Ne yazık ki, JEP hala önemli sorunlarla mücadele ediyor: http://mail.openjdk.java.net/pipermail/amber-spec-experts/2017-May/000041.html


2
Aralık 2018 itibariyle, JEP 301 civarında yine bazı yaşam belirtileri vardı , ancak bu tartışmayı gözden geçirmek, sorunların hala çözülmekten uzak olduğunu açıkça gösteriyor.
Pont

11

Cevap soruda:

tür silme nedeniyle

Bağımsız değişken türü silindiğinden, bu iki yöntemden hiçbiri mümkün değildir.

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

Ancak bu yöntemleri gerçekleştirmek için numaranızı şu şekilde oluşturabilirsiniz:

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

    private Class<?> clazz;

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

    ...

}

2
Silme, derleme zamanında gerçekleşir. Ancak derleyici, tür kontrolleri için genel tür bilgilerini kullanabilir. Ve sonra genel türü yazı tiplerine "dönüştürür". Soruyu yeniden ifade edeceğim
Lukas Eder

2
Tamamen takip ettiğimden emin değilim. Al public T convert(Object);. Bu yöntemin örneğin bir dizi farklı türü <T> olarak daraltabileceğini tahmin ediyorum, örneğin bir String. String nesnesinin yapısı çalışma zamanıdır - derleyicinin yaptığı hiçbir şey yoktur. Bu nedenle, String.class gibi çalışma zamanı türünü bilmeniz gerekir. Yoksa bir şey mi kaçırıyorum?
Martin Algesten

6
<T> genel türünün enumda (veya üretilen sınıfta) olduğu gerçeğini kaçırdığınızı düşünüyorum. Numaralamanın tek örnekleri, tümü sabit bir genel tür bağlama sağlayan değişmez değerleridir. Dolayısıyla, public T convert (Object) 'de T ile ilgili bir belirsizlik yoktur;
Lukas Eder

2
Aha! Anladım. Benden daha
zekisin

1
Sanırım diğer her şeye benzer şekilde değerlendirilen bir sıralama için sınıfa geliyor. Java'da her şey sabit görünse de öyle değil. public final static String FOO;Statik bir blokla düşünün static { FOO = "bar"; }- ayrıca sabitler derleme zamanında bilinse bile değerlendirilir.
Martin Algesten

5

Çünkü yapamazsınız. Ciddi anlamda. Bu, dil spesifikasyonuna eklenebilir. Olmadı. Biraz karmaşıklık katacaktır. Maliyet avantajı, yüksek bir öncelik olmadığı anlamına gelir.

Güncelleme: Şu anda JEP 301: Geliştirilmiş Enums altındaki dile ekleniyor .


Komplikasyonları detaylandırır mısınız?
Mr_and_Mrs_D

5
Bunu birkaç gündür düşünüyordum ve hala herhangi bir komplikasyon görmüyorum.
Marko Topolnik

4

ENUM'da işe yaramayan başka yöntemler de var. Ne MyEnum.values()dönecekti?

Peki ya MyEnum.valueOf(String name)?

ValueOf için, derleyicinin aşağıdaki gibi genel bir yöntem yapabileceğini düşünüyorsanız

public static MyEnum valueOf (Dize adı);

diye adlandırmak için MyEnum<String> myStringEnum = MyEnum.value("some string property")bu da işe yaramaz. Mesela ararsan ne olur MyEnum<Int> myIntEnum = MyEnum.<Int>value("some string property")? Bu yöntemin doğru çalışması için uygulanması mümkün değildir, örneğin MyEnum.<Int>value("some double property"), tür silme nedeniyle bir istisna attığınızda veya böyle çağırdığınızda null döndürmek mümkün değildir .


2
Neden çalışmıyorlar? Sadece joker karakterler kullanırlardı ...: MyEnum<?>[] values()veMyEnum<?> valueOf(...)
Lukas Eder

1
Ama o zaman MyEnum<Int> blabla = valueOf("some double property");türler uyumlu olmadığı için bu şekilde atama yapamazsınız. Ayrıca bu durumda null almak istiyorsunuz çünkü çift özellik adı için mevcut olmayan MyEnum <Int> döndürmek istiyorsunuz ve silme nedeniyle bu yöntemin düzgün çalışmasını sağlayamazsınız.
user1944408

Ayrıca, değerler () arasında döngü yaparsanız, MyEnum <?> Kullanmanız gerekir ki bu genellikle istediğiniz şey değildir, çünkü örneğin yalnızca Int özellikleriniz üzerinden döngü yapamazsınız. Ayrıca kaçınmak istediğiniz çok sayıda döküm yapmanız gerekir, her tür için farklı numaralandırmalar oluşturmanızı veya örneklerle kendi sınıfınızı oluşturmanızı öneririm ...
user1944408

Sanırım ikisine birden sahip olamazsın. Genellikle kendi "enum" uygulamalarıma başvururum. Bu sadece Enumbir çok başka kullanışlı özelliğe sahip ve bu da isteğe bağlı ve çok faydalı olacak ...
Lukas Eder

0

Açıkçası bu her şeyden çok bir problem arayışında bir çözüm gibi görünüyor.

Java numaralandırmasının tüm amacı, benzer özellikleri paylaşan tür örneklerinin bir numaralandırmasını, karşılaştırılabilir String veya Integer temsillerinin ötesinde tutarlılık ve zenginlik sağlayacak şekilde modellemektir.

Bir metin kitabı sıralaması örneğini ele alalım. Bu çok kullanışlı veya tutarlı değil:

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

Neden farklı gezegenlerimin farklı genel tür dönüşümlere sahip olmasını isteyeyim? Hangi sorunu çözer? Dil anlamını karmaşıklaştırmayı haklı çıkarıyor mu? İhtiyacım varsa, bu davranış, bunu başarmak için en iyi araç mı?

Ek olarak, karmaşık dönüşümleri nasıl yönetirsiniz?

Örneğin

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

String IntegerAdı veya sırayı sağlamak için yeterince kolaydır . Ancak jenerikler herhangi bir tür tedarik etmenize izin verir. Dönüşümü nasıl yönetirsiniz MyComplexClass? Şimdi, derleyiciyi genel numaralandırmalara sağlanabilecek sınırlı bir tür alt kümesi olduğunu bilmeye zorlayarak ve çok sayıda programcıdan kaçmış gibi görünen kavrama (Generics) ek karışıklık getirerek iki yapıyı karıştırıyorsunuz.


18
Yararlı olmayacağı birkaç örnek düşünmek, hiçbir zaman yararlı olmayacağına dair korkunç bir argümandır.
Elias Vasylenko

1
Örnekler bu noktayı desteklemektedir. Numaralandırma örnekleri, türün alt sınıflarıdır (güzel ve basit) ve jenerikler dahil olmak üzere, çok belirsiz bir fayda için karmaşıklık şartları olan bir solucan kutusu. Bana olumsuz oy verecekseniz, aynı şeyi çok fazla kelimeyle söylemeyen aşağıdan Tom Hawtin'e de olumsuz oy vermelisiniz
nsfyn55

1
@ nsfyn55 Enums, Java'nın en karmaşık, en büyülü dil özelliklerinden biridir. Örneğin, statik yöntemleri otomatik olarak üreten tek bir dil özelliği yoktur. Artı, her numaralama türü zaten olan bir genel türde bir örneği.
Marko Topolnik

1
@ nsfyn55 Tasarım amacı, enum üyelerini güçlü ve esnek hale getirmekti, bu nedenle özel örnek yöntemleri ve hatta değiştirilebilir örnek değişkenleri gibi gelişmiş özellikleri destekliyorlar. Her üye için bireysel olarak özelleştirilmiş davranışa sahip olacak şekilde tasarlanmıştır, böylece karmaşık kullanım senaryolarına aktif ortak çalışanlar olarak katılabilirler. Hepsinden önemlisi, Java numaralandırması genel numaralandırma deyimi türünü güvenli hale getirmek için tasarlanmıştır , ancak tür parametrelerinin eksikliği maalesef bu en değerli hedefin gerisinde kalmaktadır. Bunun çok özel bir nedeni olduğuna oldukça ikna oldum .
Marko Topolnik

1
Contravariance'den bahsettiğinizi fark etmedim ... Java onu destekliyor, sadece kullanım-sitesi, bu da onu biraz hantal hale getiriyor. 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); }Hangi tipin tüketildiğini ve hangisinin üretildiğini her seferinde manuel olarak hesaplamalı ve buna göre sınırları belirlemeliyiz.
Marko Topolnik

-2

Çünkü "enum", numaralandırmanın kısaltmasıdır. Bu, kodu daha iyi okunabilir hale getirmek için sıra sayılarının yerine duran bir adlandırılmış sabitler kümesidir.

Bir tür parametreleştirilmiş sabitin amaçlanan anlamının ne olabileceğini anlamıyorum.


1
In java.lang.String: public static final Comparator<String> CASE_INSENSITIVE_ORDER. Şimdi görüyorsun? :-)
Lukas Eder

4
Bir tür parametreleştirilmiş sabitin anlamının ne olabileceğini görmediğini söyledin. Bu yüzden size tip parametreli bir sabit gösterdim (bir fonksiyon göstericisi değil).
Lukas Eder

Tabii ki hayır, çünkü java dili bir işlev işaretçisi gibi bir şey bilmiyor. Yine de sabit tip parametreleştirilmemiştir, ancak tipidir. Ve tür parametresinin kendisi sabittir, bir tür değişkeni değildir.
Ingo

Ancak enum sözdizimi yalnızca sözdizimsel şekerdir. Altında, tıpkı şuna benziyorlar CASE_INSENSITIVE_ORDER... Eğer Comparator<T>bir enum olsaydı , neden herhangi bir ilişkili sınır ile değişmez değerlere sahip değilsin <T>?
Lukas Eder

Karşılaştırıcı <T> bir numaralandırma olsaydı, gerçekten de her türlü tuhaf şeye sahip olabilirdik. Belki Tamsayı <T> gibi bir şey. Ama değil.
Ingo

-3

Bence temelde Enums örneklenemez

JVM izin verdiyse, T sınıfını nereye ayarlarsınız?

Numaralandırma, her zaman aynı olması veya en azından dinamik olarak değişmeyeceği varsayılan verilerdir.

yeni MyEnum <> ()?

Yine de aşağıdaki yaklaşım faydalı olabilir

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

8
Gibi emin değilim setO()bir de yöntemle enum. Sabitleri sabitler olarak görüyorum ve bana göre bu değişmez anlamına geliyor . Yani mümkün olsa bile yapmam.
Martin Algesten

2
Numaralandırmalar, derleyici tarafından oluşturulan kodda belirtilir. Her değişmez bilgi, özel kuruculara çağrı yapılarak oluşturulur. Bu nedenle, genel türü derleme zamanında kurucuya geçirme şansı olacaktır.
Lukas Eder

2
Numaralandırmalardaki ayarlayıcılar tamamen normaldir. Özellikle bir tekli oluşturmak için numaralandırmayı kullanıyorsanız (Joshua Bloch tarafından Öğe 3 Etkili Java)
Preston
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.