Genel statik son alanları olan bir sınıfa göre Java numaralandırmasının avantajı nedir?


152

C # ile çok aşinayım ama Java ile daha çok çalışmaya başladım. Java'daki numaralandırmaların temelde C #'dakilere eşdeğer olduğunu öğrenmeyi bekliyordum, ancak görünüşe göre durum böyle değil. Başlangıçta, Java numaralandırmalarının çok avantajlı görünen birden fazla veri parçası içerebileceğini öğrenmekten heyecan duydum ( http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html ). Bununla birlikte, o zamandan beri, C # 'da önemsiz olan, bir enum öğesine kolayca belirli bir değer atama yeteneği ve sonuç olarak bir tamsayıyı makul miktarda çaba harcamadan bir numaralandırmaya dönüştürme yeteneği gibi birçok özellik eksik buldum ( yani tamsayı değerini eşleşen Java Enum değerine dönüştür .

Öyleyse sorum şu: Bir grup public static final alanı olan bir sınıf üzerinde Java numaralandırmalarının herhangi bir faydası var mı? Yoksa sadece daha kompakt bir sözdizimi mi sağlıyor?

DÜZENLEME: Daha açık konuşayım. Java numaralandırmalarının , aynı türden bir grup genel statik son alan içeren bir sınıfa göre faydası nedir ? Örneğin, ilk bağlantıdaki gezegen örneğinde, bu genel sabitlere sahip bir sınıfa göre bir numaralamanın avantajı nedir:

public static final Planet MERCURY = new Planet(3.303e+23, 2.4397e6);
public static final Planet VENUS = new Planet(4.869e+24, 6.0518e6);
public static final Planet EARTH = new Planet(5.976e+24, 6.37814e6);
public static final Planet MARS = new Planet(6.421e+23, 3.3972e6);
public static final Planet JUPITER = new Planet(1.9e+27, 7.1492e7);
public static final Planet SATURN = new Planet(5.688e+26, 6.0268e7);
public static final Planet URANUS = new Planet(8.686e+25, 2.5559e7);
public static final Planet NEPTUNE = new Planet(1.024e+26, 2.4746e7);

Anladığım kadarıyla, kazablanka'nın cevabı bunu tatmin eden tek cevap.


4
@Bohemian: OP, yalnızca public static finaldeğerleri girilebilen ve mutlaka ints olmayan alanlardan bahsettiği için yinelenmemiş olabilir .
kazablanka

1
@Shahzeb Pek. Açıkça, dize sabitleri yerine numaralandırmaları kullanmak SON DERECE iyi bir fikirdir ve tavsiye edilenden daha fazlasıdır. Tür güvenliği, statik yardımcı program işlevlerine ihtiyaç duymaz, vb. Bunun yerine dizeleri kullanmak için kesinlikle bir neden yoktur.
Voo

1
@Voo Evet anlaşmazlıklar olacağını biliyordum. Ve işte 49 saniyede bir. Numaralandırmalar harikadır (ve onları çok seviyorum ve çok sık kullanıyorum) ama sabit ilan ederken veya kullanırken ne tür bir güvenliğe ihtiyacınız var? String değişmezi için bir sabit bildirmeniz gerektiğinde her seferinde enum oluşturmak aşırı bir işlemdir.
Shahzeb

4
@Shahzeb Eğer tek bir değişkeniniz varsa, kesinlikle bir dizge kullanın, burada pek bir şey olamaz (tek bir değer parametre olarak anlamsızdır). Ancak sabitten bahsettiğimize dikkat edin S, bu yüzden şimdi muhtemelen onları işlevlere vb. Aktarmaktan bahsediyoruz. Tip-güvenliğe ihtiyacımız var mı? Pekala, hayır, ama o zaman çoğu insan c-stili "her şeyin void*" iyi bir stil olduğunu düşünür ve hataları durdurabilir (özellikle birden fazla enum / string parametresi geçerse!). Ayrıca sabitleri kendi isim alanlarına vb. Koyar. Bunun aksine, sadece düz değişkenlere sahip olmanın gerçek bir avantajı yoktur.
Voo

