Mengapa konstruktor enum tidak dapat mengakses bidang statis?

110

Mengapa konstruktor enum tidak dapat mengakses bidang dan metode statis? Ini benar-benar valid dengan kelas, tetapi tidak diperbolehkan dengan enum.

Apa yang saya coba lakukan adalah menyimpan contoh enum saya di Peta statis. Pertimbangkan kode contoh ini yang memungkinkan pencarian dengan singkatan:

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

Ini tidak akan berfungsi karena enum tidak mengizinkan referensi statis dalam konstruktornya. Namun itu berfungsi hanya menemukan jika diimplementasikan sebagai kelas:

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
}
Steve Kuo
sumber

Jawaban:

113

Konstruktor dipanggil sebelum semua bidang statis diinisialisasi, karena bidang statis (termasuk yang mewakili nilai enum) diinisialisasi dalam urutan tekstual, dan nilai enum selalu muncul sebelum bidang lainnya. Perhatikan bahwa dalam contoh kelas Anda, Anda belum menunjukkan tempat ABBREV_MAP diinisialisasi - jika setelah MINGGU, Anda akan mendapatkan pengecualian saat kelas diinisialisasi.

Ya, ini agak merepotkan dan mungkin bisa dirancang lebih baik.

Namun, jawaban yang biasa dalam pengalaman saya adalah memiliki static {}blok di akhir semua penginisialisasi statis, dan melakukan semua inisialisasi statis di sana, menggunakan EnumSet.allOf untuk mendapatkan semua nilai.

Jon Skeet
sumber
40
Jika Anda menambahkan kelas bertingkat, maka statika itu akan dijalankan pada waktu yang tepat.
Tom Hawtin - tackline
Ooh, bagus. Saya tidak memikirkan itu.
Jon Skeet
3
Sedikit yang aneh tetapi jika Anda memanggil metode statis dalam konstruktor enum yang mengembalikan nilai statis, ia akan dikompilasi dengan baik - tetapi nilai yang dikembalikannya akan menjadi nilai default untuk jenis itu (yaitu 0, 0.0, '\ u0000' atau null), meskipun Anda menyetelnya secara eksplisit (kecuali jika dinyatakan sebagai final). Tebak itu akan sulit ditangkap!
Mark Rhodes
2
pertanyaan spin-off cepat @JonSkeet: Ada alasan yang Anda gunakan, EnumSet.allOfbukan Enum.values()? Saya bertanya karena valuesini semacam metode hantu (tidak dapat melihat sumbernya Enum.class) dan saya tidak tahu kapan dibuat
Chirlo
1
@Chirlo Ada pertanyaan tentang itu. Tampaknya itu Enum.values()lebih cepat jika Anda berencana untuk mengulanginya dengan loop for yang ditingkatkan (karena mengembalikan array), tetapi sebagian besar tentang gaya dan kasus penggunaan. Mungkin lebih baik digunakan EnumSet.allOf()jika Anda ingin menulis kode yang ada di dokumentasi Java daripada hanya di spesifikasinya, tetapi banyak orang yang tampaknya sudah terbiasa Enum.values().
4castle
31

Kutipan dari JLS, bagian "Enum Body Declarations" :

Tanpa aturan ini, kode yang tampaknya wajar akan gagal pada waktu proses karena sirkularitas inisialisasi yang melekat pada jenis enum. (Lingkaran ada di semua kelas dengan bidang statis "diketik sendiri".) Berikut adalah contoh jenis kode yang akan gagal:

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

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

Inisialisasi statis dari jenis enum ini akan memunculkan NullPointerException karena variabel statis colorMap tidak diinisialisasi saat konstruktor untuk konstanta enum dijalankan. Pembatasan di atas memastikan bahwa kode tersebut tidak akan dikompilasi.

Perhatikan bahwa contoh dapat dengan mudah difaktor ulang agar berfungsi dengan baik:

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

Versi refactored jelas benar, karena inisialisasi statis terjadi dari atas ke bawah.

Phani
sumber
9

mungkin ini yang kamu inginkan

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);
    }
}
pengguna4767902
sumber
Menggunakan Collections.unmodifiableMap()adalah praktik yang sangat bagus di sini. +1
4castle
Persis apa yang saya cari. Saya juga suka melihat Collections.unmodifiableMap. Terima kasih!
LethalLima
6

Masalah diselesaikan melalui kelas bersarang. Kelebihan: lebih pendek dan juga lebih baik dengan konsumsi CPU. Kekurangan: satu kelas lagi dalam memori JVM.

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);
    }
Pavel Vlasov
sumber
1

Saat kelas dimuat di JVM, bidang statis diinisialisasi sesuai urutan kemunculannya dalam kode. Misalnya

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

Outputnya akan menjadi 0. Perhatikan bahwa inisialisasi test4 terjadi dalam proses inisialisasi statis dan selama waktu ini j belum diinisialisasi seperti yang muncul nanti. Sekarang jika kita mengganti urutan penginisialisasi statis sehingga j muncul sebelum test4. Outputnya adalah 6. Tetapi dalam kasus Enums, kita tidak dapat mengubah urutan bidang statis. Hal pertama dalam enum haruslah konstanta yang sebenarnya merupakan instance akhir statis dari jenis enum. Jadi, untuk enum, selalu dijamin bahwa bidang statis tidak akan diinisialisasi sebelum konstanta enum. Karena kita tidak dapat memberikan nilai yang masuk akal ke bidang statis untuk digunakan dalam konstruktor enum , tidak ada artinya mengaksesnya dalam konstruktor enum.

Hitesh
sumber