Enum
Enum이란?
열거형이라고도 불리는 Enum은 멤버라 불리는 명명된 값의 집합을 이루는 자료형이다.
상수 데이터들의 집합이라고 생각하면된다.
예로는 요일 / 계절 / 주사위 등이 있다.
월, 화, 수, 목, 금, 토, 일 / 봄, 여름, 가을 , 겨울 / 1, 2, 3, 4, 5, 6
이와 같이 한정된 데이터의 묶음을 열겨형 타입 Enum으로 묶는다면 구조적으로 설계가 가능하다.
과거 상수 정의
final 상수
final
제어자를 이용해 변수를 상수화
정의된 상수는 클래스 내에서 전역으로 사용될 수 있으며, 한 번 정의된 후에는 값을 변경할 수 없습니다.
하지만, 숫자 상수만으로는 어떤 값이 어떤 의미를 가지는지 직관적으로 알기 어렵다. 코드만 봐서는 1이 월요일을 나타내는지 알기 어렵다. 또한 정수형 상수는 같은 정수형을 사용하는 다른 값들과 구분이 어렵다. 잘못된 값을 넣더라도 컴파일러가 이를 인식하지 못한다.
유지보수측에서도 문제가 생긴다.
상수 값이 변경되거나 추가될 때 코드 전반에 걸쳐 수정이 필요할 수 있다. 특히 상수가 많이 사용되는 경우 이를 모두 찾아 수정하는 것은 어려울 수 있다.
class EnumExample {
private final static int MONDAY = 1;
private final static int TUESDAY = 2;
private final static int WEBNESDAY = 3;
private final static int THURSDAY = 4;
private final static int FRIDAY = 5;
private final static int SATURDAY = 6;
private final static int SUNDAY = 7;
public static void main(String[] args) {
int day = EnumExample.MONDAY;
switch (day) {
case EnumExample.MONDAY:
System.out.println("월요일 입니다");
break;
case EnumExample.TUESDAY:
System.out.println("화요일 입니다.");
break;
case EnumExample.WEBNESDAY:
System.out.println("수요일 입니다.");
break
}
}
}
인터페이스 상수
interface는 반드시 추상 메소드만 선언할 수 있는 것이 아니다. 인터페이스 내에서도 상수를 선언할 수 있는데, 인터페이스에 정의된 상수는 자동으로 public static final로 간주되므로, 명시적으로 이를 지정하지 않아도 된다.
interface DAY {
int MONDAY = 1;
int TUESDAY = 2;
int WEBESDAY = 3;
int THURSDAY = 4;
int FRIDAY = 5;
int SATURDAY = 6;
int SUNDAY = 7;
}
interface MONTH {
int JANUARY = 1;
int FEBRUARY = 2;
int MARCH = 3;
int APRIL = 4;
int MAY = 5;
int JUNE = 6;
int JULY = 7;
int AUGUST = 8;
int SEPTEMBER = 9;
int OCTOBER = 10;
int NOVEMBER = 11;
int DECEMBER = 12;
}
인터페이스도 상수를 정의하는 방법에 문제점
상수가 결국 정수값이라는 문제(명확하지 않은 의도)
- 상수는 변하지 않는 값이라는 의미이지만 고유한 값이라는 의미도 어느정도 내포하고 있다.
- 그래서 아래 코드처럼 다른 집합에 정의된 상수들끼리 서로 비교하는 로직이 가능하거나, 잘못된 상수가 할당되었음에도 결국은 정수값이기 때문에 컴파일 에러없이 실행된다는 점이다.
상속의 남용
- 인터페이스 상수를 사용하는 클래스는 종종 불필요하게 인터페이스를 구현해야 하는 경우가 있습니다. 이는 코드의 복잡성을 증가시키고, 실제로는 상수만 필요로 하는 클래스가 불필요하게 인터페이스를 구현하게 됩니다.
public static void main(String[] args) {
int day = DAY.MONDAY;
//상수를 비교하는 논리적으로 잘못된 행위를 함으로써 day 변수에 다른 상수 값이 들어가 버림
if (DAY.MONDAY == MONTH.JANUARY) {
day = MONTH.JANUARY;
}
// day 변수에 있는 상수는 MONTH 상수이기 때문에 조건문에서 걸러져야 되지만,
// 결국 정수값이기 때문에 에러가 안나고 그대로 수행됨 -> 리펙토링의 어려움
switch (day) {
case DAY.MONDAY:
System.out.println("월요일 입니다");
break
case DAY.TUESDAY:
System.out.println("화요일 입니다");
break
case DAY.WEDNESDAY:
Sytem.out.println("수요일 입니다.");
break;
}
}
자체 클래스 상수
상수를 정수 값으로 구성하는 것이 아니라 독립된 고유 객체로 선언하는 취지로 자체 클래스 인스턴스화를 이용해 상수처럼 사용하는 기법
자기 자신 객체를 인스턴스화하고 final static화 함으로써 고유 객체를 얻게 되어 이를 상수처럼 활용하는 것
class Day {
// 자기자신 객체를 인스턴스화하고 final static화 함으로써 고유 객체 상수를 얻게 됌
public final static Day MONDAY = new Day();
public final static Day TUESDAY = new Day();
public final static Day WEDNESDAY = new Day();
}
class Month {
public final static Month JANUARY = new Month();
public final static Month FEBRUARY = new Month();
public final static Month MARCH = new Month();
}
그러나 가독성이 안좋아졌고, if문에서는 문제는 없지만 switch문에서 사용할 수 없다는 큰 단점 존재
switch 문의 조건에 들어가는 데이터 타입이 제한적
public static void main(String[] args) {
Day day = Day.MONDAY;
// if 문은 문제 없지만
if(day == Day.MONDAY) {
System.out.println("월요일 입니다");
}
//switch문에서는 사용할 수 없다.
switch (day) {
case DAY.MONDAY:
System.out.println("월요일 입니다.");
break;
case DAY.TUESDAY:
System.out.println("화요일 입니다.");
break;
case DAY.WEDNESDAY:
System.out.println("수요일 입니다.");
break;
}
}
enum 상수
이러한 문제들 때문에 자바에서는 아예 상수만을 다루는 enum 타입 클래스를 만들어 배포
enum Day{
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
}
enum Month {
JANUARY, FEBURARY, MARCH, APRIL, MAY, JUNE, JULY,
AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER;
}
enum은 상수를 단순히 정수로 치부하지 않고 객체지향적으로 객체화해서 관리하자는 취지
예를 들어 C언어의 enum은 그냥 정수이며, C++ enum은 타입이지만, JAVA의 enum은 인터페이스와 같이 독립된 특수 클래스로 구분한다.
즉, 일종의 객체이기 때문에 힙 메모리에 저장되며 각 enum 상수들은 별개의 메모리 주소값을 가짐으로써 완벽히 독립된 상수를 구성할 수 있는 것이다.
Enum의 장점
- 코드가 단순해지며 가독성이 좋아진다.
- 허용 가능한 값들을 제한하여 유형 안전을 제공
- 키워드 enum을 사용하기 때문에 구현의 의도가 열거임을 분명하게 나타낼 수 있다.
- 자체 클래스 상수와 달리 switch문에서도 사용할 수 있다.
- 단순 상수와 비교해 IDE 적극적인 지원을 받을 수 있다.
- 단순 상수와 비교해 IDE의 적극적인 지원을 받을 수 있다.
- 리펙토링시 변경 범위가 최소화된다
- enum은 본질적으로 Thread safe인 싱글톤 객체이므로 싱글톤 클래스를 생성하는데에도 사용
추가적으로 enum성능은 어떨가 싶지만 정수 상수와 다르지 않으며 열거 타입을 메모리에 올리는 공간과 초기화 시간이 있지만 체감될 정도는 아님
Enum이 switch문에 사용 가능한 이유
Java의 switch
문은 원래 정수, 문자, 문자열 등의 기본타입에 대해서만 사용이 가능했다.
그러나 enum
타입은 특별한 처리를 통해 switch
문에서 사용할 수 있도록 설계되었다.
enum타입이 switch문에서 사용될 수 있는 이유는 컴파일러가 이를 정수 값으로 변환 처리하기 때문이다.
public enum Day {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}
public class EnumSwitchExample {
public static void main(String[] args) {
Day today = Day.WEDNESDAY;
switch (today) {
case MONDAY:
System.out.println("It's Monday!");
break;
case WEDNESDAY:
System.out.println("It's Wednesday!");
break;
case FRIDAY:
System.out.println("It's Friday!");
break;
default:
System.out.println("It's some other day.");
break;
}
}
}
위와 같은 코드가 있다고 할때, 컴파일러는 switch문에 enum값을 처리하기 위해 내부적으로 다음과 같은 변환 작업을 수행
각 enum 상수에 대해 정수 값을 할당
enum 상수는 선언된 순서대로 0부터 시작하는 정수 값이 할당된다. : 예를 들어
SUNDAY는 0
MONDAY는 1
TUESDAY는 2
WEDNESDAY는 3
THURSDAY는 4
FRIDAY는 5
SATURDAY는 6
switch 문을 정수 값 기반으로 변환한다.
내부적으로 enum 값은 정수 값으로 변환되어 switch문에 사용된다.
이를 위한 코드는 컴파일러에 의해 자동 생성된다.
컴파일러는 switch문을 정수값 기반의 switch문으로 변환한다
내부적으로 생성되는 코드
컴파일러는 내부적으로 보조클래스를 생성하여 enum 상수를 정수값으로 매핑
public class EnumSwitchExample {
// 자동 생성된 보조 클래스
static class SwitchMap {
static final int[] $SwitchMap$Day;
static {
$SwitchMap$Day = new int[Day.values().length];
try {
$SwitchMap$Day[Day.MONDAY.ordinal()] = 1;
} catch (NoSuchFieldError e) { }
try {
$SwitchMap$Day[Day.WEDNESDAY.ordinal()] = 2;
} catch (NoSuchFieldError e) { }
try {
$SwitchMap$Day[Day.FRIDAY.ordinal()] = 3;
} catch (NoSuchFieldError e) { }
}
}
public static void main(String[] args) {
Day today = Day.WEDNESDAY;
switch (SwitchMap.$SwitchMap$Day[today.ordinal()]) {
case 1:
System.out.println("It's Monday!");
break;
case 2:
System.out.println("It's Wednesday!");
break;
case 3:
System.out.println("It's Friday!");
break;
default:
System.out.println("It's some other day.");
break;
}
}
}
결론적으로 요약하면 다음과 같다.
- enum 타입은 정수값으로 변환되어 switch 문에서 사용될 수 있다.
- 컴파일러는 enum 상수에 대해 정수 값을 할당하고, 이를 기반으로 switch문을 변환
- 자동 생성된 보조 클래스를 통해 enum 상수의 정수 값 매핑이 이루어진다.
Enum 선언
열거 타입은 상수 데이터들의 집합이라고 하였다. 따라서 아래와 같이 마치 배열 처럼 나열하여 표현
- enum 명은 클래스와 같이 첫문자를 대문자로 하고 나머지는 소문자로 구성한다.
- 관례적으로 열거 상수는 모두 대문자로 작성
- 열거 상수가 여러 단어로 구성될 경우, 단어 사이를 밑줄(_)로 연결한다
//요일 열거 타입
enum Week {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
// 계절 열거 타입
enum Season {
Spring,
Summer,
Autumn,
Winter
}
enum LoginResult {
LOGIN_SUCCESS,
LOGIN_FAILED
}
Enum 메소드 종류
String 같은 자바의 여러 클래스가 자체 내장 메소드를 가지고 있듯이, enum 역시 내장 메소드를 지니고 있다.
모든 Enum타입은 컴파일 시에 java.lang.Enum 클래스를 상속하게 되어있기 때문에, java.lang.Enum에 선언된 메소드를 이용할 수 있다.
enum Week {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
Enum 객체가 가지는 메소드는 아래와 같다.
name()
: 열거 객체의 문자열을 리턴 → String
- 열거 객체가 가지고 있는 문자열을 리턴
- 반환되는 문자열은 열거 타입을 정의할 때 사용한 상수 이름과 동일
Week w = Week.FRIDAY;
//열거 객체의 문자열을 리턴
String weekName = w.name();
System.out.println(weekName); //Spring
ordinal()
: 열거 객체의 순번(0부터 시작)을 리턴 → int
- 열거 타입을 정의할 때 주어진 순번을 리턴
- 전체 열거 객체중 몇번째 열거 객체인지 알려준다.
Week w = Week.FRIDAY;
// 열거 객체의 순번(0부터 시작)을 리턴
// 전체 열거 객체 중 몇 번째 열거 객체인지 알려준다.
int weekNum = w.ordinal();
System.out.println(weekNum); //4
compareTo()
: 열거 객체를 비교해서 순번 차이를 리턴 → int
- 매개값으로 주어진 열거 객체를 비교해서 순번 차이를 리턴
- 열거 객체가 매개값의 열거 객체보다 순번이 빠르다 → 음수를 리턴
- 열거 객체가 매개값의 열거 객체보다 순번이 늦다 → 양수를 리턴
// 열거 객체를 비교해서 순번 차이를 리턴 (시작점을 어느 열거 객체의 기준으로 몇번째 위치하는지)
Week w1 = Week.TUESDAY; // 2
Week w2 = Week.SATURDAY; // 6
// 열거 객체가 매개값의 열거 객체보다 순번이 빠르다 -> 음수를 리턴
int compare1 = w1.compareTo(w2) // SATURDAY 기준으로 TUESDAY 위치 ( 6에서 2가 되기 위한 값 )
System.out.println(compare1); // -4
// 열거 객체가 매개값의 열거 객체보다 순번이 늦다 -> 양수를 리턴
int compare2 = w2.compareTo(w1); // TUESDAY 기준으로 SATURDAY 위치 (2에서 6가 되기 위한 값)
System.out.println(compare2); // 4
valueOf(String name)
: 문자열을 입력받아서 일치하는 열거 객체를 리턴 → enum
- 매개값으로 주어지는 문자열과 동일한 문자열을 가지는 열거 객체를 리턴
// 문자열을 입력받아서 일치하는 열거 객체를 리턴
Week w3 = Week.valueOf("SUNDAY"); // w3 변수는 Week.SUNDAY 열거 객체를 참조하게 됨
System.out.println(w3); // SUNDAY
values()
: 모든 열거 객체들을 배열로 리턴 → enum[]
- 열거 타입의 모든 열거 객체들을 배열로 만들어 리턴
// 모든 열거 객체들을 배열로 리턴
Week[] w4 = Week.values();
System.out.println(Arrays.toString(w4)); //[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY]
for (Week type : Week.values()) { // 열거 순회
System.out.println(type); // 순서대로 열거 객체 출력
}
Java.lang.Enum 클래스
모든 클래스가 Object 클래스를 자동 상속하는 것처럼, Enum 클래스도 무조건 java.lang.Enum이라는 클래스의 상속을 받는다.
그리고 java.lang.Enum 클래스에 정의되어 있는 메소드를 가져와 사용하는 것
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
private final String name;
private final int ordinal;
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
}
- String name : Enum(열거) 상수의 이름
- int ordinal : Enum의 순서, 상수가 선언된 순서대로 0부터 증가한다.
- protected Enum 생성자 : 컴파일러에서 자동으로 호출되도록 해놓은 생성자이다. 하지만, 개발자가 이 생성자를 호출할 수는 없다.
마찬가지로 java.lang.Enum 클래스도 Object 클래스를 자동 상속해서, enum에서도 Object 클래스의 메소드를 그대로 사용이 가능하다.
하지만 Enum클래스는 개발자들이 Object클래스 중 다음 4개의 메소드를 오버라이딩하지 못하도록 메소드를 final화하여 막아놓았다. 왜냐하면 enum은 고유한 상수이기 때문에 오버라이딩해서 자기 마음대로 바꿔버리면 고유성이 깨지기 때문이다.
- clone() : 객체를 복제하기 위한 메소드. 하지만, 이 메소드는 enum 클래스에서 사용하면 안된다. 만약 호출될 경우엔 CloneNotSupportedException이라는 예외를 발생시키도록 되어있다.
- finalize() : GC가 발생할 때 처리하기 위한 메소드. 이 메서드를 사용하여 객체가 소멸되기 전에 자원을 정리하거나 특정 작업을 수행할 수 있다. 그러나 enum은 그 자체로 불변 객체이기 때문에 가비지 컬렉션 과정에서 특별한 자원 정리가 필요하지 않다. 또한, enum의 인스턴스는 JVM에 의해 관리되고 프로그램 실행동안 일정한 수의 인스턴스만 존재
- hashCode() : int 타입의 해시 코드 값을 리턴하는 메소드. 객체를 해시기반 컬렉션(HashMap, HashSet)에 사용할 때 필요. enum의 각 인스턴스는 고유하기 때문에 기본적으로 상속된 hashCode() 메서드가 각 인스턴스를 고유하게 식별하는데 충분
- equals() : 두개의 객체가 동일한지를 확인하는 메소드
public String toString() { return name; }
public final boolean equals(Object other) { return this == other; }
public final int hashCode() { return super.hashCode(); }
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
public final int compareTo(E o) {
Enum<?> other = (Enum<?>)o;
Enum<E> self = this;
if (self.getClass() != other.getClass() && //optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
Enum 매핑
Season이라는 4계절 상수를 저장한 enum이 있는데, 만일 SPRING 상수를 가져오면 “봄”이라는 상수의 고유값을 문자열로 출력하게 만들고 싶을때 enum을 매핑해서 구성해줄 수 있다.
// enum 매핑 클래스
enum Season {
SPRING("봄"),
SUMMER("여름"),
Autumn("가을"),
WINTER("겨울");
// 문자열을 저장할 필드
private String season;
// 생성자 (싱글톤)
private Season(String season) {
this.season = season;
}
//getter
public String getSeason() {
return season;
}
}
public static void main(String[] args) throws Exception {
Season s = Season.SUMMER;
System.out.println(s.name()); // 열거 객체명 출력 : SUMMER
System.out.println(s.getSeason()); // 매핑된 열거 데이터 출력 : 봄
}
열거 객체의 SPRING("봄")
에서 “봄” 문자열이 enum 클래스 생성자 입력값으로 들어가 private String season 필드에 담기게 된다. 그래서 열거 객체 변수에서 getter 메서드 getSeason() 를 통해 해당 열거 객체에 맵핑된 문자열을 가져올 수 있는 것이다.
enum 내부 구성 이해하기
단순히 열거 상수 집합일 것이라 생각했지만 이런식으로 사용이 가능한 이유는
Enum 상수 하나당 자신의 인스턴스를 하나씩 만들어 public static final 필드로 공개하기 때문이다.
즉, Season 이라는 열거형 타입을 우리가 아는 클래스로 표현하자면 다음과 같이 표현 가능.
// 타입 안전 열거 패턴
final class Season {
public static final Season SPRING = new Season("SPRING"); // 자기 자신의 인스턴스를 만들어 상수화
public static final Season SUMMER = new Season("SUMMER");
public static final Season AUTUMN = new Season("AUTUMN");
public static final Season WINTER = new Season("WINTER");
private String season;
private Season(String season) {
this.season = season;
}
public String getSeason() {
return season;
}
}
이처럼 각 enum 객체마다 자신의 클래스를 인스턴스화하여 저장하니 enum을 매핑한다거나 추상메소드 확장한다거나 등 응용이 가능하다.
그리고 위 코드를 보면 생성자 자체가 private 접근제어자이기 때문에, 밖에 접근할 수 있는 생성자를 제공하지 않으므로 사실상 final이 되게 된다. 그래서 클라이언트가 인스턴스를 직접 생성하거나 확장할 수 없으니 열거 타입 선언으로 만들어진 인스턴스들은 딱 하나씩만 존재함이 보장된다.
Enum 클래스 로딩 과정
enum 타입은 일반 클래스와 다르게 특별한 관리가 필요하기 때문에, 다음과 같은 방식으로 처리된다.
enum의 정의
public enum Day {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}
클래스 로드
JVM은 Day enum 클래스를 로드한다. 이과정에서 Day.class
파일을 찾아 메모리에 로드한다.
클래스 로더는 Day
enum 타입을 java.lang.Enum
클래스를 상속 받는 특별한 클래스로 처리
링크
- 검증 : 클래스 파일의 형식이 올바른지 확인
- 준비 : 정적 필드를 위한 메모리를 할당하고, 기본값으로 초기화한다.
- 해결 : 클래스의 상수 풀 내의 심볼릭 참조를 실제 참조로 변환초기화
초기화
클래스 초기화 단계에서 enum 타입의 각 상수는 유일한 인스턴스로 생성
이 과정에서 JVM은 enum 상수를 정적필드로 초기화한다.
public final class Day extends Enum<Day> {
public static final Day SUNDAY = new Day("SUNDAY", 0);
public static final Day MONDAY = new Day("MONDAY", 1);
public static final Day TUESDAY = new Day("TUESDAY", 2);
public static final Day WEDNESDAY = new Day("WEDNESDAY", 3);
public static final Day THURSDAY = new Day("THURSDAY", 4);
public static final Day FRIDAY = new Day("FRIDAY", 5);
public static final Day SATURDAY = new Day("SATURDAY", 6);
private static final Day[] VALUES = {SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY};
private Day(String name, int ordinal) {
super(name, ordinal);
}
public static Day valueOf(String name) {
for (Day day : VALUES) {
if (day.name().equals(name)) {
return day;
}
}
throw new IllegalArgumentException("No enum constant " + name);
}
}
enum은 데이터의 그룹화 및 관리에 용이
데이터들은 서로 관련되어 있지만 관련된 형태를 구현하는데 있어 에러사항이 생긴다면 enum을 통해 한 클래스 내에서 관리할 수 있게 된다.
- 예를 들어 체크카드를 다룬다고 한다면, 카드사마다 다르며 신한카드를 쓰더라도 또 여러가지 카드 종류가 잇을텐데, 본래라며 카드사 클래스와 카드 종류 클래스 이렇게 두개 나누어 서로 연관되게 코드를 구성해야 된다.
하지만, enum을 이용하면 한눈에 보기 쉽게 아래처럼 구성이 가능하여 관계를 가시적으로 표현할 수 있다.
그리고 적절하게 메소드 로직을 enum 객체 내 안에 구현해준다면 강력한 상수 클래스를 구현할 수 있게 된다.
enum CreditCard {
SHINHAN("신한", Arrays.asList("Mr.Life 카드", "Deep Dream 카드", "Deep Oil 카드")),
KB("국민", Arrays.asList("톡톡 카드", "티타늄 카드", "다담 카드")),
NH("농협", Arrays.asList("올바른 Flex 카드", "테이크 5 카드", "NH 올원 파이카드"));
private final String Enterprise;
private final List<String> cards;
CreditCard(String name, List<String> cards) {
this.Enterprise = name;
this.cards = cards;
}
String getCard(String cardName) {
return Arrays.stream(CreditCard.values())
.filter(creditCard -> creditCard.equals(cardName))
.findFirst()
.orElseThrow(Exception::new);
}
}
Enum 의 확장
enum 매핑기능 확장하여 enum을 단순 상수값을 넘어서 상수 메소드로서도 이용이 가능하다.
위 enum 매핑은 클래스 필드와 생성자를 정의해서 상수의 매개변수값을 필드에 저장하고 인스턴스화함 수으로써 고유한 상수 객체로써 활용했었다.
여기서 더 나아가 추상메소드를 정의해서 각 상수마다 익명클래스(lambda) 처럼 메소드 재정의를 하게 해서 각 상수마다 다른 역할을 하는 메소들를 갖게 되는 원리이다.
enum Operation {
PLUS("+") {
public double apply(double x, double y) {
return x+y;
}
},
MINUS("-") {
public double apply(double x, double y) {
return x-y;
}
},
MULTI("*") {
public double apply(double x, double y) {
return x * y;
}
},
DIVIDE("/") {
public double apply(double x, double y) {
return x/y;
}
};
private final String symbol;
Operation(String symbol) {
this.symbol = symbol;
}
// toString을 재정의하여 열거 객체의 매핑된 문자열을 반환하도록
@Override
public String toString(){
return symbol;
}
// 열거 객체의 메소드에 사용될 추상메소드 정의
public abstract double apply(double x, double y);
}
public static void main(String[] args) {
double x = 2.5;
double y = 5.0;
//Operation 상수집합의 PLUS 상수를 정의
Operation plus = Operation.PLUS;
// enum 매핑값 출력
String name = plus.toString();
System.out.println(name);
// Operation 상수 집합의 PLUS상수를 정의
Operation multi = Operation.MULTI;
String name = multi.toString();
System.out.println(name);
// enum 확장 메소드 실행
double result2 = multi.apply(x,y); //곱셈을 수행하는 메소드
System.out.println(result2);
}
enum은 상태와 행위를 한곳에서 관리 가능
이처럼 enum을 단순히 상수표현식을 넘어서 동작, 행위를 수행하는 상수로서도 응용확장이 가능하다.
상수 상태와 행위를 한 곳에서 관리가 가능한 것이다.
예를 들어 각 상수를 특정 데이터와 연결 짓거나 상수마다 다르게 동작하게 하는 로직이 필요할때, 보통이라면, 단순히 if문, switch문으로 상수를 분기하여 각 메소드들을 실행하는 것이 일반적인 로직이기 마련
그러나 코드 구성은 값(상태)과 메소드(행위)가 어떤 관계가 있는지에 대해 코드 정의문을 찾아 기웃거리며 파악하는 시간이 걸림
class Operation {
public void calculate(String oper) {
if("+".equals(oper)) {
plus();
} else if("-".equals(oper)){
minus();
} else if("*".equals(oper)) {
multi();
} else if("/".equals(oper)) {
divide();
}
}
void plus() { ... }
void minus() { ... }
void multi() { ... }
void divide() { ... }
}
하지만 enum을 이용하면 상태와 행위를 한누에 파악이 가능하다.
객체 지향적으로 코드를 패턴화하여 유지보수가 용이하게 만드는 것으로 보면 된다.
enum Operation {
PLUS("+") {
public double apply(double x, double y) {
return x + y;
}
},
MINUS("-") {
public double apply(double x, double y) {
return x - y;
}
},
MULTI("*") {
public double apply(double x, double y) {
return x * y;
}
},
DIVIDE("/") {
public double apply(double x, double y) {
return x / y;
}
};
람다식으로 enum을 표현할 수 있다.
결국은 enum에 익명 클래스를 적용한 것과 같으니 람다표현식을 통해 위의 긴 코드를 깔끔하게 만들 수 있음
// 함수형 인터페이스 임포트
import java.util.function.DoubleBinaryOperator;
enumj Operation {
PLUS("+", (x,y) -> x+y),
MINUS("-", (x,y) -> x-y),
MULTI("*", (x,y) -> x*y),
DIVIDE("/", (x,y) -> x/y);
private final DoubleBinaryOperator op; //람다식을 저장할 필드
private final String symbol;
Operation(String symbol, DoubleBinaryOperator op) {
this.symbol = symbol;
this.op = op;
}
@Override
public String toString() { return symbol; }
// apply메소드가 호출되면 함수형 인터페이스 applyAsDouble메소드가 호출됌
public double apply(double x, double y) {
return op.applyAsDouble(x,y);
}
}