열거 타입은 서로 관련된 값 몇 개를 미리 정해 두고, 그중 하나만 고르게 만드는 타입이다.
String처럼 아무 글자나 넣게 두는 방식보다 훨씬 안전하다.
처음 보면 새로운 문법 하나를 배우는 것처럼 느껴질 수 있다.
하지만 열거 타입에서 먼저 봐야 하는 것은 문법이 아니라 역할이다.
열거 타입의 가장 중요한 목적은 잘못된 값을 애초에 넣지 못하게 막고, 코드의 의미를 더 분명하게 만드는 것이다.
예를 들어 계절은 보통 봄, 여름, 가을, 겨울처럼 정해진 값 안에서만 움직인다.
이럴 때 아무 문자열이나 받게 두는 것보다, 미리 정해 둔 값만 쓰게 만드는 편이 훨씬 자연스럽다.
이 글은 먼저 개념을 충분히 이해한 뒤, 마지막에 예제로 전체 흐름을 확실하게 정리하는 순서로 본다.
열거 타입은 한정된 값 목록을 하나의 타입으로 묶어 두는 방식이다.
쉽게 말하면 “이 변수에는 아무 값이나 넣지 말고, 미리 정한 후보 중 하나만 넣어라”라고 정해 두는 것이다.
초보자는 열거 타입을 처음 볼 때 “상수 여러 개를 모아 둔 문법인가?”라고 생각하기 쉽다.
완전히 틀린 말은 아니지만, 그렇게만 이해하면 핵심이 빠진다.
열거 타입의 진짜 핵심은 상수를 모아 두는 데 있는 것이 아니라, 사용할 수 있는 값의 범위를 타입 차원에서 제한하는 데 있다.
즉, 열거 타입은 “값 목록”이면서 동시에 “그 값만 담을 수 있는 전용 타입”이다.
이 점을 먼저 머릿속에 잡아 두면 뒤 내용이 훨씬 쉽게 이어진다.
예전에는int상수를 여러 개 선언해서 비슷하게 표현하기도 했다.
하지만 그 방식은 값 이름을 묶어 둘 수는 있어도, “이 값은 반드시 같은 종류여야 한다”는 규칙까지 강하게 지켜 주지는 못한다.
열거 타입은 바로 그 부족한 부분을 채워 준다.
값을 제한한다는 뜻
열거 타입은 값이 여러 개 있다는 사실보다, 아무 값이나 넣을 수 없게 만든다는 점이 더 중요하다.
예를 들어 계절을 표현할 때 정말 필요한 값은 봄, 여름, 가을, 겨울 정도다.
그런데String으로 처리하면 그 외의 값도 얼마든지 들어갈 수 있다.
열거 타입을 쓰면 사용할 수 있는 값 후보가 처음부터 정해진다.
그래서 변수에 저장할 때도 그 후보 중 하나만 선택하게 된다.
이 때문에 코드가 더 안전해지고, 읽는 사람도 이 변수가 어떤 종류의 값을 가지는지 바로 이해할 수 있다.
언제 잘 어울리는가
열거 타입은 값의 종류가 처음부터 정해져 있는 상황에서 특히 잘 맞는다.
예를 들어 아래 같은 경우다.
- 요일
- 월
- 계절
- 상태값
- 권한 종류
- 메뉴 종류
이런 값들은 아무 글자나 받아야 하는 것이 아니라, 정해진 후보 안에서만 움직여야 한다.
그래서 열거 타입과 아주 잘 맞는다.
반대로 입력 종류가 매우 자유롭고, 사용자가 어떤 값이든 넣을 수 있어야 하는 경우라면 열거 타입보다String같은 방식이 더 자연스러울 수 있다.
즉, 열거 타입은 값이 고정되어 있을 때 쓰는 도구라고 이해하면 된다.
열거 타입이 왜 필요한지 이해하려면 먼저
String방식과 비교해 보는 것이 좋다.
문자열은 자유롭게 값을 넣을 수 있어서 편해 보이지만, 그만큼 실수도 쉽게 들어간다.
그리고 잘못된 값이 들어가도 컴파일 단계에서 바로 막지 못하는 경우가 많다.
예를 들어 계절을 문자열로 관리하면"SPRING"도 넣을 수 있고"SUMMER"도 넣을 수 있다.
그런데"SPRNG"처럼 오타가 들어가도 문법 오류가 나지 않는다.
프로그램은 그 값을 그냥 문자열 하나로만 보기 때문이다.
이 문제는 초보자에게 특히 치명적이다.
분명히 봄을 뜻한다고 생각하고 코드를 작성했는데, 철자가 하나만 틀려도 전혀 다른 값이 되어 버린다.
그런데 문법 오류가 바로 나지 않으니, 문제를 늦게 발견하게 된다.
문자열 방식의 한계
문자열 방식은 자유롭다는 장점이 있지만, 그 자유로움이 오히려 문제를 만든다.
아래처럼 오타가 들어가도 프로그램은 일단 받아들인다.
"SPRING"과"SPRNG"를 구분하지 못하고 둘 다 문자열로만 본다- 이 값이 진짜 계절인지, 그냥 아무 글자인지 타입만 봐서는 알 수 없다
- 비교할 때도
equals()같은 문자열 비교를 신경 써야 한다즉, 문자열은 편해 보이지만, 값을 안전하게 관리하는 데에는 약한 방식이다.
열거 타입이 해결하는 것
반면 열거 타입은 미리 정한 상수만 쓸 수 있다.
그래서 존재하지 않는 값을 넣으려고 하면 바로 막힌다.
이 차이가 아주 크다.
열거 타입이 필요한 이유는 크게 세 가지로 정리할 수 있다.
- 잘못된 값을 막을 수 있다
- 코드만 봐도 의미가 더 분명하다
- 비교와 분기문에서도 더 안전하고 깔끔하게 사용할 수 있다
예를 들어
Season.SPRING이라고 쓰면 “계절 타입의 봄”이라는 의미가 바로 보인다.
반면String season = "SPRING";는 단순히 글자를 하나 저장한 것처럼 보인다.
즉, 값 자체의 의미를 코드에서 더 또렷하게 드러내는 것도 열거 타입의 중요한 장점이다.
열거 타입은
enum키워드로 선언한다.
형식은 단순하다.
타입 이름을 적고, 중괄호 안에 사용할 수 있는 상수들을 나열하면 된다.
아래 이미지를 먼저 보면 구조가 바로 보인다.
Week는 열거 타입 이름이고, 그 아래의MONDAY부터SUNDAY까지는 열거 상수 목록이다.
즉, 이 타입에서 사용할 수 있는 값 후보가 미리 정해져 있는 것이다.
이 이미지를 볼 때 가장 먼저 구분해야 할 것은 타입 이름과 상수 이름의 역할 차이다.
Week는 타입 이름이다.
반면MONDAY,TUESDAY같은 값들은 그 타입 안에 들어 있는 구체적인 상수들이다.
선언 구조를 쉽게 보면
열거 타입 선언은 아래처럼 이해하면 쉽다.
enum은 열거 타입을 만들겠다는 뜻이다Week는 열거 타입 이름이다- 중괄호 안의 값들은 이 타입에서 쓸 수 있는 상수 목록이다
즉, “요일이라는 타입을 만들고, 그 안에 월요일부터 일요일까지를 대표 값으로 넣어 둔다”는 구조다.
이름을 짓는 관례
이때 이름을 짓는 관례도 함께 알아두면 좋다.
- 열거 타입 이름은 보통
Week,Season,Month처럼 첫 글자를 대문자로 쓴다- 열거 상수는
MONDAY,SPRING처럼 모두 대문자로 쓰는 경우가 많다- 여러 단어로 이루어져 있다면 보통 밑줄
_로 연결한다이 규칙은 문법 자체는 아니지만, 거의 기본처럼 사용된다.
그래서 처음부터 익숙해지는 것이 좋다.
여기서 중요한 점은 열거 상수를 일반 변수처럼 만드는 것이 아니라, 타입 안에서 미리 정해 둔 대표 값으로 선언한다는 것이다.
그래서 나중에 변수를 만들 때도 아무 값이 아니라 이 상수들 중 하나만 저장하게 된다.
초보자가 가장 헷갈리기 쉬운 부분이 바로 이 부분이다.
열거 상수는 단순한 글자가 아니다.
겉으로는JANUARY같은 이름으로 보이지만, 실제로는 각 상수가 하나의 열거 객체로 만들어져서 관리된다.
아래 이미지를 보면Year안에 있는JANUARY부터DECEMBER까지가 각각 힙 영역의 객체로 연결되는 구조가 보인다.
즉, 상수 이름만 달랑 저장되는 것이 아니라, 각 상수가 자기 객체를 가지고 있는 것이다.
이 그림은 처음에는 조금 어렵게 느껴질 수 있다.
하지만 이 단계에서는 “열거 상수는 단순 글자가 아니라 실제 객체로 존재한다”는 점만 확실히 이해하면 된다.
왜 객체로 이해해야 하는가
열거 상수를 단순 문자열이라고 생각하면 뒤 내용이 잘 연결되지 않는다.
예를 들어 왜==비교가 가능한지, 왜 메서드를 붙일 수 있는지, 왜 타입처럼 다룰 수 있는지가 잘 안 보인다.
그런데 열거 상수가 객체라고 이해하면 흐름이 자연스럽다.
- 각 상수는 독립적인 값으로 존재한다
- 각 상수는 같은 타입 안에 묶여 있다
- 각 상수는 필요하면 메서드와 함께 다룰 수 있다
즉, 열거 타입은 “글자 목록”이 아니라 “정해진 객체 집합”에 더 가깝다.
그리고 이 점이 바로==비교와도 연결된다.
각 상수는 미리 하나의 객체로 만들어져 있고, 변수는 그 객체를 참조한다.
그래서 둘이 같은 상수를 가리키는지 비교할 때도 문자열 내용 비교가 아니라 같은 객체인지를 바로 확인할 수 있다.
이 그림에서 꼭 봐야 할 점
이 이미지에서 꼭 봐야 할 핵심은 아래와 같다.
- 열거 타입 정보와 상수 이름이 따로 관리된다
- 실제 값은 힙 영역의 열거 객체로 만들어진다
- 각 상수 이름은 자기 객체를 가리킨다
즉, 열거 상수는 단순한 문자열 조각이 아니라, 미리 만들어져 있는 열거 객체를 가리키는 이름이다.
이 점을 이해하면 뒤에서 왜==비교가 가능한지도 자연스럽게 이어진다.
여기서 “왜 굳이 객체로 만들까?”라는 생각이 들 수 있다.
이유는 단순하다.
이렇게 해야 각 상수를 독립적인 값으로 안정적으로 다룰 수 있고, 메서드도 붙일 수 있고, 타입 규칙도 강하게 유지할 수 있기 때문이다.
그래서 열거 타입은 단순 상수 묶음보다 더 강한 구조라고 볼 수 있다.
열거 타입 변수를 선언하고 상수를 저장하면, 변수 안에 상수 이름 글자가 들어가는 것이 아니다.
그 상수가 참조하고 있는 같은 열거 객체를 가리키는 정보가 들어간다.
쉽게 말하면 같은 객체를 함께 바라보는 구조라고 이해하면 된다.
아래 이미지를 보면Week today = Week.SUNDAY;에서today가SUNDAY라는 글자를 복사해 오는 것이 아니라,Week.SUNDAY가 가리키는 객체를 함께 참조하는 구조가 보인다.
이 부분은 열거 타입을 이해하는 데 아주 중요하다.
변수 안에 들어가는 것이 글자 자체가 아니라는 점이 뒤의 비교 방식과 바로 연결되기 때문이다.
변수에는 무엇이 들어가는가
Week today = Week.SUNDAY;를 보면 초보자는today안에"SUNDAY"라는 문자열이 저장된다고 생각하기 쉽다.
하지만 실제로는 그렇지 않다.
today는Week.SUNDAY가 가리키는 같은 객체를 참조하게 된다.
즉, 이 코드는 글자를 복사하는 코드가 아니라 참조를 저장하는 코드다.
그래서today와Week.SUNDAY는 결국 같은 대상을 바라보게 된다.
왜
==비교가 가능한가이 구조를 이해하면 왜
today == Week.SUNDAY가true인지도 바로 이해된다.
둘이 같은 이름을 비교하는 것이 아니라, 결국 같은 객체를 가리키고 있는지 비교하는 것이기 때문이다.
이 부분은 문자열과 크게 다르다.
문자열은 같은 글자를 가지고 있어도 비교 방식에 신경을 써야 한다.
하지만 열거 타입은 미리 정해진 상수 객체를 공유하므로==비교가 자연스럽고 안전하다.
null도 저장할 수 있는가열거 타입 변수도 참조형 변수이기 때문에
null을 저장할 수 있다.
즉, 아직 어떤 상수도 가리키지 않는 상태를 만들 수 있다는 뜻이다.
다만 실제 사용에서는 값이 없는 상태를 특별히 표현해야 할 때만 조심해서 쓰는 편이 좋다.
왜냐하면null상태에서 메서드를 호출하면 오류가 날 수 있기 때문이다.
그래서 열거 타입 변수는 “문자열 한 조각을 저장하는 변수”가 아니라, 정해진 상수 객체 중 하나를 참조하는 변수라고 이해하는 것이 정확하다.
이 차이를 머릿속에 확실히 잡아 두면 뒤 예제가 훨씬 쉽게 읽힌다.
열거 타입은 비교가 편하다.
이미 정해진 상수 객체끼리 다루기 때문에==로 비교해도 된다.
즉, “이 값이 봄인가”, “이 값이 가을인가”를 직관적으로 확인할 수 있다.
예를 들어enum_v == Season.FALL처럼 쓰면 “현재 값이 가을 상수와 같은가”를 바로 검사한다.
문자열처럼equals()를 따로 떠올릴 필요가 없다.
이 점이 초보자에게 특히 편하다.
분기문에서도 마찬가지다.
switch문에 열거 타입 변수를 넣으면, 경우를 나눌 때 코드가 훨씬 깔끔해진다.
예를 들어SPRING이면 어떤 동작,SUMMER면 다른 동작처럼 나누기 좋다.
비교가 쉬운 이유
열거 타입은 이미 정해진 상수 객체를 공유한다.
그래서 비교할 때 “글자 내용이 같은가”를 따지는 것이 아니라, “같은 상수를 가리키는가”를 보면 된다.
- 문자열은 같은 글자라도 비교 방식에 신경 써야 한다
- 열거 타입은 같은 상수인지
==로 바로 확인할 수 있다이 차이 때문에 비교 코드가 더 단순하고 실수도 줄어든다.
모든 비교가 가능한 것은 아니다
여기서 한 가지 더 구분해야 할 점이 있다.
열거 타입은 같은 상수인지 확인하는==비교는 아주 잘 맞는다.
하지만String이나 숫자처럼<,>,<=,>=로 크기 비교를 하는 방식은 사용할 수 없다.
왜냐하면 열거 타입의 핵심은 “정해진 상수 집합”이지, 숫자처럼 자유롭게 크기를 비교하는 값이 아니기 때문이다.
그래서 순서를 비교하고 싶다면 비교 연산자 대신compareTo()를 사용한다.
즉, 열거 타입은 같은가를 볼 때는==, 순서를 볼 때는compareTo()라고 이해하면 된다.
switch와 잘 맞는 이유열거 타입은 경우를 나누는 코드와 특히 잘 맞는다.
후보가 정해져 있기 때문에switch문으로 분기하기가 아주 자연스럽다.
SPRING이면 봄에 맞는 동작SUMMER면 여름에 맞는 동작FALL이면 가을에 맞는 동작WINTER면 겨울에 맞는 동작이처럼 각 경우를 나눌 때 코드 의미가 훨씬 또렷해진다.
여기서 하나 기억할 점이 있다.
switch문 안의case에서는Season.SPRING처럼 타입 이름까지 다 쓰지 않고SPRING처럼 상수 이름만 적는다.
이미switch(p)에서p가Season타입이라는 것이 정해져 있기 때문이다.
이 문법은 처음에는 낯설 수 있지만, 한 번 익숙해지면 오히려 더 읽기 쉽다.
결국 열거 타입은 비교와 분기문에서 코드의 의미를 더 또렷하게 보여 준다.
그래서 상태값, 옵션값, 요일, 월, 계절처럼 후보가 정해진 값들에 잘 어울린다.
열거 타입은 상수만 나열하는 문법으로 끝나지 않는다.
필요하면 필드도 둘 수 있고, 생성자도 둘 수 있고, 메서드도 둘 수 있다.
이 부분을 이해하면 열거 타입은 단순한 값 목록이 아니라, 값과 설명과 동작을 함께 묶는 타입이라는 점이 더 분명해진다.
초보자는 보통 열거 타입을SPRING,SUMMER처럼 이름만 모아 둔 구조로 먼저 이해한다.
그 이해도 맞다.
하지만 실제 사용에서는 상수마다 숫자 코드, 기호, 한글 이름, 설명 문구 같은 추가 정보가 필요한 경우가 많다.
그럴 때 열거 타입 안에 멤버를 넣는다.
상수마다 추가 정보를 붙일 수 있다
예를 들어 방향을 나타내는 열거 타입이 있다고 하자.
단순히EAST,WEST만으로도 방향은 표현할 수 있다.
하지만 화면에 화살표 기호를 같이 보여 주고 싶다면, 상수마다 기호 값을 함께 가지고 있는 편이 더 자연스럽다.
이때는 상수 옆에 값을 붙이고, 그 값을 저장할 필드를 만든다.
그리고 생성자를 통해 상수가 만들어질 때 그 값을 함께 넘겨 준다.
즉, 열거 상수는 이름만 가진 값이 아니라 필요한 데이터를 함께 가진 객체가 될 수 있다.
생성자는 바깥에서 직접 호출하지 않는다
열거 타입 안에 생성자를 둘 수 있다고 해서,
new로 마음대로 객체를 만들 수 있는 것은 아니다.
열거 타입의 상수는 이미 미리 정해진 것만 존재해야 한다.
그래서 생성자는 바깥에서 직접 호출하는 방식이 아니라, 상수가 만들어질 때 내부적으로만 사용된다고 이해하면 된다.
즉, 열거 타입은 “새 값을 계속 만들어 내는 구조”가 아니라, 미리 정한 상수들을 안정적으로 관리하는 구조다.
그래서 생성자도 일반 클래스와 똑같이 열려 있는 느낌으로 보면 안 된다.
멤버를 넣을 때 꼭 기억할 구조
열거 타입에 멤버를 넣을 때는 구조를 한 번에 기억하는 것이 좋다.
- 먼저 열거 상수를 적는다
- 상수마다 전달할 값이 있으면 괄호에 넣는다
- 상수 목록이 끝나면
;를 붙인다- 그 아래에 필드, 생성자, 메서드를 적는다
이 순서를 기억해 두면 멤버가 있는 열거 타입도 훨씬 쉽게 읽힌다.
상수 목록 뒤의;는 특히 초보자가 자주 놓치는 부분이라 같이 기억해 두는 것이 좋다.
예제로 보면 더 분명해진다
아래 예제는 각 방향 상수에 숫자 코드와 기호를 함께 붙여 두는 구조다.
상수마다 값이 다르지만, 모두 같은Direction타입 안에서 관리된다.// EnumMemberTest.java enum Direction { EAST(1, ">"), SOUTH(2, "v"), WEST(3, "<"), NORTH(4, "^"); // 상수마다 값 전달 private final int code; // 숫자 코드 저장 private final String symbol; // 방향 기호 저장 Direction(int code, String symbol) { // 상수 생성 시 값 저장 this.code = code; this.symbol = symbol; } int getCode() { return code; // 숫자 코드 반환 } String getSymbol() { return symbol; // 방향 기호 반환 } } public class EnumMemberTest { public static void main(String[] args) { Direction d = Direction.EAST; // 동쪽 상수 저장 System.out.println(d); // 상수 이름 출력 System.out.println(d.getCode()); // 추가 정보 출력 System.out.println(d.getSymbol()); // 추가 정보 출력 } }// 출력결과 // EAST // 1 // >이 예제를 보면
Direction.EAST는 단순히 이름만 가진 값이 아니라,1과">"라는 정보까지 함께 가진다는 점이 보인다.
즉, 열거 타입은 정해진 후보만 안전하게 다루는 동시에, 그 후보에 필요한 설명 정보까지 함께 묶을 수 있는 구조다.
열거 타입에서는 몇 가지 기본 메서드를 자주 사용한다.
대표적으로name(),valueOf(),values(),ordinal()이 있다.
이 메서드들은 이름만 외우면 금방 헷갈린다.
그래서 각각이 어느 방향으로 움직이는지 이해하는 것이 중요하다.
즉, 문자열로 바꾸는지, 문자열에서 다시 상수로 바꾸는지, 전체 목록을 꺼내는지, 순서를 숫자로 확인하는지로 나눠서 보면 훨씬 쉽다.
name()
name()은 현재 열거 상수의 이름을 문자열로 꺼내는 메서드다.
예를 들어Season.SPRING.name()은"SPRING"을 돌려준다.
즉, 열거 상수 → 문자열 방향이다.
현재 어떤 상수가 저장되어 있는지 이름으로 확인하고 싶을 때 사용한다.
valueOf()
valueOf()는 반대다.
문자열을 받아서 같은 이름의 열거 상수를 찾아준다.
예를 들어Season.valueOf("SUMMER")는Season.SUMMER를 돌려준다.
즉, 문자열 → 열거 상수 방향이다.
문자열로 받은 값을 다시 열거 타입 값으로 바꾸고 싶을 때 사용한다.
단, 이름이 정확히 일치해야 한다는 점을 같이 기억해야 한다.
values()
values()는 해당 열거 타입에 있는 모든 상수를 배열로 돌려준다.
즉, 이 타입 안에 어떤 상수들이 들어 있는지 한 번에 꺼낼 수 있다.
반복문과 같이 쓰면 전체 상수를 순서대로 확인할 수 있다.
요일 전체를 출력하거나, 메뉴 전체를 화면에 보여 주고 싶을 때 유용하다.
ordinal()
ordinal()은 현재 열거 상수가 몇 번째 위치에 있는지를 숫자로 돌려준다.
첫 번째 상수는0, 그다음은1처럼 순서대로 올라간다.
예를 들어SPRING이 첫 번째로 선언되어 있으면ordinal()값은0이다.
즉,ordinal()은 상수의 선언 순서를 숫자로 보고 싶을 때 사용하는 메서드다.
다만 이 값은 상수를 선언한 순서에 따라 달라진다.
그래서 화면에 보여 줄 고정 번호나 중요한 업무 코드처럼 오래 유지해야 하는 숫자를ordinal()에 그대로 기대는 것은 조심하는 편이 좋다.
즉,ordinal()은 순서를 확인하는 용도로 이해하는 것이 자연스럽다.
네 메서드를 한 번에 정리하면
네 메서드는 아래처럼 구분하면 헷갈리지 않는다.
name()은 상수 이름을 꺼낸다valueOf()는 문자열을 상수로 바꾼다values()는 전체 상수 목록을 배열로 꺼낸다ordinal()은 선언 순서를 숫자로 알려 준다이 메서드들은 단순 편의 기능이 아니다.
열거 타입이 단순 상수 묶음이 아니라, 타입으로서 동작하는 구조라는 점을 보여 주는 대표적인 예다.
이제 개념을 코드로 연결해 보면 이해가 훨씬 단단해진다.
아래 예제는 열거 타입 선언, 상수 출력, 변수 저장,==비교,switch사용,valueOf(),name(),values(),compareTo(),ordinal()까지 한 번에 보여 준다.
즉, 열거 타입에서 꼭 알아야 할 핵심 흐름이 모두 들어 있다.// EnumTest.java package extraexam; enum Season { SPRING, SUMMER, FALL, WINTER } public class EnumTest { public static void main(String[] args) { System.out.println(Season.SPRING); // 상수 이름 출력 System.out.println(Season.SUMMER); // 상수 이름 출력 System.out.println(Season.FALL); // 상수 이름 출력 System.out.println(Season.WINTER); // 상수 이름 출력 Season enum_v = Season.FALL; // 가을 상수 저장 if (enum_v == Season.FALL) // 같은 상수인지 비교 System.out.println("당신이 좋아하는 계절은 가을!!"); printFruit(enum_v); // 가을에 맞는 과일 출력 // enum_v = "SPRING"; // 문자열은 저장할 수 없음 enum_v = Season.SPRING; // 봄 상수로 변경 if (enum_v == Season.SPRING) // 같은 상수인지 비교 System.out.println("당신이 좋아하는 계절은 봄!!"); printFruit(enum_v); // 봄에 맞는 과일 출력 System.out.println(Season.valueOf("SUMMER")); // 문자열을 상수로 변환 System.out.println(enum_v.name()); // 상수 이름을 문자열로 꺼내기 System.out.println(enum_v.ordinal()); // 선언 순서 확인 System.out.println(Season.SUMMER.compareTo(Season.SPRING)); // 순서 비교 for (Season s : Season.values()) { System.out.println(s); // 전체 상수 순서대로 출력 } } static void printFruit(Season p) { switch (p) { case SPRING: System.out.println("대저토마토"); break; case SUMMER: System.out.println("복숭아"); break; case FALL: System.out.println("홍로"); break; case WINTER: System.out.println("레드향"); } } }// 출력결과 // SPRING // SUMMER // FALL // WINTER // 당신이 좋아하는 계절은 가을!! // 홍로 // 당신이 좋아하는 계절은 봄!! // 대저토마토 // SUMMER // SPRING // 0 // 1 // SPRING // SUMMER // FALL // WINTER이 예제를 읽는 순서
이 예제를 볼 때는 한 줄씩 따로 보기보다 흐름으로 보는 것이 중요하다.
코드가 어떤 순서로 열거 타입의 특징을 보여 주는지 따라가면 이해가 훨씬 쉽다.
- 먼저
Season이라는 열거 타입을 선언한다- 그 안에 사계절 상수를 넣는다
- 열거 상수를 직접 출력해 본다
- 변수를 만들고 상수를 저장한다
==로 비교해 본다switch문으로 경우를 나눠 본다valueOf()와name()을 확인한다ordinal()과compareTo()로 순서도 확인한다values()로 전체 목록도 꺼내 본다즉, 앞에서 배운 핵심 개념이 코드 안에서 순서대로 다시 등장한다고 보면 된다.
코드 흐름을 쉽게 풀어 보면
먼저
Season이라는 열거 타입을 선언하고, 그 안에 사계절 상수를 넣었다.
이 순간부터Season타입 변수에는 네 계절 중 하나만 저장할 수 있게 된다.
그다음System.out.println(Season.SPRING);같은 코드는 열거 상수를 직접 출력한 것이다.
출력하면 상수 이름이 그대로 나온다.
그래서SPRING,SUMMER같은 문자열이 보인다.
이후Season enum_v = Season.FALL;에서는 열거 타입 변수enum_v가FALL상수를 참조하게 된다.
이제enum_v에는 계절 타입 값이 들어 있는 상태다.
문자열"FALL"을 넣은 것이 아니라Season.FALL상수를 저장한 것이다.
그다음if (enum_v == Season.FALL)는 현재 저장된 값이 가을 상수와 같은지 확인한다.
앞에서 본 것처럼 둘은 같은 상수 객체를 가리키므로==비교가 가능하다.
그래서 조건이 참이 되고, 가을을 좋아한다는 문장이 출력된다.
printFruit(enum_v)는 현재 계절에 맞는 과일을 출력하는 메서드다.
여기서switch문이 사용된다.
가을이면홍로, 봄이면대저토마토가 출력된다.
즉, 열거 타입은 분기문에서 각 경우를 깔끔하게 나누기에 아주 좋다.
잘못된 예와 올바른 예
주석 처리된
// enum_v = "SPRING";는 일부러 잘못된 예를 보여 준다.
열거 타입 변수에는 문자열을 직접 넣을 수 없다.
이 부분이 바로 열거 타입의 타입 안정성을 보여 준다.
아무 글자나 집어넣는 방식이 아니라, 정해진 상수만 허용한다는 규칙이 코드에서 강하게 유지된다.
그 아래enum_v = Season.SPRING;는 올바른 방식이다.
같은Season타입의 상수이므로 정상적으로 저장할 수 있다.
그리고 다시==비교와printFruit()가 이어지면서, 이번에는 봄 기준 결과가 출력된다.
마지막 줄들에서 꼭 봐야 할 것
Season.valueOf("SUMMER")는 문자열"SUMMER"를Season.SUMMER상수로 바꿔 준다.
반대로enum_v.name()은 현재 저장된Season.SPRING의 이름을 문자열"SPRING"으로 꺼낸다.
즉, 앞에서 설명한 방향 차이가 실제 코드에서 그대로 보이는 부분이다.
enum_v.ordinal()은 현재 상수가 몇 번째 위치인지 보여 준다.
Season.SUMMER.compareTo(Season.SPRING)은 두 상수의 선언 순서를 비교한다.
또Season.values()를 반복문에 넣으면 모든 상수를 한 번에 확인할 수 있다.
즉, 마지막 부분은 “열거 타입이 단순 비교만 하는 도구가 아니라, 이름 확인, 문자열 변환, 순서 확인, 전체 목록 조회까지 가능한 타입”이라는 점을 보여 준다.
이 예제에서 가장 중요한 것은 출력 결과 자체보다, 값이 다뤄지는 방식이다.
Season은 계절이라는 범위를 가진 타입이고,SPRING,SUMMER,FALL,WINTER는 그 타입 안에 미리 준비된 상수들이다.
변수에 저장할 때는 아무 문자열이 아니라 그 상수 중 하나를 저장한다.
비교할 때도 문자열 비교가 아니라 같은 상수를 참조하는지 확인한다.
분기할 때도switch와 잘 맞는다.
그리고 필요하면name(),valueOf(),values(),ordinal()로 문자열과 열거 상수, 순서 정보 사이를 오갈 수 있다.
핵심만 다시 정리하면
- 열거 타입은 정해진 값만 안전하게 다루기 위한 타입이다
- 열거 상수는 단순 글자가 아니라 열거 객체로 관리된다
- 변수에는 상수 이름이 아니라 그 객체를 가리키는 정보가 저장된다
- 그래서
==비교와switch분기가 자연스럽고 안전하다- 필요하면 필드, 생성자, 메서드를 넣어서 상수마다 추가 정보도 함께 관리할 수 있다
name(),valueOf(),values(),ordinal()로 문자열과 상수, 순서 정보 사이를 오갈 수 있다정리하면 열거 타입은 “값 몇 개를 예쁘게 모아 둔 문법”이 아니라, 제한된 값 집합을 안전하게 다루기 위한 타입이다.
이 점이 이해되면 열거 타입은 외울 대상이 아니라, 상황에 맞게 꺼내 쓰는 도구가 된다.
특히 요일, 월, 계절, 상태값, 권한, 메뉴 종류처럼 후보가 명확히 정해져 있는 값이라면 열거 타입을 먼저 떠올리는 습관이 중요하다.
이 흐름을 잡아 두면 뒤에서 나오는switch표현식이나 더 복잡한 상태 관리도 훨씬 쉽게 연결된다.