Enum'un kurucusu neden statik alanlara erişemiyor?


110

Enum'un kurucusu neden statik alanlara ve yöntemlere erişemiyor? Bu, bir sınıf için mükemmel bir şekilde geçerlidir, ancak bir enum ile izin verilmez.

Yapmaya çalıştığım şey, numaralandırma örneklerimi statik bir Haritada depolamak. Kısaltmaya göre aramaya izin veren şu örnek kodu düşünün:

public enum Day {
    Sunday("Sun"), Monday("Mon"), Tuesday("Tue"), Wednesday("Wed"), Thursday("Thu"), Friday("Fri"), Saturday("Sat");

    private final String abbreviation;

    private static final Map<String, Day> ABBREV_MAP = new HashMap<String, Day>();

    private Day(String abbreviation) {
        this.abbreviation = abbreviation;
        ABBREV_MAP.put(abbreviation, this);  // Not valid
    }

    public String getAbbreviation() {
        return abbreviation;
    }

    public static Day getByAbbreviation(String abbreviation) {
        return ABBREV_MAP.get(abbreviation);
    }
}

Bu, enum yapıcısında statik referanslara izin vermediğinden çalışmaz. Ancak, bir sınıf olarak uygulanıp uygulanmadığını bulmak için çalışır:

public static final Day SUNDAY = new Day("Sunday", "Sun");
private Day(String name, String abbreviation) {
    this.name = name;
    this.abbreviation = abbreviation;
    ABBREV_MAP.put(abbreviation, this);  // Valid
}

Yanıtlar:


113

Yapıcı, statik alanların tümü başlatılmadan önce çağrılır, çünkü statik alanlar (enum değerlerini temsil edenler dahil) metin sırasına göre başlatılır ve enum değerleri her zaman diğer alanlardan önce gelir. O ise - ABBREV_MAP başlatılır nerede sınıf örneğinde sen gösterilmez var Not sonra PAZAR sınıf başlatıldığında, bir istisna elde edersiniz.

Evet, biraz acı verici ve muhtemelen daha iyi tasarlanmış olabilirdi.

Bununla birlikte, deneyimlerime göre olağan cevap static {}, tüm statik başlatıcıların sonunda bir bloğa sahip olmak EnumSet.allOf ve tüm değerleri elde etmek için kullanarak tüm statik başlatmayı orada yapmaktır .


40
İç içe geçmiş bir sınıf eklerseniz, bunun statiği uygun bir zamanda başlatılacaktır.
Tom Hawtin - tackline

Ooh, güzel şey. Bunu düşünmemiştim.
Jon Skeet

3
Garip bir bittir, ancak bir enum kurucusunda statik bir değer döndüren statik bir yöntem çağırırsanız, iyi derler - ancak döndürdüğü değer bu tür için varsayılan değer olacaktır (yani 0, 0.0, '\ u0000' veya null), açıkça ayarlasanız bile (olarak bildirilmediği sürece final). Sanırım bu yakalanması zor olacak!
Mark Rhodes

2
hızlı spin-off soru @JonSkeet: EnumSet.allOfBunun yerine kullanmanızın herhangi bir nedeni var mı Enum.values()? Soruyorum çünkü valuesbir çeşit hayali yöntem (kaynağı göremiyorum Enum.class) ve ne zaman oluşturulduğunu bilmiyorum
Chirlo

1
@Chirlo Bununla ilgili bir soru var. Enum.values()Geliştirilmiş bir for döngüsü ile (bir dizi döndürdüğü için) onları yinelemeyi planlıyorsanız, bu daha hızlı görünüyor , ancak çoğunlukla stil ve kullanım durumu ile ilgili. Yalnızca EnumSet.allOf()spesifikasyonlar yerine Java'nın belgelerinde bulunan kodu yazmak istiyorsanız kullanmak muhtemelen daha iyidir , ancak çoğu insan Enum.values()yine de aşina görünüyor .
4castle

31

JLS'den alıntı , "Gövde Bildirimleri Enum" bölümü :

