해당 글에서는 Java에서 Enum에 대해 이해하고 이를 활용할 수 있는 방안에 대해서 알아봅니다.
1) Enum(Enumerated type) : 열거형
💡 Enum(Enumerated type) : 열거형 - 특정 값들의 집합을 나타내는 데이터 타입을 의미하며 서로 연관되어 있는 ‘미리 정의된 상수 집합’을 표현하는 데 사용되는 특수 클래스입니다.
- 변수가 정해진 명시적인 값을 가질 수 있도록 하며 코드의 가독성을 높이며 안정성을 향상하는데 도움이 됩니다. - Enum의 데이터 구조는 일반적으로 ‘이름’과 ‘값’의 쌍으로 구성되어 있습니다. 각 이름은 고유하고 일반적으로 문자열로 표시되며 값은 숫자입니다. 이러한 이름-값 쌍은 Enum 내에서 중복될 수 없습니다. - 예를 들어 ‘요일’ Enum에서 월요일-1, 화요일-2과 같이 각 요일에 고유한 숫자 값을 할당할 수 있습니다.
💡 사용예시
- Day라는 enum 구조에서는 MONDAY라는 ‘이름(name)’과 월요일이라는 ‘값(value)’으로 구성되어 있습니다.
public enum Day {
MONDAY("월요일"),
TUESDAY("화요일"),
WEDNESDAY("수요일"),
THURSDAY("목요일"),
FRIDAY("금요일"),
SATURDAY("토요일"),
SUNDAY("일요일");
private final String korean;
Day(String korean) {
this.korean = korean;
}
public String getKoreanTranslation() {
return this.korean;
}
}
2) Enum 사용 이전 사용 형태
💡 Enum 사용 이전 사용 형태
- Enum은 Java 1.5부터 사용이 되었습니다. 이를 사용하기 이전에 사용되었던 형태에 대해서 알아봅니다. - final 상수, 인터페이스 상수, 자체 클래스 상수 형태가 있습니다.
1. final 상수
💡 final 상수
- 자바에서 특정 변수가 한 번 초기화되면 그 이후로 변경이 불가능한 상수를 의미합니다.
- 또한 final 키워드 앞에 static 키워드를 붙여서 클래스 레벨에서 공유가 되는 변수가 됩니다. 즉 상수로써 어떤 객체에서도 동일한 값에 접근이 가능한 형태를 의미합니다.
💡 [사용예시 : 선언부]
- 아래는 final 상수를 이용하여 정규식을 구성한 예시입니다.
public class RegExpConstants {
// 1. 문자열만 허용하는 정규식 - 공백 미 허용
public static final String REGEXP_PATTERN_CHAR = "^[\\\\w]*$";
// 2. 문자열만 허용하는 정규식 - 공백 허용
public static final String REGEXP_PATTERN_NO_CHAR = "^[\\\\W]*$";
// 3. 숫자만 허용하는 정규식
public static final String REGEXP_PATTERN_NUMBER = "^[\\\\d]*$";
}
💡 [사용예시 : 접근부]
- 아래는 위에 구성한 final 상수를 호출하여 정규식을 기반으로 검증을 하는 예시입니다. - 구체적으로는 숫자만의 사용하는 REGEXP_PATTERN_NUMBER 정규식을 통해 해당 문자열이 숫자인지 여부를 체크하는 예시입니다.
- 아래와 같이 <<클래스.변수>> 형태로 접근이 가능하거나 즉시 클래스 변수를 import 하여서 사용이 가능합니다.
- 인터페이스에서 정의된 모든 필드에는 자동으로 public, static, final 키워드가 적용되는 것을 의미합니다. 이를 통해 인터페이스 필드는 상수 취급을 받게 됩니다.
- 'public'은 해당 상수가 어디에서든 접근 가능함을 의미하며 'static'은 해당 상수가 인터페이스에 속하며 인스턴스화 없이도 접근할 수 있음을 의미합니다. 'final'은 한번 초기화된 값을 변경할 수 없음을 의미하므로 이 키워드가 붙은 필드는 변하지 않는 상수가 됩니다. - 따라서, 인터페이스 상수는 변경 불가능한 공용 상수로 사용될 수 있습니다.
💡 [사용예시 : 선언부]
- 아래와 같은 정규식을 interface 내에 선언하여 public, static, final로 선언된 상수와 같이 구성하였습니다.
public interface RegExpConstInterface {
// 1. 문자열만 허용하는 정규식 - 공백 미 허용
String REGEXP_PATTERN_CHAR = "^[\\\\w]*$";
// 2. 문자열만 허용하는 정규식 - 공백 허용
String REGEXP_PATTERN_NO_CHAR = "^[\\\\W]*$";
// 3. 숫자만 허용하는 정규식
String REGEXP_PATTERN_NUMBER = "^[\\\\d]*$";
}
💡 [사용예시: 접근부-1] - 숫자만의 사용하는 REGEXP_PATTERN_NUMBER 정규식을 통해 해당 문자열이 숫자인지 여부를 체크하는 예시입니다. - 아래와 같이 <<인터페이스.변수>> 형태로 접근이 가능합니다.
인터페이스 상수는 인터페이스를 구현하는 클래스에서 상속받을 수 있지만, 이로 인해 불필요하거나 예상치 못한 상속 문제가 발생할 수 있습니다.
객체 지향 프로그래밍 원칙 위반
인터페이스는 주로 행동을 정의하는 것이므로, 상수(상태를 정의하는 것)를 인터페이스 내에 정의하는 것은 객체 지향 프로그래밍 원칙에 부합하지 않을 수 있습니다.
3. 자체 클래스 상수
💡 자체 클래스 상수
- 클래스 내부에 상수를 정의하는 방법으로 클래스의 static final 필드를 사용하여 이루어집니다. 해당 클래스와 관련된 상수를 한 곳에 모아 놓을 수 있다는 장점이 있습니다.
💡 [사용예시 : 선언부]
- 자체 클래스의 상수로 RegExp 클래스의 상수로 REGEXP_PATTERN_CHAR, REGEXP_PATTERN_NO_CHAR, REGEXP_PATTERN_NUMBER가 있음을 선언하였습니다.
public class RegExp {
// 1. 문자열만 허용하는 정규식 - 공백 미 허용
public final RegExp REGEXP_PATTERN_CHAR = new RegExp();
// 2. 문자열만 허용하는 정규식 - 공백 허용
public final RegExp REGEXP_PATTERN_NO_CHAR = new RegExp();
// 3. 숫자만 허용하는 정규식
public final RegExp REGEXP_PATTERN_NUMBER = new RegExp();
}
💡 [호출부]
- final 상수나 인터페이스 상수와 다르게 상수 자체만 정의하고 클래스의 인스턴스로써 상수로 사용됩니다.
💡 자체 클래스 상수의 경우 아래와 같은 단점을 가지게 됩니다.
단점
설명
타입 안전성
원시 유형이나 문자열 등 어떤 데이터 타입도 가질 수 있어 타입 안전성 보장이 어렵습니다.
상수 값의 중복
같은 값을 가진 여러 변수를 선언 가능, 실수 유발 가능성 있습니다.
코드 가독성
여러 클래스 상수는 코드 내에서 흩어져 있을 수 있음, 가독성 해칩니다.
4. 종합
💡 종합 - final 상수, 인터페이스 상수, 자체 클래스 상수, ENUM을 각각 비교합니다.
비교항목
final 상수
인터페이스 상수
자체 클래스 상수
Enum
타입 안정성
부족
부족
부족
보장
상수 값의 중복
가능
가능
가능
불가능
코드 가독성
보통
보통
보통
높음
객체 지향 프로그래밍 원칙 적용
가능
부족
가능
가능
싱글톤 패턴 구현
불가능
불가능
불가능
가능
3) Enum의 주요 특징
1. 타입 안정성(Type-safe)
💡 타입 안정성(Type-safe) - Enum 값이 명시적으로 정의되어 있으며 이외의 값은 허용되지 않습니다. 따라서 Enum에 정의되지 않았는데 접근을 하는 경우 ‘컴파일 시점에서 에러를 방지’ 할 수 있습니다.
- 이를 통해 컴파일 시점에 에러를 방지하여 ‘런타임에서 발생하는 에러’를 줄이고 코드의 안정성과 신뢰성을 높이는데 도움이 됩니다.
💡 [사용 예시]
- 아래와 같이 Enum 타입으로 변수를 구성하였습니다.
public enum SeasonEnum {
SPRING,
SUMMER,
FALL,
WINTER
}
💡 위에와 같이 선언된 Enum에 접근하고자 할 때, 컴파일 단계에서 접근 가능한 요소에 대해서 확인이 가능합니다. 그렇기에 런타임 시점에 발생할 수 있는 에러를 방지할 수 있고 ‘타입 안정성’을 가지고 있습니다.
2. 클래스처럼 사용이 됩니다.
💡 클래스처럼 사용이 됩니다 - enum 클래스 내에서는 메서드, 변수, 생성자를 가질 수 있고 클래스가 가질 수 있는 특성을 Enum이 가지고 있기에 클래스처럼 보이게 작성이 됩니다.
public enum Day {
// 변수 영역
MONDAY("월요일"),
TUESDAY("화요일"),
WEDNESDAY("수요일"),
THURSDAY("목요일"),
FRIDAY("금요일"),
SATURDAY("토요일"),
SUNDAY("일요일");
// 변수 영역
private String koreanTranslation;
// 생성자 영역
Day(String koreanTranslation) {
this.koreanTranslation = koreanTranslation;
}
// 메서드 영역
public String getKoreanTranslation() {
return koreanTranslation;
}
}
💡 구성된 Enum을 접근하고자 할때, 메서드로 접근하는 경우 각 요일에 대한 한국어 번역 정보를 얻을 수 있습니다.
- 클래스의 인스턴스가 단 하나만 생성되어야 할 때 사용하는 디자인 패턴입니다. - Enum은 자바에서 싱글톤 패턴을 만드는 가장 안전한 방법으로 간주됩니다. Enum은 싱글톤을 만들 때 직렬화, 스레드 안정성 등에 대한 걱정 없이 싱글톤 패턴을 구현하기 위한 완벽한 솔루션을 제공합니다.
💡 아래와 같이 사용하여 SingletonEnum.INSTANCE는 싱글톤 인스턴스가 되며,SingletonEnum.INSTANCE.performOperation()는 싱글턴 객체의 동작을 정의합니다.
public enum SingletonEnum {
INSTANCE; // 싱글턴 객체를 의미합니다.
// 필요한 멤버 변수와 메서드를 선언할 수 있습니다.
// 싱글턴 객체의 동작을 정의합니다.
public void performOperation() {
// 동작
}
}
- Enum 내부에 메서드를 정의하고 Enum 상수가 이를 오버라이드하게 하여 각 상수가 다른 동작을 하도록 설정할 수 있습니다. 이를 통해 상수별로 다른 동작을 하는 Enum을 만들 수 있습니다.
💡 이런 방식은 각 Enum 상수가 고유한 동작을 가져야 할 때 유용합니다.
public enum Operation {
PLUS {
public int apply(int x, int y) { return x + y; }
},
MINUS {
public int apply(int x, int y) { return x - y; }
},
TIMES {
public int apply(int x, int y) { return x * y; }
},
DIVIDE {
public int apply(int x, int y) { return x / y; }
};
public abstract int apply(int x, int y);
}
4) Enum 메서드
1. Enum 주요 메서드
리턴 타입
메서드
설명
protected Object
clone()
객체의 복제를 생성하여 새로운 인스턴스를 반환합니다.
int
compareTo(E o)
주어진 객체와 이 객체를 비교하고, 순서를 나타내는 정수를 반환합니다.
boolean
equals(Object other)
이 객체와 주어진 객체가 동일한지 확인하고, 동일하면 true를 아니면 false를 반환합니다.
- 호출한 Enum 인스턴스의 이름을 문자열 형태로 반환합니다. 이 이름은 Enum 상수를 선언할 때 사용한 정확한 문자열입니다.
💡 Day.MONDAY.name()을 호출하면 "MONDAY"라는 문자열을 반환합니다.
public enum Day {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
Day.MONDAY.name(); // MONDAY
3. original()
💡 original()
- Enum의 요소가 선언된 순서에 따라 인덱스를 반환합니다. 이 인덱스는 0부터 시작합니다.
💡 "Days.MONDAY.ordinal()"은 0을 반환하고, "Days.TUESDAY.ordinal()"은 1을 반환합니다.
public enum Days {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
Days.MONDAY.ordinal(); // 0
Days.TUESDAY.ordinal(); // 1
4. compareTo()
💡 compareTo() - Enum 인스턴스들 간의 순서를 비교하는 메서드입니다. - 해당 메서드는 호출한 Enum 인스턴스가 인자로 받은 Enum 인스턴스보다 앞에 위치할 경우 음수를, 같은 위치에 있을 경우 0을, 뒤에 위치할 경우 양수를 반환합니다.
public enum Days {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
Days.MONDAY.compareTo(Days.WEDNESDAY); // -2
5. valueOf()
💡 주어진 문자열에 해당하는 Enum 상수를 반환합니다. 이 메서드는 주로 문자열을 Enum 타입으로 변환할 때 사용됩니다.
public enum Days {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
Days.valueOf("MONDAY"); // Days.MONDAY
Days.valueOf("NOTTHING"); // IllegalArgumentException 발생
6. values()
💡 values() - Enum에 속하는 모든 상수를 배열 형태로 반환합니다. 이 메서드는 주로 Enum의 모든 상수를 반복하거나 조회할 때 사용됩니다.
public enum Days {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
for (Object regExp : RegExpEnum.values()) {
regExp.toString(); // MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
5) Enum 메서드 활용 예시
1. Exception 성공 코드 & 에러 코드 관리
💡 사용예시 - API 통신에 대한 '에러 코드'를 Enum 형태로 관리를 한다. - 해당 Enum 형태로 관리하면 정형화된 데이터로 관리할 수 있습니다. SuccessCode.INSERT에 대해서 ‘코드 상태’, ‘코드 값’, ‘코드 메시지’ 형태로 관리가 가능합니다.
package com.adjh.multiflexapi.common.codes;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* [공통 코드] API 통신에 대한 '에러 코드'를 Enum 형태로 관리를 한다.
* Success CodeList : 성공 코드를 관리한다.
* Success Code Constructor: 성공 코드를 사용하기 위한 생성자를 구성한다.
*
* @author lee
*/
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public enum SuccessCode {
/**
* ******************************* Success CodeList ***************************************
*/
// 조회 성공 코드 (HTTP Response: 200 OK)
SELECT(200, "200", "SELECT SUCCESS"),
// 삭제 성공 코드 (HTTP Response: 200 OK)
DELETE(200, "200", "DELETE SUCCESS"),
// 전송 성공 코드 (HTTP Response: 200 OK)
SEND(200, "200", "SEND SUCCESS"),
// 삽입 성공 코드 (HTTP Response: 201 Created)
INSERT(201, "201", "INSERT SUCCESS"),
// 수정 성공 코드 (HTTP Response: 201 Created)
UPDATE(204, "204", "UPDATE SUCCESS"),
; // End
/**
* ******************************* Success Code Constructor ***************************************
*/
// 성공 코드의 '코드 상태'를 반환한다.
private int status;
// 성공 코드의 '코드 값'을 반환한다.
private String code;
// 성공 코드의 '코드 메시지'를 반환한다.
private String message;
// 생성자 구성
SuccessCode(final int status, final String code, final String message) {
this.status = status;
this.code = code;
this.message = message;
}
}
💡 아래와 같이 호출을 할 때는 SuccessCode.INSERT의 메서드 형태인 getStatus(), getCode(), getMessage()로 접근이 가능합니다.
/**
* [API] 코드 등록
*
* @param codeDto codeDto
* @return ResponseEntity<Integer> : 응답 결과 및 응답 코드 반환
*/
@PostMapping("/code")
@Operation(summary = "코드 등록", description = "코드 등록")
public ResponseEntity<ApiResponse<Object>> insertCode(@RequestBody @Validated CodeDto codeDto) throws IOException {
log.debug("코드를 등록합니다.");
int resultList = codeService.insertCode(codeDto);
ApiResponse<Object> ar = ApiResponse.builder()
.result(resultList)
.resultCode(SuccessCode.INSERT.getStatus())
.resultMsg(SuccessCode.INSERT.getMessage()).build();
return new ResponseEntity<>(ar, HttpStatus.OK);
}