3
@Bohemian: Nasıl olduğunu anlamıyorum. S ile inttip güvenliği yoktur çünkü herhangi bir değeri geçebilir. Öte yandan, yazılan nesneler, tür güvenliği açısından numaralandırmalardan farklı değildir.
kazablanka

Yanıtlar:


78

Teknik olarak bir kişi numaralandırmaları bir grup sabit yazılı olan bir sınıf olarak görebilir ve bu aslında enum sabitlerinin dahili olarak nasıl uygulandığıdır. enumBununla birlikte, kullanmak size, aksi takdirde kendiniz uygulamanız gereken yararlı yöntemler ( Enum javadoc ) sağlar, örneğin Enum.valueOf.


15
.values()Değerler listesi üzerinde yineleme yapmak da var .
h3xStream

1
Pek tatmin edici olmasa da bu doğru cevap gibi görünüyor. Kanımca, Java'nın yalnızca daha kompakt sözdizimi ve Enum yöntemlerine erişim için numaralandırmalar için destek eklemesi zar zor değerliydi.
Craig W

7
@Craig içgüdüleriniz haklı - bu çok kötü bir cevap çünkü enums amacını tamamen kaçırdı. Nedenlerinin bir kısmı için soru altındaki yorumlarıma bakın.
Bohemian

1
@Bohemian: Enums "amacını" kaçırmadım - onları her zaman kullanıyorum. Yukarıdaki yorumunuza cevabımı görün.
kazablanka

1
Enum.valuesOf yöntemi iki kez çağrılırsa aynı nesneyi döndürür mü?
Emre Aktürk

108
  1. Tip güvenliği ve değer güvenliği.
  2. Garantili tek ton.
  3. Yöntemleri tanımlama ve geçersiz kılma yeteneği.
  4. Değerleri switchifade caseifadelerinde nitelendirmeden kullanabilme.
  5. Değerlerin yerleşik olarak sıralanması ordinal().
  6. Değere göre değil isme göre serileştirme, bir dereceye kadar geleceğe hazır olma imkanı sunar.
  7. EnumSetve EnumMapsınıflar.

19
Tüm bunları söyledikten sonra, bir Enum'a her kod koyduğumda pişman oldum.
Marki Lorne

4
Neden pişman oldun? Ben hiç yapmadım…
glglgl

2
@glglgl Uygulamaya özel kodu, gerçekten ait olmadığını hissettiğim bir yere koydu, bu gerçekten sadece bir dizi değeri tanımlıyordu. Tekrar yapmam gerekirse switch, an'ı kullanmanın orijinal motivasyonu olan sayısız ifadeden birine dahil ederdim Enum.
Lorne Markisi

72

Bunları switchifadelerde kullanma yeteneğinden kimse bahsetmedi ; Bunu da içine atacağım.

Bu, keyfi olarak karmaşık numaralandırmaların instanceof, potansiyel olarak kafa karıştırıcı ifsıralar veya dize olmayan / int anahtarlama değerleri kullanılmadan temiz bir şekilde kullanılmasına izin verir . Kanonik örnek, bir durum makinesidir.


Her neyse, numaralandırmalardan statik alanlara karşı herhangi bir faydadan bahsetmediniz, statik alanlara sahip switch deyimlerinde yeterince tür kullanabilirsiniz. Op İhtiyacı gerçek fonksiyoneller veya Perfomance farklar
Genaut

@Genaut Faydası, numaralandırmaların bir dizeden veya int'ten daha fazla işlevselliğe sahip olmasıdır. Soru, sağladığım farklılıklar hakkındaydı. OP, numaralandırmaların ne olduğunun zaten farkında ve bunu 4,5 yıl önce yayınladığımda başka hiç kimse switch ifadelerinden bahsetmemişti ve en azından birkaç kişi yeni bilgi sağladığını gördü ¯_ (ツ) _ / ¯
Dave Newton

44

Birincil avantaj, tip güvenliğidir. Bir dizi sabitle, aynı iç türdeki herhangi bir değer kullanılabilir ve bu da hatalara neden olabilir. Bir numaralandırmayla yalnızca geçerli değerler kullanılabilir.

