enum의 생성자가 정적 필드에 액세스 할 수없는 이유는 무엇입니까?


110

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

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

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

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

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

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


40
중첩 된 클래스를 추가하면 해당 정적이 적절한 시간에 초기화됩니다.
Tom Hawtin-tackline

오, 좋은 사람. 나는 그것을 생각하지 않았다.
Jon Skeet

3
조금 이상하지만 정적 값을 반환하는 열거 형 생성자에서 정적 메서드를 호출하면 제대로 컴파일되지만 반환되는 값은 해당 유형에 대한 기본 값이됩니다 (예 : 0, 0.0, '\ u0000'또는 null), 명시 적으로 설정하더라도 (으로 선언되지 않은 경우 final). 잡기 어려울 것 같아요!
Mark Rhodes

2
빠른 스핀 오프 질문 @JonSkeet : EnumSet.allOf대신 사용 하는 이유 는 Enum.values()무엇입니까? 나는 values일종의 유령 방법 이기 때문에 묻습니다 (에서 소스를 볼 수 없습니다 Enum.class) 그리고 그것이 언제 생성
되었는지 모르겠습니다

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

열거 형 상수에 대한 생성자가 실행될 때 정적 변수 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);
    }
}

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


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

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.