Menu

자바 열거형(enum): 이름이 있는 상수의 고정된 집합

자바 enum이 무엇인지, 어떻게 선언하는지, 필드와 메서드를 추가하는 법, switch에서 분기하는 법, 그리고 enum이 잔뜩 쌓인 int나 String 상수보다 나은 이유를 알아봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

이름이 있는 값들의 고정된 집합

어떤 값들은 작고 알려진 목록 중 하나로서만 의미가 있습니다. 요일, 카드 한 벌의 무늬, 주문이 가질 수 있는 상태 등이죠. 이런 것들을 int 코드(0은 대기 중, 1은 발송됨)나 문자열("PENDING", "SHIPPED")로 모델링할 수도 있지만, 호출자가 7이나 "PNEDING"을 넘기는 것을 막을 방법이 없습니다. enum은 그 구멍을 막아 줍니다. 타입 자체가 여러분이 선언한 값만 허용하니까요.

Day는 완전히 새로운 타입입니다. Day 타입의 변수는 이 일곱 개 상수 중 정확히 하나(또는 null)만 담을 수 있고 그 외에는 아무것도 담을 수 없습니다. 컴파일러는 Day d = "WEDNESDAY";Day d = 2;를 거부합니다. 바로 이 보장이 전부의 핵심입니다.

각 상수(Day.MONDAY 등)는 한 번만 생성되는 단일 공유 인스턴스입니다. MONDAY는 언제나 하나뿐이므로, enum은 equals가 아니라 ==로 비교합니다. 같은 객체이기 때문이죠.

enum에 대한 switch

enum은 switch와 자연스럽게 어울립니다. switch 안에서는 각 상수를 타입 이름으로 한정할 필요조차 없습니다.

switch 안에서는 case Day.SATURDAY가 아니라 case SATURDAY라고 씁니다. 자바가 타입을 추론합니다. 문자열이나 int에 대한 switch보다 가장 큰 이점은, 나중에 새 상수를 추가했을 때 그것을 처리하는 것을 빠뜨린 모든 switch를 IDE와 완전성 검사가 짚어 줄 수 있다는 점입니다.

모든 상수 순회하기

모든 enum은 자동으로 정적 메서드 values()를 얻는데, 이는 모든 상수를 선언 순서대로 반환합니다. 또한 각 상수의 0부터 시작하는 위치를 주는 ordinal()도 얻습니다.

name()은 상수의 식별자를 String으로 주고, values()는 메뉴를 만들거나 옵션을 순회하는 데 안성맞춤입니다. 한 가지 주의할 점이 있습니다. ordinal()을 영속적인 곳에 저장하지 마세요. 나중에 누군가 상수의 순서를 바꾸면 숫자가 밀려서 저장한 데이터가 망가집니다. ordinal()은 안정적인 ID가 아니라 구현 세부 사항으로 다루세요.

enum은 진짜 클래스입니다: 필드와 메서드

여기서 자바의 enum은 다른 언어의 이름 있는 정수 enum을 넘어섭니다. 각 상수는 생성자를 통해 전달되는 데이터를 가질 수 있고, enum은 메서드를 가질 수 있습니다. 먼저 상수를 나열하고, 각각에 생성자 인자를 주고, 세미콜론 뒤에 필드, 생성자, 메서드를 선언합니다.

구조에 주목하세요. 상수가 먼저 오고 세미콜론으로 끝난 뒤, 클래스의 나머지가 이어집니다. 생성자는 암묵적으로 private입니다. 허용되는 유일한 인스턴스는 맨 위에 선언된 것들뿐이므로 new Planet(...)은 결코 쓸 수 없습니다. final 필드는 각 상수를 불변으로 만드는데, 이는 공유되는 싱글턴에 바로 원하는 바입니다.

문자열에서 변환하고 다시 되돌리기

문자열을 일치하는 상수로 바꾸려면 자동 생성된 valueOf를 사용하세요.