Örneğin

public static final int SIZE_SMALL  = 1;
public static final int SIZE_MEDIUM = 2;
public static final int SIZE_LARGE  = 3;

public void setSize(int newSize) { ... }

obj.setSize(15); // Compiles but likely to fail later

vs

public enum Size { SMALL, MEDIUM, LARGE };

public void setSize(Size s) { ... }

obj.setSize( ? ); // Can't even express the above example with an enum

3
sınıflar da güvenli ...: / (statik alanın konteyner sınıfının türünde olduğu varsayılarak)
h3xStream

Yine de geçersiz bir değer iletebilirsiniz, ancak bu, tespit edilmesi çok daha kolay olan bir derleme zamanı hatası olacaktır.
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz

2
setSize(null)İkinci örneğinizi arayabilirsiniz , ancak muhtemelen ilk örnekteki hatadan çok daha erken başarısız olacaktır.
Jeffrey

42

Daha az kafa karışıklığı var. Örneğin alın Font. İstediğiniz ismini Font, boyutunu ve stilini ( new Font(String, int, int)) alan bir kurucuya sahiptir . Bugüne kadar stilin mi yoksa boyutun mu önce geldiğini hatırlayamıyorum. Eğer Fontbir kullanmıştı enumonun farklı stiller tümü için ( PLAIN, BOLD, ITALIC, BOLD_ITALIC), onun yapıcı şekilde görünecektir Font(String, Style, int)karışıklığı önler. Maalesef, sınıf oluşturulduğunda enumortalıkta Fontyoktu ve Java'nın ters uyumluluğu sürdürmesi gerektiğinden, bu belirsizlikten her zaman rahatsız olacağız.

Tabii ki, bu sadece sabitler enumyerine bir kullanmak için bir argümandır public static final. Numaralandırmalar aynı zamanda tekli kullanım için mükemmeldir ve daha sonra özelleştirmeye izin verirken varsayılan davranışı uygular (IE strateji modeli ). İkincisine bir örnek java.nio.file's'dir OpenOptionve StandardOpenOption: bir geliştirici kendi standart dışı olanını yaratmak OpenOptionisterse, yapabilir.


Aynı numaralandırmalardan ikisi istenmişse 'Yazı Tipi' durumunuz hala belirsizdir. Bu sorunun kanonik cevabı, birçok dilin Adlandırılmış Parametreler dediği şeydir . Bu veya daha iyi IDE yöntemi imza desteği.
aaaaaa

1
@aaaaaa Bir kurucunun varargs enumkullanmadan aynı şeyi iki tane alacağı veya Setrastgele bir sayıda almak için a alacağı pek çok durum görmedim .
Jeffrey

@aaaaaa Adlandırılmış parametrelerle ilgili ana sorun, uygulama ayrıntılarına (parametrenin adı) dayanır. Arayüz yapabilirim interface Foo { public void bar(String baz); }. Birisi çağıran bir sınıf yapar bar: someFoo.bar(baz = "hello");. İmzasını olarak Foo::bardeğiştiriyorum public void bar(String foobar). Şimdi, arayan kişinin someFoohala çalışmasını istiyorsa kodunu değiştirmesi gerekecektir.
Jeffrey

Ardışık sıralama türlerini de gördüğümü hatırlamıyorum, ancak ortak olanın DAY_OF_WEEK veya benzeri bir şey olabileceğini düşündüm. Ve arayüz hakkında iyi bir nokta - bunu düşünmemiştim. Şahsen ben bu sorunu, daha güçlü IDE desteği gerektiren adlandırılmamış parametrelerin neden olduğu yaygın belirsizliğin üstesinden gelirdim. Ancak bunun bir yargı çağrısı olduğunu ve API değişikliklerini bozmanın ciddi olarak dikkate alınması gereken bir şey olduğunu anlıyorum.
aaaaaa

26

Burada pek çok iyi yanıt vardır, ancak hiçbiri özellikle numaralandırmalar için Collection API sınıflarının / arabirimlerinin son derece optimize edilmiş uygulamalarının olduğundan bahsetmez :

