Java 항해일지 11. Enum
목표
자바의 열거형에 대해 학습하세요.
학습할 것 (필수)
- enum 정의하는 방법
- enum이 제공하는 메소드 (values()와 valueOf())
- java.lang.Enum
- EnumSet
enum이란?
enum은 열거형(enumerated type)이라고 부른다. 열거형은 서로 연관된 상수들의 집합이라고 할 수 있다.
열거형은 연관된 값들을 저장하며, 저장된 값들이 변경되지 않도록 보장한다. 또한 enum의 경우 열거형 그 자체이면서 클래스이기 때문에 enum내부에 생성자나 필드, 메서드를 가질 수 있어 다양한 역할을 할 수 있다.
상수 목록이 필요해 상수목록만 적어놓은 class를 활용한 적이 있는데, 이런 사용은 자제하고 enum을 활용할 수 있다.
enum 정의하는 방법
1. 별도의 java 파일로 정의
2. 클래스 내부에서 정의
public class MonthClass {
public enum Month {
JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC;
}
public static void main(String[] args) {
Month jan = Month.JAN;
System.out.println(jan);
}
}
3. 클래스 외부에서 정의
enum Month {
JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC;
}
public class MonthClass {
public static void main(String[] args) {
Month jan = Month.JAN;
System.out.println(jan);
}
}
몇몇 블로그에서 ;(세미콜론)은 생략할 수 없고 반드시 사용해야 한다는 것을 봤는데 이는 잘못된 사실이다. 상수로 사용할 목록만 정의하는 경우 세미콜론은 생략 가능하고 실제로 인텔리제이에서 세미콜론을 붙여봤는데 아래와 같이 불필요한 세미콜론을 제거할 것을 추천했다.
enum이 유용하게 사용될 수 있는 상황은 어떤 상황이 있을까?
예를 들어 숫자만 알고, 영어는 아예 모르는 사람이 '26, JAN'과 같이 표기된 달력을 본다고 가정해보자. 이 사람은 26이 날짜인 건 알고 JAN은 모른다. 이때, JAN을 입력했을 때 숫자로 바꿔서 알려주는 프로그램의 코드는 아래와 같을 것이다. (예제는 Enum을 별도의 파일로 정의했다.)
public class MonthClass {
public static void main(String[] args) {
// JAN 입력 받음
MonthEnum month = MonthEnum.JAN;
int numOfMonth = 0;
switch (month) {
case JAN: numOfMonth = 1; break;
case FEB: numOfMonth = 2; break;
case MAR: numOfMonth = 3; break;
case APR: numOfMonth = 4; break;
case MAY: numOfMonth = 5; break;
case JUN: numOfMonth = 6; break;
case JUL: numOfMonth = 7; break;
case AUG: numOfMonth = 8; break;
case SEP: numOfMonth = 9; break;
case OCT: numOfMonth = 10; break;
case NOV: numOfMonth = 11; break;
case DEC: numOfMonth = 12; break;
}
System.out.println(month + ": " + numOfMonth + "월 입니다.");
}
}
enum 작성에 switch문까지 작성할 코드가 굉장히 많다. enum을 제대로 활용하고 있는 코드라고 할 수는 없다.
enum의 각 상수를 대표하는 정수를 지정해서 사용한다면? 코드는 다음과 같이 간결해 질 수 있다.
먼저 enum을 아래와 같이 수정한다.
public enum MonthEnum {
JAN(1),
FEB(2),
MAR(3),
APR(4),
MAY(5),
JUN(6),
JUL(7),
AUG(8),
SEP(9),
OCT(10),
NOV(11),
DEC(12);
private int numOfMonth;
MonthEnum(int numOfMonth) {
this.numOfMonth = numOfMonth;
}
public int getNumOfMonth() {
return numOfMonth;
}
}
각 상수를 대표하는 정수 값을 지정해주고, 생성자를 만들고, 정수 값을 반환받는 메서드도 만들어줬다.
enum이 수정됐다면 메인메서드는 아래와 같이 간결해질 수 있다.
public class MonthClass {
public static void main(String[] args) {
// JAN 입력 받음
MonthEnum month = MonthEnum.JAN;
System.out.println(month + ": " + MonthEnum.JAN.getNumOfMonth() + "월 입니다.");
}
}
훨씬 간결한 코드가 같은 값을 출력해주고 있다.
이번에는 숫자를 모르고 영어밖에 모르는 사람이 있다고 가정해보자. 이 사람은 JAN과 같이 축약된 단어는 알아볼 수가 없고, January같이 full name을 이용해서만 달력을 읽을 수 있다. 이런 사람을 위한 코드는 아래와 같다.
public enum MonthEnum {
JAN(1, "January"),
FEB(2, "February"),
MAR(3, "March"),
APR(4, "April"),
MAY(5, "May"),
JUN(6, "June"),
JUL(7, "July"),
AUG(8, "August"),
SEP(9, "September"),
OCT(10, "October"),
NOV(11, "November"),
DEC(12, "December");
private int numOfMonth;
private String fullNameOfMonth;
MonthEnum(int numOfMonth) {
this.numOfMonth = numOfMonth;
}
MonthEnum(int numOfMonth, String fullNameOfMonth) {
this.numOfMonth = numOfMonth;
this.fullNameOfMonth = fullNameOfMonth;
}
public int getNumOfMonth() {
return numOfMonth;
}
public String getFullNameOfMonth() {
return fullNameOfMonth;
}
}
생성자 오버라이딩을 통한 enum을 수정하고 메인메서드를 다음과 같이 수정했다.
public class MonthClass {
public static void main(String[] args) {
// JAN 입력 받음
MonthEnum month = MonthEnum.JAN;
System.out.println(month + ": '" + MonthEnum.JAN.getFullNameOfMonth() + "' 입니다.");
}
}
만약 숫자와 full name을 같이 보고 싶다면 메인메서드를 다음과 같이 수정할 수 있을 것이다.
public class MonthClass {
public static void main(String[] args) {
// JAN 입력 받음
MonthEnum month = MonthEnum.JAN;
System.out.println(month + ": " + MonthEnum.JAN.getNumOfMonth() +
"월, full-name은 '" + MonthEnum.JAN.getFullNameOfMonth() + "' 입니다.");
}
}
이러한 기능 뿐만 아니라 함수형 인터페이스를 사용할 수도 있다. 아래는 각 달의 주제를 함수형 인터페이스를 통해 출력한 예제이다.
"January", "February" 등 일반 문자열을 출력하는 것과 같은 기능이지만, 어떻게 구현했는지가 다르다.
import java.util.function.Supplier;
public enum MonthEnum {
JAN(1, "January", () -> "주제가 있는 달"),
FEB(2, "February", () -> "생각의 달"),
MAR(3, "March", () -> "생명의 달"),
APR(4, "April", () -> "과학의 달"),
MAY(5, "May", () -> "가정의 달"),
JUN(6, "June", () -> "호국 보훈의 달"),
JUL(7, "July", () -> "자연과 환경의 달"),
AUG(8, "August", () -> "환상의 달"),
SEP(9, "September", () -> "독서의 달"),
OCT(10, "October", () -> "문화의 달"),
NOV(11, "November", () -> "불조심 강조의 달"),
DEC(12, "December", () -> "나눔 달");
private int numOfMonth;
private String fullNameOfMonth;
private Supplier<String> monthInfo;
MonthEnum(int numOfMonth, String fullNameOfMonth, Supplier<String> monthInfo) {
this.numOfMonth = numOfMonth;
this.fullNameOfMonth = fullNameOfMonth;
this.monthInfo = monthInfo;
}
public void printMonthInfo() {
System.out.println(monthInfo.get());
}
}
public class MonthClass {
public static void main(String[] args) {
// JAN 입력 받음
MonthEnum month = MonthEnum.JAN;
month.printMonthInfo();
}
}
enum이 제공하는 메소드 (values()와 valueOf())
enum이 제공하는 기본적인 메서드들은 values()와 valueOf() 외에도 ordinal(), name() 등이 있다. 예제를 통해 살펴보자.
values()
enum 안에 있는 모든 상수들을 배열로 반환한다.
import java.util.Arrays;
public class MonthClass {
public static void main(String[] args) {
Arrays.stream(MonthEnum.values()).forEach(System.out::println);
}
}
import java.util.Arrays;
public class MonthClass {
public static void main(String[] args) {
Arrays.stream(MonthEnum.values()).forEach(m -> m.printMonthInfo());
}
}
valueOf()
String타입을 매개변수로 받아 해당 매개변수와 동일한 이름의 상수를 찾아 반환한다. 없는 경우 IllegalArgumentException을 발생시킨다.
public class MonthClass {
public static void main(String[] args) {
System.out.println(MonthEnum.valueOf("Jan")); //JAN인 경우 JAN, Jan은 아래와 같이 예외 출력
}
}
java.lang.Enum
enum 클래스는 java.lang.Enum 클래스를 상속받았으므로 다른 클래스를 상속받을 수 없다.(다중 상속 불가)
아래는 Enum 클래스에서 사용할 수 있는 다양한 메서드들의 예시다.
ordinal()
해당 상수가 Enum 클래스 내에 몇 번째로 선언됐는지를 반환한다.
public class MonthClass {
public static void main(String[] args) {
MonthEnum month = MonthEnum.JAN;
System.out.println(month.ordinal());
}
}
name()
상수의 값을 반환한다.
public class MonthClass {
public static void main(String[] args) {
MonthEnum month = MonthEnum.JAN;
System.out.println(month.name());
}
}
compareTo(E o)
비교하고자 하는 상수와 위치 값의 차이를 반환한다. (AUG = 7, JAN = 0)
public class MonthClass {
public static void main(String[] args) {
MonthEnum month = MonthEnum.AUG;
System.out.println(month.compareTo(MonthEnum.JAN));
}
}
EnumSet
EnumSet 클래스는 java.util 패키지에 정의되어 있으며, 우리가 흔히 알고 있는 Set 자료구조의 특징을 따른다.
import java.util.EnumSet;
public class MonthClass {
public static void main(String[] args) {
EnumSet<MonthEnum> enumSet = EnumSet.allOf(MonthEnum.class);
// 모든 요소를 포함한 EnumSet 반환
EnumSet<MonthEnum> winterEnumSet = EnumSet.of(MonthEnum.DEC, MonthEnum.JAN, MonthEnum.FEB);
// 특정 상수만 포함한 EnumSet 반환
EnumSet<MonthEnum> exceptWinterSet = enumSet.complementOf(winterEnumSet);
// 특정 상수를 제외한 EnumSet 반환
EnumSet<MonthEnum> springEnumSet = EnumSet.range(MonthEnum.MAR, MonthEnum.MAY);
// 특정 범위의 EnumSet 반환, MAR, APR, MAY
System.out.println(enumSet);
System.out.println(winterEnumSet);
System.out.println(exceptWinterSet);
System.out.println(springEnumSet);
}
}
참고.
docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Enum.html