enum 정의하는 방법
■ enum이란?
enum은 열거형이라고 부른다.
상수를 관리하는 키워드이며, 다수의 상수를 선언할 때 관련된 상수를 편리하게 선언하기 위해 사용한다.
또한, 정의된 값 이외는 허용하지 않는 특성을 가지고 있다.
※ java.lang.Enum 클래스 사용
■ enum 정의
enum Test {ONE, TWO, THREE, ...}
위의 예시처럼 enum 키워드와 열거형 이름을 설정하고 상수명을 입력한다.
enum을 정의하는 방법은 크게 3가지 정도로 나눌 수 있다.
1. 별도 java 파일
// 첫 번째 java(Enumtest.java) 파일
public enum Fruit { APPLE, BANANA, MANGO }
// 두 번째 java 파일
public class Test {
public static void main(String[] args) {
Enumtest.Fruit f = Enumtest.Fruit.APPLE;
System.out.println(f);
}
}
//결과 : APPLE
2. 클래스 내부
public class Test {
public enum Fruit { APPLE, BANANA, MANGO }
public static void main(String[] args) {
Fruit f = Fruit.APPLE;
System.out.println(f);
}
}
//결과 : APPLE
3. 클래스 외부
enum Fruit { APPLE, BANANA, MANGO }
public class Test {
public static void main(String[] args) {
Fruit f = Fruit.APPLE;
System.out.println(f);
}
}
//결과 : APPLE
※ 각 방법마다 IDE에 표시되는 enum 객체 폴더 구조의 차이가 있다
■ enum을 왜 사용할까?
enum은 코드의 가독성을 높일 수 있고 협업에서의 논리적인 오류를 줄일 수 있다.
기존에 상수를 생성할 때는 무조건 초기화를 해주어야 했다.
따라서, 아무 의미없는 1이나 2와 같은 값을 넣기도 하였는데
이러한 부분은 의도치 않은 문제를 발생시킬 수 있다.
그리고 프로그램이 대규모로 확장될수록 여러 사람이 동일한 이름의 상수를 작성하려고 하다보면 컴파일 에러가 발생하기도 해서 하나하나 확인해야 하는 불편한 사항이 있었다.
이러한 문제점을 해결한 것이 enum이다.
■ enum 생성자
Java에서 enum은 클래스 취급을 받는다.
사실 정의하는 방법과 사용 방법을 살펴보면 클래스의 냄새가 나긴한다...
enum은 상수를 입력한 뒤 세미콜론을 붙여야 하는데, 단순히 상수 이름만 선언할 때에는 세미콜론을 생략해도 되지만 생성자를 정의하기 위해서는 붙여야 한다.
enum Fruit {
APPLE, BANANA, MANGO;
Fruit() {
System.out.println("Fruit의 "+this.name()+" 생성자 호출");
}
}
public class Test {
public static void main(String[] args) {
Fruit f = Fruit.APPLE;
System.out.println(f);
}
}
//결과
Fruit의 APPLE 생성자 호출
Fruit의 BANANA 생성자 호출
Fruit의 MANGO 생성자 호출
APPLE
위의 코드를 살펴보자.
우선, enum은 상수 하나 당 인스턴스가 만들어지며
모두 public static final이다.
추가적으로 객체를 생성할 때 Fruit f = new Fruit() 는 에러가 발생하는데, 그 이유는 enum 생성자의 접근제어자는 항상 private이기 때문이다.
enum이 제공하는 메소드 (values()와 valueOf())
여기서 큰 제목은 values()와 valueOf()이지만.... 배우는 김에 추가적으로 몇 개를 더 해보자.
우선, values()이다.
values()
values() 메소드는 enum의 모든 상수들을 배열로 만들어서 리턴한다.
enum Fruit {
APPLE, BANANA, MANGO;
}
public class Test {
public static void main(String[] args) {
for(Fruit f : Fruit.values()) {
System.out.println(f);
}
}
}
// 출력
APPLE
BANANA
MANGO
valueOf()
valueOf() 메소드는 매개변수로 들어오는 문자열과 동일한 문자열을 가지는 enum 상수를 리턴한다.
enum Fruit {
APPLE, BANANA, MANGO;
}
public class Test {
public static void main(String[] args) {
Fruit f = Fruit.valueOf("MANGO");
System.out.println(f);
}
}
// 출력 : MANGO
ordinal()
ordinal() 메소드는 전체 enum 상수 중에서 몇 번째인지 알려준다.
이것은 배열과 동일하게 0부터 시작한다.
enum Fruit {
APPLE, BANANA, MANGO;
}
public class Test {
public static void main(String[] args) {
Fruit f = Fruit.BANANA;
System.out.println(f.ordinal());
}
}
// 출력 : 1
name()
name() 메소드는 위에서 생성자 예시를 작성할 때 몰래 써보았던 메소드이다.
enum 객체(상수라고 해도 무방...)가 가지고 있는 문자열을 리턴
enum Fruit {
APPLE, BANANA, MANGO;
}
public class Test {
public static void main(String[] args) {
Fruit f = Fruit.APPLE;
String s = f.name();
System.out.println(s);
}
}
// 출력 : APPLE
compareTo()
compareTo() 메소드는 매개변수로 주어진 enum 상수 기준으로 전후 몇 번째에 위치하는지 비교한다.
만약, enum 상수가 매개변수의 값보다 순번이 빠르면 음수, 늦으면 양수가 리턴된다.
enum Fruit {
APPLE, BANANA, MANGO;
}
public class Test {
public static void main(String[] args) {
Fruit a = Fruit.APPLE;
Fruit b = Fruit.BANANA;
int r1 = a.compareTo(b); // a가 b보다 더 빠르기 때문에 -1
int r2 = b.compareTo(a); // b는 a보다 더 늦기 때문에 1
System.out.println(r1);
System.out.println(r2);
}
}
// 출력
-1
1
java.lang.Enum
public abstract class Enum<E extends Enum<E>>
implements Constable, Comparable<E>, Serializable {
private final String name;
private final String name() {
return name;
}
......
}
java.lang.Enum은 모든 enum의 조상 클래스이다.
enum은 Enum 클래스를 상속 받기 때문에 enum type은 별도의 상속을 받을 수 없다.
위에서 학습했던 다섯 개의 메소드 역시 해당 클래스에 정의되어 있으며, 대부분 final로 선언되었기 때문에 오버라이딩을 할 수 없다.
그리고 enum 생성자를 할 때 private으로 했었는데... 왜 그랬는지 조사해보았다.
우선, enum은 열거형을 의미하는 특별한 클래스이기 때문에 일반 클래스와 같이 생성자가 필요하지만
따로 만들지 않아도 컴파일러가 만들어준다.
enum은 고정(static)된 상수들의 집합으로써, 런타임 시점이 아닌 컴파일 시점에 모든 값을 알고 있어야 한다.
이 말인 즉슨... 다른 패키지나 클래스에서 enum에 접근하여 동적으로 값을 변경할 수 없다.
해당 enum 클래스 내부에서도 new키워드로 인스턴스 생성이 불가능한데, 사실상 외부에서 접근 가능한 생성자가 없으므로 final과 다름이 없다.
EnumSet
public abstract class EnumSet { //EnumSet은 abstract하여 객체 생성이 불가능하다.
...
//noneOf 메소드에서 상황에 따라 다른 구현체 객체들을 만들어서 반환해주고 있다.
public static noneOf(...) {
if (enumElementSize <= 64)
return new RegularEnumSet<>(...);
else
return new JumboEnumSet<>(...);
}
EnumSet은 java.util 패키지 내에 정의되어 있는 추상 클래스이다.
HashSet과 비교했을 때, 성능 상의 이점이 많기 때문에 열거형 데이터를 위한 Set이 필요한 경우 사용한다.
> 어떤 이점이 있는지는 잘 모르겠음... 그냥 그렇다고 하네...
■ 특징
Enum 값만 포함될 수 있으며, 모든 값은 Enum에 속해야 한다.
Null 값을 추가할 수 없다.(NullPointerException을 throws하는 것도 안됨)
Thread-safe하지 않으므로, 필요한 경우 Collections.synchronizedMap을 사용하거나 외부에서 동기화해야 한다.
Enum에 선언 된 순서에 따라 저장된다.(ordinal 값)
■ 메소드
allOf() : enum에 정의 된 정보를 모두 추가
noneOf() : 아무것도 추가하지 않음
of() : 요소를 직접 추가 가능
range() : enum의 하위 집합 생성
complementOf() : 매개변수로 전달된 요소를 제외
copyOf() : 다른 EnumSet의 모든 요소를 복사하여 EnumSet 생성
■ 예시
enum Fruit {
APPLE, BANANA, MANGO, ORANGE, LEMON, MELON;
}
public class Test {
public static void main(String[] args) {
EnumSet<Fruit> set1, set2, set3, set4, set5;
set1 = EnumSet.allOf(Fruit.class);
set2 = EnumSet.of(Fruit.APPLE, Fruit.ORANGE);
set3 = EnumSet.complementOf(set2);
set4 = EnumSet.range(Fruit.BANANA, Fruit.LEMON);
set5 = EnumSet.noneOf(Fruit.class);
set5.add(Fruit.MELON);
set5.add(Fruit.MANGO);
set5.remove(Fruit.MANGO);
System.out.println("set1 = " + set1);
System.out.println("set2 = " + set2);
System.out.println("set3 = " + set3);
System.out.println("set4 = " + set4);
System.out.println("set5 = " + set5);
System.out.println(set5.contains(Fruit.MANGO));
set5.add(Fruit.MANGO);
System.out.println(set5.contains(Fruit.MANGO));
}
}
// 결과
set1 = [APPLE, BANANA, MANGO, ORANGE, LEMON, MELON]
set2 = [APPLE, ORANGE]
set3 = [BANANA, MANGO, LEMON, MELON]
set4 = [BANANA, MANGO, ORANGE, LEMON]
set5 = [MELON]
false
true