Bu numaralandırmaya özgü sınıflar yalnızca Enumörnekleri EnumMapkabul eder Enum(yalnızca anahtarlar olarak kabul edilir) ve mümkün olduğunda, uygulamalarında kompakt gösterime ve bit manipülasyonuna geri döner.

Ne anlama geliyor?

Tipimiz Enum64 öğeden fazla değilse (gerçek hayat Enumörneklerinin çoğu buna uygun olacaktır), uygulamalar öğeleri tek bir longdeğerde depolar, Enumsöz konusu her örnek bu 64 bit uzunluğundaki bir parçayla ilişkilendirilir long. Bir elemanına bir eleman eklemek EnumSet, sadece uygun biti 1'e ayarlamaktır, onu kaldırmak sadece o biti 0'a ayarlamaktır. Bir elemanın içinde olup olmadığını Settest etmek sadece bir bit maskesi testidir! Şimdi bunun için onları sevmelisin Enum!


1
Bu ikisini önceden biliyordum ama senin What does this mean?bölümünden çok şey öğrendim . 64 boyutunda bir uygulama bölünmesi olduğunu biliyordum, ama aslında nedenini bilmiyordum
Christopher Rucinski

15

misal:

public class CurrencyDenom {
   public static final int PENNY = 1;
 public static final int NICKLE = 5;
 public static final int DIME = 10;
public static final int QUARTER = 25;}

Java Sabitlerinin Sınırlandırılması

1) Tip Güvenliği Yok : Her şeyden önce tip güvenli değildir; int için herhangi bir geçerli int değeri atayabilirsiniz, örneğin, bu değeri temsil edecek jeton olmasa da 99.

2) Anlamlı Baskı Yok : Bu sabitlerden herhangi birinin baskı değeri, anlamlı madeni para adı yerine sayısal değerini basacaktır, örneğin NICKLE yazdırdığınızda "NICKLE" yerine "5" basacaktır.

3) Ad alanı yok : currencyDenom sabitine erişmek için, yalnızca PENNY kullanmak yerine, örneğin CurrencyDenom.PENNY gibi sınıf adının önekini almamız gerekir, ancak bu, JDK 1.5'te statik içe aktarma kullanılarak da sağlanabilir.

Numaralandırmanın avantajı

1) Java'daki numaralandırmalar tür açısından güvenlidir ve kendi ad alanına sahiptir. Bu, numaranızın örneğin aşağıdaki örnekte "Para Birimi" türüne sahip olacağı ve Enum Sabitlerinde belirtilenden başka bir değer atayamayacağınız anlamına gelir.

public enum Currency {PENNY, NICKLE, DIME, QUARTER};

Currency coin = Currency.PENNY; coin = 1; //compilation error

2) Java'daki Enum, sınıf veya arabirim gibi başvuru türüdür ve Java Enum türünün bir sonraki örneğinde gösterildiği gibi, java Enum içinde C ve C ++ 'daki Enum'dan daha güçlü kılan yapıcı, yöntemler ve değişkenler tanımlayabilirsiniz.

3) Oluşturma sırasında enum sabitlerinin değerlerini aşağıdaki örnekte gösterildiği gibi belirtebilirsiniz: genel enum Para birimi {PENNY (1), NICKLE (5), DIME (10), QUARTER (25)}; Ancak bunun çalışması için bir üye değişkeni ve kurucu tanımlamanız gerekir çünkü PENNY (1) aslında int değerini kabul eden bir kurucu çağırıyor, aşağıdaki örneğe bakın.

public enum Currency {
    PENNY(1), NICKLE(5), DIME(10), QUARTER(25);
    private int value;

    private Currency(int value) {
            this.value = value;
    }
}; 

Referans: https://javarevisited.blogspot.com/2011/08/enum-in-java-example-tutorial.html


11

Daha önce fark ettiğiniz gibi numaralandırmanın ilk yararı sözdizimi basitliğidir. Ancak numaralandırmaların ana noktası, varsayılan olarak bir aralık oluşturan ve tür ve değer güvenlik kontrolleri aracılığıyla daha kapsamlı kod analizi yapmaya yardımcı olan iyi bilinen bir sabit kümesi sağlamaktır.