Bu kural olmadan, görünüşe göre makul kod, enum türlerinde bulunan başlatma döngüselliği nedeniyle çalışma zamanında başarısız olur. ("Kendi kendine yazılan" statik alana sahip herhangi bir sınıfta döngüsellik vardır.) Başarısız olacak kod türünün bir örneği aşağıda verilmiştir:

enum Color {
    RED, GREEN, BLUE;
    static final Map<String,Color> colorMap = new HashMap<String,Color>();

    Color() {
       colorMap.put(toString(), this);
    }
}

Bu enum türünün statik olarak başlatılması, bir NullPointerException oluşturacaktır çünkü statik değişken colorMap, enum sabitleri için oluşturucular çalıştığında başlatılmamıştır. Yukarıdaki kısıtlama, bu tür bir kodun derlenmemesini sağlar.

Düzgün çalışması için örneğin kolayca yeniden düzenlenebileceğini unutmayın:

enum Color {
    RED, GREEN, BLUE;
    static final Map<String,Color> colorMap = new HashMap<String,Color>();

    static {
        for (Color c : Color.values())
            colorMap.put(c.toString(), c);
    }
}

Statik başlatma yukarıdan aşağıya doğru gerçekleştiğinden, yeniden düzenlenen sürüm açıkça doğrudur.


9

belki istediğin budur

public enum Day {
    Sunday("Sun"), 
    Monday("Mon"), 
    Tuesday("Tue"), 
    Wednesday("Wed"), 
    Thursday("Thu"), 
    Friday("Fri"), 
    Saturday("Sat");

    private static final Map<String, Day> ELEMENTS;

    static {
        Map<String, Day> elements = new HashMap<String, Day>();
        for (Day value : values()) {
            elements.put(value.element(), value);
        }
        ELEMENTS = Collections.unmodifiableMap(elements);
    }

    private final String abbr;

    Day(String abbr) {
        this.abbr = abbr;
    }

    public String element() {
        return this.abbr;
    }

    public static Day elementOf(String abbr) {
        return ELEMENTS.get(abbr);
    }
}

Kullanmak Collections.unmodifiableMap()burada çok iyi bir uygulamadır. +1
4castle

Tam olarak aradığım şey. Ayrıca Collections.unmodifiableMap'i görmeyi de seviyorum. Teşekkür ederim!
LethalLima

6

Sorun, iç içe geçmiş bir sınıf aracılığıyla çözüldü. Artıları: daha kısa ve ayrıca CPU tüketimiyle daha iyi. Eksileri: JVM belleğinde bir sınıf daha.

enum Day {

    private static final class Helper {
        static Map<String,Day> ABBR_TO_ENUM = new HashMap<>();
    }

    Day(String abbr) {
        this.abbr = abbr;
        Helper.ABBR_TO_ENUM.put(abbr, this);

    }

    public static Day getByAbbreviation(String abbr) {
        return Helper.ABBR_TO_ENUM.get(abbr);
    }

1

JVM'ye bir sınıf yüklendiğinde, statik alanlar kodda göründükleri sırayla başlatılır. Örneğin

public class Test4 {
        private static final Test4 test4 = new Test4();
        private static int j = 6;
        Test4() {
            System.out.println(j);
        }
        private static void test() {
        }
        public static void main(String[] args) {
            Test4.test();
        }
    }

Çıkış 0 olacaktır. Test4 başlatmanın statik başlatma sürecinde gerçekleştiğini ve bu süre zarfında j'nin daha sonra göründüğü gibi henüz başlatılmadığını unutmayın. Şimdi, statik başlatıcıların sırasını değiştirirsek, j test4'ten önce gelir. Çıktı 6 olacaktır, ancak Enums durumunda statik alanların sırasını değiştiremeyiz. Numaralandırmadaki ilk şey, enum türünün aslında statik son örnekleri olan sabitler olmalıdır.Bu nedenle, numaralandırmalar için statik alanların enum sabitlerinden önce başlatılmayacağı her zaman garantilidir. , bunlara enum yapıcısında erişmek anlamsız olacaktır.

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.