enum의 생성자가 정적 필드에 액세스 할 수없는 이유는 무엇입니까? Wednesday(“Wed”), Thursday(“Thu”), Friday(“Fri”), Saturday(“Sat”);

열거 형의 생성자가 정적 필드 및 메서드에 액세스 할 수없는 이유는 무엇입니까? 이것은 클래스에서 완벽하게 유효하지만 열거 형에서는 허용되지 않습니다.

내가하려는 것은 정적 맵에 열거 형 인스턴스를 저장하는 것입니다. 약어로 조회 할 수있는 다음 예제 코드를 고려하십시오.

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
}



답변

생성자는 정적 필드가 모두 초기화되기 전에 호출됩니다. 정적 필드 (열거 형 값을 나타내는 필드 포함)는 텍스트 순서로 초기화되고 열거 형 값은 항상 다른 필드 앞에 오기 때문입니다. 클래스 예제에서는 ABBREV_MAP가 초기화 된 위치를 표시하지 않았습니다. SUNDAY 이후 이면 클래스가 초기화 될 때 예외가 발생합니다.

예, 약간의 고통이며 아마도 더 잘 설계되었을 수 있습니다.

그러나 내 경험상 일반적인 대답 static {}은 모든 정적 이니셜 라이저 끝에 블록을두고 EnumSet.allOf 모든 값을 가져 오는 데 사용하여 모든 정적 초기화를 수행 하는 것입니다.


답변

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

열거 형 상수에 대한 생성자가 실행될 때 정적 변수 colorMap이 초기화되지 않기 때문에이 열거 형 유형의 정적 초기화는 NullPointerException을 throw 합니다. 위의 제한은 이러한 코드가 컴파일되지 않도록합니다.

예제는 제대로 작동하도록 쉽게 리팩토링 할 수 있습니다.

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

리팩토링 된 버전은 정적 초기화가 위에서 아래로 발생하므로 명확하게 정확합니다.


답변

아마도 이것은 당신이 원하는 것입니다

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


답변

중첩 된 클래스를 통해 문제가 해결되었습니다. 장점 : 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);
    }


답변

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이지만 Enum의 경우 정적 필드의 순서를 변경할 수 없습니다. enum의 첫 번째 것은 enum 유형의 실제 정적 최종 인스턴스 인 상수 여야합니다. 따라서 enum의 경우 항상 정적 필드가 enum 상수보다 먼저 초기화되지 않을 것임을 보장합니다. , enum 생성자에서 액세스하는 것은 의미가 없습니다.


답변