Numaralandırmanın bu nitelikleri hem bir programcıya hem de bir derleyiciye yardımcı olur. Örneğin, bir tamsayı kabul eden bir işlev gördüğünüzü varsayalım. Bu tam sayı ne anlama gelebilir? Ne tür değerler aktarabilirsiniz? Gerçekten hemen bilmiyorsun. Ancak enum kabul eden bir işlev görürseniz, geçirebileceğiniz tüm olası değerleri çok iyi bilirsiniz.

Derleyici için, numaralandırmalar bir dizi değerin belirlenmesine yardımcı olur ve numaralandırma üyelerine özel değerler atamazsanız, bunlar 0 ve üstü aralığındadır. Bu, tür güvenlik kontrolleri ve daha fazlasıyla koddaki hataları otomatik olarak izlemeye yardımcı olur. Örneğin, derleyici, switch deyiminizdeki tüm olası enum değerlerini işlemediğiniz konusunda sizi uyarabilir (yani, case'e sahip değilseniz defaultve N enum değerinden yalnızca birini işlediğinizde). Ayrıca, rasgele bir tamsayıyı enum'a dönüştürdüğünüzde de sizi uyarır çünkü enum'un değer aralığı tamsayılardan daha azdır ve bu da işlevde bir tamsayıyı gerçekten kabul etmeyen hataları tetikleyebilir. Ayrıca, değerler 0 ve üstü olduğunda anahtar için bir atlama tablosu oluşturmak daha kolay hale gelir.

Bu yalnızca Java için değil, sıkı bir tür denetimi olan diğer diller için de geçerlidir. C, C ++, D, C # iyi örneklerdir.


4

Bir enum özel kurucular ile, tüm değerler aynı tipte veya bir alt tipi, implictly nihaidir, kullanmakta tüm değerlerini elde edebilirsiniz values(), onun alır name()veya ordinal()değerini veya numarası veya adıyla bir enum bakabilirsiniz.

Ayrıca alt sınıfları da tanımlayabilirsiniz (kavramsal olarak son olsa bile, başka bir şekilde yapamayacağınız bir şey)

enum Runner implements Runnable {
    HI {
       public void run() {
           System.out.println("Hello");
       }
    }, BYE {
       public void run() {
           System.out.println("Sayonara");
       }
       public String toString() {
           return "good-bye";
       }
    }
 }

 class MYRunner extends Runner // won't compile.

4

enum Faydaları:

  1. Numaralandırmalar tür açısından güvenlidir, statik alanlar değildir
  2. Sonlu sayıda değer vardır (var olmayan enum değerini geçirmek mümkün değildir. Statik sınıf alanlarınız varsa, bu hatayı yapabilirsiniz)
  3. Her numaralandırma birden çok özelliğe (alanlar / alıcılar) sahip olabilir - kapsülleme. Ayrıca bazı basit yöntemler: YEAR.toSeconds () veya benzeri. Şunu karşılaştırın: Colors.RED.getHex () ile Colors.toHex (Colors.RED)

"bir enum öğesine kolayca belirli bir değer atama yeteneği gibi"

enum EnumX{
  VAL_1(1),
  VAL_200(200);
  public final int certainValue;
  private X(int certainValue){this.certainValue = certainValue;}
}

"ve sonuç olarak bir tamsayıyı makul miktarda çaba harcamadan bir numaralandırmaya dönüştürme yeteneği" int'i enum'a dönüştüren ve bunu yapan bir yöntem ekleyin. Haritalama java numaralandırmasını içeren statik HashMap <Integer, EnumX> eklemeniz yeterlidir .

Ord = VAL_200.ordinal () öğesini gerçekten val_200'e dönüştürmek istiyorsanız, şunu kullanın: EnumX.values ​​() [ord]


3

Diğer bir önemli fark, java derleyicisinin ilkel türler ve Stringstatic final alanlarını değişmez değerler olarak ele almasıdır. Bu, bu sabitlerin satır içi hale geldiği anlamına gelir. Önişlemciye benzer . Bu SO sorusuna bakın . Numaralandırma ile durum böyle değildir.C/C++ #define



2

En büyük avantajı, tekillerin yazılması kolay ve iş parçacığı açısından güvenlidir:

public enum EasySingleton{
    INSTANCE;
}

ve

/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
     private volatile DoubleCheckedLockingSingleton INSTANCE;

     private DoubleCheckedLockingSingleton(){}

     public DoubleCheckedLockingSingleton getInstance(){
         if(INSTANCE == null){
            synchronized(DoubleCheckedLockingSingleton.class){
                //double checking Singleton instance
                if(INSTANCE == null){
                    INSTANCE = new DoubleCheckedLockingSingleton();
                }
            }
         }
         return INSTANCE;
     }
}

