...
Enum 열거 타입
먼저 Enum은 "Enumeration"의 약자다. Enumeration은 "열거, 목록, 일람표" 라는 뜻을 가지고 있으며, 보통 한글로는 열거형이라고 부른다. 즉, 열거형(enum)은 요소, 멤버라 불리는 명명된 값의 집합을 이루는 자료형이다. 어렵게 생각할 필요 없이 상수 데이터들의 집합 으로 치부하면 된다.
먼저 Enum은 "Enumeration"의 약자다.
Enumeration은프로그래밍을 하다보면 배열이나 리스트 ..등 여러개의 묶음 데이터를 다루는 일이 빈번하다. 이 묶음 데이터 중에는 데이터 주제에 따라 몇가지로 한정된 값만을 가지는 경우가 존재한다.
대표적으로는 '요일' 이나 '계절' 또는 '주사위' 같은 예제를 들 수 있다. 요일은 월,화,수,목,금,토,일 이렇게 7가지 밖에 없고, 계절도 봄,여름,가을,겨울 4가지 계절로 한정되어 있으며, 주사위는 1,2,3,4,5,6 숫자가 6개로만 구성되어 있다.
이와 같이 정해져 있는 한정된 데이터 묶음을 열거형 타입인 Enum 으로 묶어주면 보다 구조적으로 프로그래밍 할 수 있다.
과거엔 상수를 어떻게 정의했는가?
final 상수
가장 먼저 떠오르는 것은 final 제어자를 이용해 변수를 상수화 하는 것일 것이다.
final 제어자를 할당함으로써을 사용하여 한번 지정하면 바뀌지 않게 설정되며, 동시에 static을 사용하여 메모리에 한 번만 할당 되게 설정된다. 하지만 이 방법은 접근제어자들 때문에 가독성이 그렇게 좋지 못하다라는 단점이 존재한다.
class EnumExample {
private final static int MONDAY = 1;
private final static int TUESDAY = 2;
private final static int WEDNESDAY = 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.WEDNESDAY:
System.out.println("수요일 입니다.");
break;
}
}
}
인터페이스 상수
interface는 반드시 추상 메소드만 선언할 수 있는 것이 아니다. 인터페이스 내에서도 상수를 선언할 수 있는데, 인터페이스의 멤버는 public static final 속성을 생략할 수 있는 특징을 이용하여 코드를 조금 더 간결하게 작성할 수 있게 된다.
interface DAY {
int MONDAY = 1;
int TUESDAY = 2;
int WEDNESDAY = 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:
System.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 으로 바꿔보면 다음과 같다.
enum Day{
MONDAY,TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
}
enum Month{
JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY,
AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER;
}
enum의 핵심은 이러한 상수를 단순히 정수로 치부하지말고 객체 지향적으로 객체화해서 관리하자는 취지이다.
예를들어 C언어의 enum은 그냥 정수이며, C++의 enum은 타입이지만, JAVA의 enum은 인터페이스와 같이 독립된 특수한 클래스로 구분한다. 즉, 일종의 객체이기 때문에 힙(heap) 메모리에 저장되며 각 enum 상수들은 별개의 메모리 주소값을 가짐으로써 완벽히 독립된 상수를 구성할 수 있는 것이다.
또한 IDE에서 클래스 파일을 생성할때 독립된 열거형 파일도 생성할 수 있다. (독립된 클래스이기 때문에)
Enum의 장점
- 코드가 단순해지며 가독성이 좋아진다
- 허용 가능한 값들을 제한하여 유형 안전(type safe)을 제공한다.
- 키워드 enum을 사용하기 때문에 구현의 의도가 열거임을 분명하게 나타낼 수 있다.
- 자체 클래스 상수와 달리 switch문에서도 사용할 수 있다
- 단순 상수와 비교해 IDE의 적극적인 지원을 받을 수 있다 (자동완성, 오타검증, 텍스트 리팩토링 등등)
- 리팩토링시 변경 범위가 최소화 된다 (enum에서 한번에 관리하기 때문에 내용의 추가가 필요하더라도, Enum 코드외에 수정할 필요가 없다)
- enum은 본질적으로 Thread safe인 싱글톤 객체 이므로 싱글톤 클래스를 생성하는데에도 사용된다
추가적으로 enum 성능은 어떨가 싶지만, 정수 상수와 다르지 않으며 열거 타입을 메모리에 올리는 공간과 초기화 시간이 있지만 체감될 정도는 아니다.
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 참조 방식
Enum 타입 객체도 하나의 데이터 타입이므로 변수를 선언하고 사용하면 된다.
// 열거타입 변수 = 열거타입.열거상수;
Week monday = Week.MONDAY;
Week sunday = Week.SUNDAY;
한가지 알아둘 점은 enum 타입은 특수한 클래스 라는 점이다.
즉, primitive 타입이 아닌 referece 타입으로 분류되며, 그래서 enum 상수값은 힙(heap) 영역에 저장되게 된다.
String 처럼 스택 영역에 있는 변수들이 힙 영역에 있는 데이터의 주소값을 저장함으로써 참조 형태를 띄게 된다. 그래서 다음과 같이 같은 enum 타입 변수 끼리 같은 상수 데이터를 바라봄으로써 둘이 주소를 비교하는 == 연산 결과는 true가 되게 된다.
Week today = null; // 참조 타입이기 때문에 null도 저장 가능
today = Week.SUNDAY;
// 주소값 비교
System.out.println(today == Week.SUNDAY); // true
마찬가지로 enum 상수들을 배열로 만들어 저장할시에도, 각 배열 원소들 마다 참조 주소값들이 저장되어 힙 영역의 상수 데이터들을 가리키게 된다.
// enum Week 의 모든 상수값들을 배열로 변환
Week[] days = Week.values();
Enum 메소드 종류
String 같은 자바의 여러 클래스가 자체 내장 메소드를 가지고 있듯이, enum 역시 내장 메소드를 지니고 있다.
모든 Enum 타입은 컴파일 시에 java.lang.Enum 클래스를 상속하게 되어있기 때문에, java.lang.Enum 에 선언된 메소드를 이용할 수 있다.
Enum 객체가 가지는 메소드는 아래와 같다
메소드 | 설명 | 리턴 타입 |
name() | 열거 객체의 문자열을 리턴 | String |
ordinal() | 열거 객체의 순번(0부터 시작)을 리턴 | int |
compareTo() | 열거 객체를 비교해서 순번 차이를 리턴 | int |
valueOf(String name) | 문자열을 입력받아서 일치하는 열거 객체를 리턴 | enum |
values() | 모든 열거 객체들을 배열로 리턴 | enum[] |
enum Week {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
name() 메소드
- 열거 객체가 가지고 있는 문자열을 리턴
- 반환되는 문자열은 열거 타입을 정의할 때 사용한 상수 이름과 동일
Week w = Week.FRIDAY;
// 열거 객체의 문자열을 리턴
String weekName = w.name();
System.out.println(weekName); // Spring
ordinal() 메소드
- 열거 타입을 정의 할 때 주어진 순번을 리턴
- 전체 열거 객체 중 몇 번째 열거 객체인지 알려준다
Week w = Week.FRIDAY;
// 열거 객체의 순번(0부터 시작)을 리턴
// 전체 열거 객체 중 몇 번째 열거 객체인지 알려준다
int weekNum = w.ordinal();
System.out.println(weekNum); // 4
compareTo() 메소드
- 매개값으로 주어진 열거 객체를 비교해서 순번 차이를 리턴
- 열거 객체가 매개값의 열거 객체보다 순번이 빠르다 → 음수를 리턴
- 열거 객체가 매개값의 열거 객체보다 순번이 늦다 → 양수를 리턴
// 열거 객체를 비교해서 순번 차이를 리턴 (시작점을 어느 열거 객체의 기준으로 몇번째 위치하는지)
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() 메소드
- 매개값으로 주어지는 문자열과 동일한 문자열을 가지는 열거 객체를 리턴
// 문자열을 입력받아서 일치하는 열거 객체를 리턴
Week w3 = Week.valueOf("SUNDAY"); // w3 변수는 Week.SUNDAY 열거 객체를 참조하게 됨
System.out.println(w3); // SUNDAY
values() 메소드
- 열거 타입의 모든 열거 객체들을 배열로 만들어 리턴
// 모든 열거 객체들을 배열로 리턴
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 클래스에 정의되어 있는 메소드를 가져와 사용하는 것이다.
- 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가 발생할 때 처리하기 위한 메소드 |
hashCode() | int 타입의 해시 코드 값을 리턴하는 메소드 |
equals() | 두 개의 객체가 동일한지를 확인하는 메소드 |
Enum 고급 문법
Enum 매핑
Season 이라는 4계절 상수를 저장한 enum이 있는데, 만일 SPRING 상수를 가져오면 "봄"이라는 상수의 고유의 값(value)을 문자열을 출력하게 만들고 싶을때 enum을 매핑해서 구성해줄 수 있다.
// enum 매핑 클래스
enum Season {
SPRING("봄"),
SUMMER("여름"),
FALL("가을"),
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 상수 객체의 매개값으로 반드시 한개만 들어갈 필요가 없다. 3개 정보 매핑이 필요하다면 enum 클래스의 생성자의 아규먼트를 3개로 지정하면 되는 일이다.
enum 내부 구성 이해하기
단순히 열거 상수 집합인줄 알았는데 enum을 이런식으로 이용이 가능한 이유는, 사실 enum 상수 하나당 자신의 인스턴스를 하나씩 만들어 public static final 필드로 공개하기 때문이다.
즉, Season 이라는 열거형 타입을 우리가 아는 클래스로 표현하자면 다음과 같이 표현이 가능하다는 것이다.
/* 타입 안전 열거 패턴(typesafe enum pattern) */
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이 나오기전 jdk 1.5 밑 버전에서는 이렇게 사용되었다.
이처럼 각 enum 객체마다 자신의 클래스를 인스턴스화 하여 저장하니 enum을 매핑한다거나 추상메소드 확장한다거나 등 응용이 가능한 것이다.
그리고 위의 코드를 보면 생성자 자체가 private 접근제어자 이기 때문에, 밖에서 접근할 수 있는 생성자를 제공하지 않으므로 사실상 final이 되게 된다. 그래서 클라이언트가 인스턴스를 직접 생성하거나 확장할 수 없으니 열거 타입 선언으로 만들어진 인스턴스들은 딱 하나씩만 존재함이 보장된다.
enum은 데이터의 그룹화 및 관리에 용이
데이터들이 서로 관련되어 있지만 관련된 형태를 구현하는데 있어 애로사항이 생긴다면 enum을 통해 한 클래스 내에서 관리할 수 있게 된다.
예를들어 체크 카드를 다룬다고 한다면, 카드사 마다 다르며 신한 카드를 쓰더라도 또 여러가지 카드 종류가 있을텐데, 본래라면 '카드사' 클래스와 '카드종류' 클래스 이렇게 두개로 나누어 서로 연관되게 코드를 구성해야 된다.
하지만 enum을 이용하면 한눈에 보기 쉽게 아래 처럼 구성이 가능하여 관계를 가시적으로 표현할 수 있다. 그리고 적절하게 메소드 로직을 enum 객체 내 안에 구현해준다면 강력한 상수 클래스를 구현할 수 있게 된다.
enum CreditCard {
SHINHAN("신한", Arrays.asList("Mr.Life 카드", "Deep Dream 카드", "Deep Oil 카드")),
KB("국민", Arrays.asList("톡톡D 카드", "티타늄 카드", "다담 카드")),
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 매핑은 클래스 필드와 생성자를 정의해서 상수의 매개변수값을 필드에 저장하고 인스턴스화 함으로써 고유한 상수 객체로서 활용했었다.
여기서 더 나아가 추상 메소드를 정의해서 각 상수마다 익명 클래스 처럼 메소드 재정의를 하게 해서 각 상수마다 다른 역할을 하는 메소드를 갖게 되는 원리이다.
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); // +
// enum 확장 메소드 실행
double result = plus.apply(x, y); // 덧셈을 수행하는 메소드 (Operation.PLUS.apply(x, y) 로 해도됨)
System.out.println(result); // 7.5
// ------------------------------------------------------------------- //
// Operation 상수집합의 PLUS 상수를 정의
Operation multi = Operation.MULTI;
String name = plus.toString();
System.out.println(name); // *
// enum 확장 메소드 실행
double result2 = multi.apply(x, y); // 곱셈을 수행하는 메소드
System.out.println(result2); // 12.5
}
enum은 상태와 행위를 한곳에서 관리가 가능
이처럼 enum을 단순히 상수 표현식을 넘어서 동작, 행위를 수행하는 상수로서도 응용 확장이 가능하다. 상수 상태와 행위를 한 곳에서 관리가 가능한 것이다.
처음 배울때는 글쓴이도 공감할정도로 매우 혼란스럽겠지만, 이 부분은 왜 자바의 Enum이 강력한지 보여주는 대목이기도 한다.
예를들어 각 상수를 특정 데이터와 연결짓거나, 상수마다 다르게 동작하게 하는 로직이 필요할때, 보통이라면 단순히 if문, switch문으로 상수를 분기하여 하여 각 메소드들을 실행하는 것이 일반적인 로직이기 마련이다.
그러나 이러한 코드 구성은 값(상태)과 메소드(행위)가 어떤 관계가 있는지에 대해 코드 정의문을 찾아 기웃거리며 파악해야 하는 시간이 걸린다.
class Operation {
public void caclulate(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을 더 깔끔하게
결국은 enum에 익명 클래스를 적용한 꼴과 같으니, 람다 표현식을 통해 위의 기나긴 코드를 깔끔하게 만들 수 있다.
// 함수형 인터페이스 임포트
import java.util.function.DoubleBinaryOperator;
enum Operation {
PLUS("+", (x, y) -> x + y),
MINUS("-", (x, y) -> x - y),
TIMES("*", (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; }
public double apply(double x, double y) {
return op.applyAsDouble(x, y);
}
}
Enum 과 싱글톤 관계
자바에서의 enum 열거 타입은 일종의 클래스이며 상수 하나당 자신의 인스턴스를 하나씩 만들어 public static final 필드로 공개하게 된다. 하지만 enum은 클래스처럼 이용할수는 있지만 인스턴스화는 할수는 없는데, 실제로 new 키워드로 인스턴스 생성을 하려고 하면 에러가 난다.
이러한 제약적인 특징을 가지고 있는 이유는 enum 타입은 고정된 상수들의 집합으로써, 런타임(run-time)이 아닌 컴파일타임(compile-time)에 모든 값을 알고 있어야 하는 규칙이 있기 때문이다. 즉, 다른 패키지나 클래스에서 enum 타입에 접근해서 변수 처럼 동적으로 어떠한 값들을 할당해 주는 행위는 금지된 것이다.
이 때문에 enum 객체의 생성자의 접근제어자를 private으로 설정해야 한다. 이렇게 되면 외부에서 접근 가능한 생성자가 없으므로 enum타입은 실제적으로 final 클래스와 다름이 없게 된다.
이러한 특성 때문에, enum타입은 싱글톤을 구현하는 하나의 방법으로 사용되기도 한다.
enum SingletonEnum {
INSTANCE;
private final Client dbClient;
SingletonEnum() {
dbClient = Database.getClient();
}
public static SingletonEnum getInstance() {
return INSTANCE;
}
public Client getClient() {
return dbClient;
}
}
public class Main {
public static void main(String[] args) {
SingletonEnum singleton = SingletonEnum.getInstance();
singleton.getClient();
}
}
# 참고자료
https://www.nextree.co.kr/p11686/
이것이 자바다 - 신용권의 Java 프로그래밍 정복
https://github.com/yeGenieee/java-live-study/blob/main/%5B11%5DJava%20Live%20Study.md
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.