Tại sao hàm tạo của enum không thể truy cập các trường tĩnh?


110

Tại sao hàm tạo của enum không thể truy cập các trường và phương thức tĩnh? Điều này hoàn toàn hợp lệ với một lớp, nhưng không được phép với một enum.

Những gì tôi đang cố gắng làm là lưu trữ các phiên bản enum của tôi trong một Bản đồ tĩnh. Hãy xem xét mã ví dụ này cho phép tra cứu theo cách viết tắt:

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

Điều này sẽ không hoạt động vì enum không cho phép các tham chiếu tĩnh trong phương thức khởi tạo của nó. Tuy nhiên, nó hoạt động chỉ cần tìm nếu được triển khai dưới dạng một lớp:

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
}

Câu trả lời:


113

Hàm tạo được gọi trước khi tất cả các trường tĩnh được khởi tạo, bởi vì các trường tĩnh (bao gồm cả những trường đại diện cho giá trị enum) được khởi tạo theo thứ tự văn bản và giá trị enum luôn đứng trước các trường khác. Lưu ý rằng trong ví dụ về lớp của bạn, bạn chưa hiển thị nơi ABBREV_MAP được khởi tạo - nếu đó là sau SUNDAY, bạn sẽ nhận được một ngoại lệ khi lớp được khởi tạo.

Vâng, đó là một chút khó khăn và có thể đã được thiết kế tốt hơn.

Tuy nhiên, câu trả lời thông thường theo kinh nghiệm của tôi là có một static {}khối ở cuối tất cả các trình khởi tạo tĩnh và thực hiện tất cả các khởi tạo tĩnh ở đó, sử dụng EnumSet.allOf để lấy tất cả các giá trị.


40
Nếu bạn thêm một lớp lồng nhau, thì các lớp tĩnh của lớp đó sẽ được khởi tạo vào một thời điểm thích hợp.
Tom Hawtin - tackline

Ồ, một cái hay đấy. Tôi đã không nghĩ về điều đó.
Jon Skeet

3
Bit của một số lẻ nhưng nếu bạn gọi một phương thức tĩnh trong một phương thức khởi tạo enum trả về giá trị tĩnh thì nó sẽ biên dịch tốt - nhưng giá trị mà nó trả về sẽ là giá trị mặc định cho kiểu đó (tức là 0, 0.0, '\ u0000' hoặc null), ngay cả khi bạn đặt nó một cách rõ ràng (trừ khi nó được khai báo là final). Đoán rằng đó sẽ là một trong những khó khăn để bắt!
Mark Rhodes

2
câu hỏi xoay nhanh @JonSkeet: Bất kỳ lý do gì mà bạn sử dụng EnumSet.allOfthay thế Enum.values()? Tôi hỏi vì valueslà loại một phương pháp ma (không thể nhìn thấy nguồn trong Enum.class) và tôi không biết khi nào tạo ra nó
Chirlo

1
@Chirlo Có một câu hỏi về điều đó. Có vẻ như Enum.values()nhanh hơn nếu bạn định lặp lại chúng bằng vòng lặp for nâng cao (vì nó trả về một mảng), nhưng chủ yếu là về kiểu và trường hợp sử dụng. Có lẽ tốt hơn nên sử dụng EnumSet.allOf()nếu bạn muốn viết mã tồn tại trong tài liệu của Java thay vì chỉ trong thông số kỹ thuật, nhưng nhiều người dường như đã quen thuộc với nó Enum.values().
4castle

31

Trích dẫn từ JLS, phần "Enum Body Decl Tuyên bố" :

Nếu không có quy tắc này, mã rõ ràng hợp lý sẽ không thành công tại thời điểm chạy do tính tuần hoàn khởi tạo vốn có trong các kiểu enum. (Một sự tuần hoàn tồn tại trong bất kỳ lớp nào có trường tĩnh "tự gõ".) Dưới đây là một ví dụ về loại mã sẽ không thành công:

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

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

Khởi tạo tĩnh kiểu enum này sẽ ném ra một NullPointerException vì biến static colorMap chưa được khởi tạo khi các hàm tạo cho hằng số enum chạy. Hạn chế ở trên đảm bảo rằng mã như vậy sẽ không biên dịch.

Lưu ý rằng bạn có thể dễ dàng cấu trúc lại ví dụ để hoạt động bình thường:

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

Phiên bản đã cấu trúc lại rõ ràng là chính xác, vì quá trình khởi tạo tĩnh diễn ra từ trên xuống dưới.


9

có lẽ đây là những gì bạn muốn

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

Sử dụng Collections.unmodifiableMap()là một thực hành rất tốt ở đây. +1
4 lâu đài

Chính xác những gì tôi đang tìm kiếm. Tôi cũng thích xem Collections.unmodifiableMap. Cảm ơn bạn!
LethalLima

6

Vấn đề được giải quyết thông qua một lớp lồng nhau. Ưu điểm: nó ngắn hơn và cũng tốt hơn nhờ mức tiêu thụ CPU. Nhược điểm: thêm một lớp trong bộ nhớ 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);
    }

1

Khi một lớp được tải trong JVM thì các trường tĩnh được khởi tạo theo thứ tự mà chúng xuất hiện trong mã. Ví dụ

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ết quả đầu ra sẽ là 0. Lưu ý rằng quá trình khởi tạo test4 diễn ra trong quá trình khởi tạo tĩnh và trong thời gian này j vẫn chưa được khởi tạo vì nó xuất hiện sau này. Bây giờ nếu chúng ta chuyển thứ tự của các bộ khởi tạo tĩnh sao cho j đến trước test4. Kết quả đầu ra sẽ là 6. Nhưng trong trường hợp Enums, chúng ta không thể thay đổi thứ tự của các trường tĩnh. Điều đầu tiên trong enum phải là các hằng thực sự là các trường hợp cuối cùng tĩnh của kiểu enum. Do đó, enum luôn đảm bảo rằng các trường tĩnh sẽ không được khởi tạo trước hằng số enum. , sẽ là vô nghĩa nếu truy cập chúng trong hàm tạo enum.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.