ikisi de benzerdir ve Serileştirmeyi kendi başlarına gerçekleştirerek

//readResolve to prevent another instance of Singleton
    private Object readResolve(){
        return INSTANCE;
    }

Daha


0

Bence enumolamaz final, çünkü başlık altında derleyici her enumgirdi için alt sınıflar oluşturur .

Kaynaktan daha fazla bilgi


Dahili olarak, nihai değildirler, çünkü - sizin de söylediğiniz gibi - dahili olarak alt sınıflara ayrılabilirler. Ancak ne yazık ki, bunları kendi başınıza alt sınıflara alamazsınız, örneğin kendi değerleriyle genişletmek için.
glglgl

0

Burada yayınlanan numaralandırmaların birçok avantajı vardır ve şu anda soruda sorulduğu gibi bu tür numaralandırmalar oluşturuyorum. Ama 5-6 alanlı bir numaram var.

enum Planet{
EARTH(1000000, 312312321,31232131, "some text", "", 12),
....
other planets
....

Bu tür durumlarda, numaralandırmada birden fazla alanınız olduğunda, kurucu ve gözü görmeniz gerektiğinden hangi değerin hangi alana ait olduğunu anlamak çok zordur.

static finalSabitleri olan sınıf ve Builderbu tür nesneleri oluşturmak için desen kullanmak onu daha okunaklı hale getirir. Ancak, ihtiyacınız olursa bir numaralandırma kullanmanın diğer tüm avantajlarını kaybedersiniz. Bu tür sınıfların bir dezavantajı, Planetnesneleri manuel olarak aşağıdakilere eklemeniz gerektiğidir list/set.Planets.

İşe values()yarayacağı ve gelecekte switchya EnumSetda EnumMapgelecekte kullanıp kullanmayacağınızı asla bilemeyeceğiniz için , bu tür bir sınıf yerine hala numaralandırmayı tercih ediyorum :)


0

Ana neden: Enums, diğer yanıtların verdiği tüm nedenlerden ötürü, parametrelerin anlamsal anlamının derleme zamanında net ve güçlü bir şekilde yazılmış olduğu iyi yapılandırılmış kod yazmanıza yardımcı olur.

Pro quo: Kutudan çıkan Java'da, Enum'un üye dizisi nihaidir. Bu normalde güvenliğe ve teste değer vermeye yardımcı olduğu için iyidir, ancak bazı durumlarda bir dezavantaj olabilir, örneğin mevcut temel kodu bir kitaplıktan genişletiyorsanız. Buna karşılık, aynı veriler statik alanlara sahip bir sınıfta bulunuyorsa, çalışma zamanında bu sınıfın yeni örneklerini kolayca ekleyebilirsiniz (bunları, bu sınıf için sahip olduğunuz herhangi bir Yinelenebilir'e eklemek için kod yazmanız da gerekebilir). Ancak Enums'ün bu davranışı değiştirilebilir: yansıma kullanarak çalışma zamanında yeni üyeler ekleyebilir veya mevcut üyeleri değiştirebilirsiniz, ancak bu muhtemelen yalnızca alternatifin olmadığı özel durumlarda yapılmalıdır: yani, karmaşık bir çözümdür ve beklenmedik sorunlar üretebilir, cevabımı görJava'da çalışma zamanında numaralandırma öğeleri ekleyip kaldırabilir miyim ?

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.