valueOf는 대소문자를 포함해 상수 이름을 정확하게 맞추고, 일치하는 것이 없으면 IllegalArgumentException을 던집니다. 이것이 흔한 함정입니다. "shipped"SHIPPED와 일치하지 않습니다. 사용자 입력이나 외부 데이터를 파싱할 때는 먼저 대소문자를 정규화하고(input.toUpperCase()) 호출을 try/catch감싸세요. 그러지 않으면 첫 번째 잘못된 값에서 크래시가 납니다.

상수별 동작

때로는 각 상수가 단지 다른 데이터를 담는 것을 넘어 다르게 동작해야 할 때가 있습니다. enum에 추상 메서드를 주고, 각 상수가 작은 본문에서 자신만의 구현을 제공하게 할 수 있습니다.

이것을 때때로 "상수별 메서드" 패턴이라고 부릅니다. 상수에 대한 장황한 switch를, 각 상수에 직접 붙인 동작으로 대체합니다. 새 연산을 추가하면 컴파일러가 그것의 apply를 정의하도록 강제하므로, 어떤 경우도 빠뜨릴 수 없습니다.

enum을 써야 할 때

어떤 값이 본질적으로 작고 고정되어 있으며 컴파일 시점에 알려진 집합 중 하나일 때는 언제나 enum을 사용하세요.

  • 워크플로의 상태(PENDING, SHIPPED, DELIVERED).
  • 카테고리, 모드, 또는 유형(READ, WRITE, EXECUTE).
  • public static final int 상수 묶음을 정의하고 싶어지는 모든 곳. enum은 같은 이름 있는 값에 더해 타입 안전성, 읽기 쉬운 toString, 그리고 switch 지원을 거저 줍니다.

열려 있거나 런타임에 결정되는 집합(사용자 이름, 데이터베이스에서 가져온 국가 목록)에는 enum을 쓰지 마세요. enum은 컴파일 시점에 박혀 버립니다. 프로그램이 실행되는 동안 집합이 바뀐다면, 대신 일반 컬렉션이 필요합니다.

다음: 제네릭

이제 values()Planet[]을 반환하는 것을 보았고, 앞선 페이지들에서 List<Shape>를 사용하는 것도 보았습니다. 그 <...> 문법이 바로 제네릭으로, 완전한 타입 안전성을 유지하면서 여러 타입에 대해 동작하는 하나의 클래스나 메서드를 작성하는 자바의 방식입니다. 다음에는 List<String>Map<K, V>가 실제로 어떻게 동작하는지, 그리고 여러분만의 제네릭 타입을 어떻게 작성하는지 풀어 보겠습니다.

자주 묻는 질문

자바에서 enum이란 무엇인가요?

enum은 그 값이 고정된 이름 있는 상수 집합 중 하나만 될 수 있는 특별한 타입입니다. 예를 들어 Day.MONDAYStatus.ACTIVE 같은 것이죠. 각 상수는 미리 만들어진 enum의 단 하나뿐인 인스턴스입니다. enum은 타입 안전성을 제공합니다. Day를 받는 메서드는 언제나 실제 요일만 받을 수 있고, 오타가 난 문자열이나 범위를 벗어난 int는 절대 받지 않습니다.

자바 enum은 필드와 메서드를 가질 수 있나요?

네. enum은 완전한 클래스입니다. 각 상수에 생성자 인자를 전달하고, 이를 private final 필드에 저장하며, 그 필드를 사용하는 메서드를 추가할 수 있습니다. 예를 들어 MERCURY(3.3e23, 2.4e6)는 질량과 반지름을 enum의 생성자에 전달하고, surfaceGravity() 메서드는 그 필드들로부터 값을 계산할 수 있습니다.

자바 enum에서 values()와 valueOf()의 차이는 무엇인가요?

values()는 enum의 모든 상수를 선언 순서대로 담은 배열을 반환합니다. 모든 옵션을 순회할 때 유용합니다. valueOf("NAME")은 그 반대로, 이름이 문자열과 정확히 일치하는 상수를 찾고, 일치하는 것이 없으면 IllegalArgumentException을 던집니다. 둘 다 컴파일러가 자동으로 생성합니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기