เหตุใดตัวสร้างของ enum จึงไม่สามารถเข้าถึงฟิลด์แบบคงที่ได้


110

เหตุใดตัวสร้างของ enum จึงไม่สามารถเข้าถึงฟิลด์และเมธอดแบบคงที่ได้? สิ่งนี้ใช้ได้กับคลาส แต่ไม่อนุญาตให้ใส่ enum

สิ่งที่ฉันพยายามทำคือจัดเก็บอินสแตนซ์ enum ของฉันไว้ในแผนที่แบบคงที่ พิจารณาโค้ดตัวอย่างนี้ซึ่งช่วยให้สามารถค้นหาได้โดยการย่อ:

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

สิ่งนี้จะไม่ทำงานเนื่องจาก enum ไม่อนุญาตให้มีการอ้างอิงแบบคงที่ในตัวสร้าง อย่างไรก็ตามมันใช้งานได้เพียงแค่ค้นหาว่าใช้งานเป็นคลาส:

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
}

คำตอบ:


113

ตัวสร้างถูกเรียกก่อนที่ฟิลด์สแตติกจะถูกเตรียมใช้งานทั้งหมดเนื่องจากฟิลด์สแตติก (รวมถึงฟิลด์ที่แสดงถึงค่า enum) จะเริ่มต้นตามลำดับข้อความและค่า enum จะมาก่อนฟิลด์อื่นเสมอ โปรดทราบว่าในตัวอย่างชั้นเรียนของคุณคุณไม่ได้แสดงตำแหน่งที่เริ่มต้น ABBREV_MAP - ถ้าหลังวันอาทิตย์คุณจะได้รับข้อยกเว้นเมื่อเริ่มต้นคลาส

ใช่มันค่อนข้างเจ็บและอาจได้รับการออกแบบให้ดีกว่านี้

อย่างไรก็ตามคำตอบตามปกติในประสบการณ์ของฉันคือการมีstatic {}บล็อกที่ส่วนท้ายของตัวเริ่มต้นแบบคงที่ทั้งหมดและทำการเริ่มต้นแบบคงที่ทั้งหมดที่นั่นโดยใช้EnumSet.allOf เพื่อรับค่าทั้งหมด


40
หากคุณเพิ่มคลาสที่ซ้อนกันสถิติของสิ่งนั้นจะเริ่มต้นในเวลาที่เหมาะสม
Tom Hawtin - แท

โอคนดี ฉันไม่ได้คิดอย่างนั้น
Jon Skeet

3
บิตของคี่ แต่ถ้าคุณเรียกใช้วิธีการแบบคงที่ในตัวสร้าง enum ซึ่งส่งคืนค่าคงที่มันจะคอมไพล์ได้ดี แต่ค่าที่ส่งกลับจะเป็นค่าเริ่มต้นสำหรับประเภทนั้น (เช่น 0, 0.0, '\ u0000' หรือ null) แม้ว่าคุณจะตั้งค่าไว้อย่างชัดเจน (เว้นแต่จะประกาศเป็นfinal) เดาว่าคงจับยาก!
Mark Rhodes

2
คำถามแยกด่วน @JonSkeet: เหตุผลใดที่คุณใช้EnumSet.allOfแทนEnum.values()? ฉันถามเพราะvaluesเป็นวิธีการหลอก (ไม่เห็นแหล่งที่มาEnum.class) และฉันไม่รู้ว่าเมื่อใดที่สร้างขึ้น
Chirlo

1
@Chirlo มี คำถามเกี่ยวกับเรื่องนั้น ดูเหมือนว่าEnum.values()จะเร็วกว่าถ้าคุณวางแผนที่จะทำซ้ำด้วยการปรับปรุงสำหรับลูป (เนื่องจากส่งคืนอาร์เรย์) แต่ส่วนใหญ่จะเกี่ยวกับสไตล์และกรณีการใช้งาน มันอาจจะดีกว่าEnumSet.allOf()ถ้าคุณต้องการเขียนโค้ดที่มีอยู่ในเอกสารของ Java แทนที่จะเป็นเพียงแค่ในรายละเอียด แต่หลาย ๆ คนดูเหมือนจะคุ้นเคยEnum.values()อยู่แล้ว
4castle

31

ข้อความจากJLS ส่วน "Enum Body Declarations" :

หากไม่มีกฎนี้ดูเหมือนว่าโค้ดที่สมเหตุสมผลจะล้มเหลวในขณะรันเนื่องจากการเริ่มต้นเป็นวงกลมที่มีอยู่ในประเภท enum (ความเป็นวงกลมมีอยู่ในคลาสใด ๆ ที่มีฟิลด์คงที่ "พิมพ์เอง") นี่คือตัวอย่างของรหัสประเภทที่จะล้มเหลว:

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

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

การเริ่มต้นแบบคงที่ของประเภท enum นี้จะทำให้NullPointerExceptionเกิดขึ้นเนื่องจาก colorMap ตัวแปรแบบคงที่ไม่ได้กำหนดค่าเริ่มต้นเมื่อตัวสร้างสำหรับค่าคงที่ enum ทำงาน ข้อ จำกัด ด้านบนช่วยให้มั่นใจได้ว่าโค้ดดังกล่าวจะไม่คอมไพล์

โปรดทราบว่าตัวอย่างสามารถปรับโครงสร้างใหม่ให้ทำงานได้อย่างถูกต้อง:

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

เวอร์ชัน refactored นั้นถูกต้องอย่างชัดเจนเนื่องจากการเริ่มต้นแบบคงที่เกิดขึ้นจากบนลงล่าง


9

บางทีนี่อาจเป็นสิ่งที่คุณต้องการ

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

1
การใช้Collections.unmodifiableMap()เป็นแนวทางปฏิบัติที่ดีมากที่นี่ +1
4castle

สิ่งที่ฉันกำลังมองหา ฉันชอบดู Collections.unmodifiableMap ด้วย ขอบคุณ!
LethalLima

6

ปัญหาได้รับการแก้ไขผ่านคลาสที่ซ้อนกัน ข้อดี: สั้นกว่าและดีกว่าด้วยการใช้ CPU จุดด้อย: อีกหนึ่งคลาสในหน่วยความจำ 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

เมื่อคลาสถูกโหลดใน JVM ฟิลด์สแตติกจะถูกเตรียมใช้งานตามลำดับที่ปรากฏในโค้ด สำหรับเช่น

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

ผลลัพธ์จะเป็น 0 โปรดทราบว่าการเริ่มต้น test4 จะเกิดขึ้นในกระบวนการเริ่มต้นแบบคงที่และในช่วงเวลานี้ j จะยังไม่เริ่มต้นตามที่ปรากฏในภายหลัง ตอนนี้ถ้าเราเปลี่ยนลำดับของตัวเริ่มต้นแบบคงที่ซึ่ง j มาก่อน test4 ผลลัพธ์จะเป็น 6 แต่ในกรณีของ Enums เราไม่สามารถเปลี่ยนลำดับของฟิลด์คงที่ได้ สิ่งแรกใน enum ต้องเป็นค่าคงที่ซึ่งเป็นอินสแตนซ์สุดท้ายแบบคงที่ของชนิด enum ดังนั้นสำหรับ enum จึงรับประกันได้เสมอว่าฟิลด์แบบคงที่จะไม่ถูกเริ่มต้นก่อนค่าคงที่ enum เนื่องจากเราไม่สามารถให้ค่าที่สมเหตุสมผลกับฟิลด์คงที่เพื่อใช้ในตัวสร้าง enum มันจะไม่มีความหมายที่จะเข้าถึงพวกมันใน enum constructor

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.