REST API에 대해서 다시 공부중인데, 코드 리뷰 중 enum을 사용해보라는 피드백을 받았다.
오늘은 enum 에 대해서 공부한 것들을 정리해보겠다!
Enum Class
보통 상수를 정의할 때는 final로 String 과 같은 문자열이나 숫자들을 나타내는 기본 자료형의 값을 '고정'한다. 하지만 이렇게 상수를 정의해서 코딩을 하게 되면 다양한 문제가 발생하게 된다 (문제점에 대해서는 아래 예시들을 통해 알아보자).
따라서 이러한 문제점을 보완하기 위해 만들어진 것이 Enum 이다. Enum은 enumeration(열거, 목록)의 앞부분을 따서 만든 예약어로, 열거형이라고 불리며 서로 연관된 상수들의 집합을 의미한다.
Enum의 장점
- 코드가 단순해지며, 가독성이 좋다.
- 인스턴스 생성과 상속을 방지하여 상수값의 타입 안정성이 보장된다.
- 새로운 상수들의 타입을 정의함으로, 정의한 타입 이외의 타입을 가진 데이터값을 컴파일시 체크한다.
- 키워드 enum을 사용하기 때문에 구현 의도가 열거임을 분명하게 알 수 있다.
enum은 자바 1.5버전 이후에 나왔다. 그럼 enum 이 없었을 때는 상수를 어떻게 정의했을까?
public class Example {
// 기존에 상수를 정의하는 방법
public static void main(String[] args) {
/*
* 복숭아 == 1
* 포도 == 2
* 사과 == 3
* 딸기 == 4
*/
int fruit = 1;
switch (fruit) {
case 1:
System.out.println("복숭아 입니다.");
break;
case 2:
System.out.println("포도 입니다.");
break;
case 3:
System.out.println("사과 입니다.");
break;
case 4:
System.out.println("딸기 입니다.");
break;
}
}
}
위 코드에서 1은 복숭아, 2는 포도, 3은 사과, 4는 딸기로 정의되어 있어야한다. 변하지 않는 상수의 값에 따라 해당하는 과일을 고정하고 있으나, 상수의 의미를 주석으로 전달하고 있다. 만약 주석이 사라지거나 주석 부분과 상수를 사용하는 부분이 분리된다면 각 숫자가 어떤 의미인지 이해하기 어려워진다.
public class Example {
private final static int Peach = 1;
private final static int Grape = 2;
private final static int Apple = 3;
private final static int Strawberry = 4;
public static void main(String[] args) {
int fruit = strawberry;
switch (fruit) {
case Peach:
System.out.println("복숭아 입니다.");
break;
case Grape:
System.out.println("포도 입니다.");
break;
case Apple:
System.out.println("사과 입니다.");
break;
case Strawberry:
System.out.println("딸기 입니다.");
break;
}
}
}
위 코드처럼 final을 사용하여 한 번 지정하면 바뀌지 않게 설정 및, static을 사용하여 메모리에 한 번만 할당되게 설정했다. 또한 이 경우, 이름이 있어 무엇을 의미하는지 한 번에 알 수 있다. 하지만 문제점이 있다. 예를 들어 이번에는 과일이 아닌 채소에 대한 상수를 추가해야한다고 가정해보자. 채소를 적으면 상수의 개수가 너무 많아지고 한 눈에 상수들의 어떤 것에 관련된 것인지 알아보기 힘들다. 또한 상수의 집합에서 동일한 이름으로 정의된 상수가 있다면 중복된 이름이기 때문에 컴파일 오류가 나고만다. 그래서 Class 또는 인터페이스를 사용하여 각각의 집합끼리 성수를 정의하면 중복된 이름이 있어도 오류를 피할 수 있다.
먼저 중복의 경우를 이해하기 위해 요일과 달로 얘기해보자.
public interface DAY{
int Monday = 1;
int Tuesday = 2;
int Wednesday = 3;
int Thursday = 4;
int Friday = 5;
int Saturday = 6;
int Sunday = 7;
}
public 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 class Example {
public static void main(String[] args) {
int day = Monday;
switch (day) {
case Monday:
System.out.println("월요일 입니다.");
break;
case Tuesday:
System.out.println("화요일 입니다.");
break;
case Wednesday:
System.out.println("수요일 입니다.");
break;
case Thursday:
System.out.println("목요일 입니다.");
break;
... (생략)
}
}
}
위의 경우, interface에 선언된 변수는 public static final 속성을 생략할 수 있다는 특징을 이용하여 코드를 조금 더 간결하게 작성하였고 중복된 값의 경우도 피할 수 있다. 하지만 또 문제가 있다! 바로 다른 집합에 정의된 상수들을 비교할 수 없다는 것이다. 다른 집합의 상수를 비교하면 컴파일 단계에서 에러를 확인할 수 있어야 하지만, 위의 경우는 확인할 수 없다. 그래서 런타임 단계에서 예기치 못한 문제가 발생할 수 있다.
public class Day{
public final static Day Monday = new Day();
public final static Day Tuesday = new Day();
public final static Day Wednesday = new Day();
public final static Day Thursday = new Day();
public final static Day Friday = new Day();
public final static Day Saturday = new Day();
public final static Day Sunday = new Day();
}
public class Month{
public final static Month January = new Month();
public final static Month February = new Month();
public final static Month March = new Month();
public final static Month April = new Month();
public final static Month May = new Month();
public final static Month June = new Month();
public final static Month July = new Month();
public final static Month August = new Month();
public final static Month September = new Month();
public final static Month October = new Month();
public final static Month November = new Month();
public final static Month December = new Month();
}
public class Example {
public static void main(String[] args) {
if (Day.Monday == Month.January){ // 에러! 다른 데이터 타입은 비교 불가
System.out.println("두 상수는 같습니다.")
}
Day day = Day.Monday;
switch (day) { // 에러! switch 문의 조건에 들어가는 데이터 타입이 제한적임
case Day.Monday:
System.out.println("월요일 입니다.");
break;
case Day.Tuesday:
System.out.println("화요일 입니다.");
break;
case Day.Wednesday:
System.out.println("수요일 입니다.");
break;
case Day.Thursday:
System.out.println("목요일 입니다.");
break;
... (생략)
}
}
}
위의 코드는 interface로 작성된 상수의 집합을 Class로 바꾸었다. 그리고 각각 상수들의 타입을 자신의 상수 집합의 이름으로 지정하였다. 그리고 자기 자신을 인스턴스화 한 값을 할당한다. 즉, 각각의 상수들이 서로 다른 데이터를 의미한다는 것이다. 하지만 같은 집합의 상수들은 같은 데이터타입을 갖는다 (데이터 타입은 같지만 서로 다른 데이터 값을 가지고 있음). 따라서 위 코드에서는 표시해둔 두 곳에서 오류가 나게 된다 (다른 데이터 값 비교, switch 문의 조건에 들어가는 데이터 타입이 제한적)
Enum 사용
위의 경우를 enum으로 사용하게 되면 어떨까?
public enum Day{
Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
}
public enum Month{
January, February, March, April, May, June, July,
August, September, October, November, December
}
public class EnumExample{
public static void main(String[] args) {
Day day = Day.Monday;
switch (day) {
case Monday:
System.out.println("월요일 입니다.");
break;
case Tuesday:
System.out.println("화요일 입니다.");
break;
case Wednesday:
System.out.println("수요일 입니다.");
break;
case Thursday:
System.out.println("목요일 입니다.");
break;
... (생략)
}
}
}
이전의 코드들과 비교하면 매우 간단하게, enum Class 를 생성해주고 값을 나열하기만 하면 된다 (위의 interface나 class를 사용했을 때 보다 훨씬 코드가 간결하고 가독성이 좋다! - Enum의 1번째 장점). 또한 switch 문의 day 는 조건으로 넘어온 데이터 타입을 잘 매칭한다. 그래서 각각의 레이블에서는 enum의 데이터 타입을 생략하고 상수만 입력할 수 있다.
Enum 선언
- Class 내부에서 선언
public class Developer {
public String name;
public int career;
public enum DevType {
BACKEND, FRONTEND, FULLSTACK
}
}
- 별도의 .java(enum) 선언
public enum DevType {
BACKEND, FRONTEND, FULLSTACK
}
public class Developer {
public String name;
public int career;
public DevType type;
}
- Class 외부에서 선언
public class Developer {
public String name;
public int career;
public DevType type;
}
enum DevType {
BACKEND, FRONTEND, FULLSTACK
}
- 열거형으로 선언된 순서에 따라 0 부터 인덱스 값을 가진다 (= 순차적으로 증가된다)
- enum 열거형으로 지정된 상수들은 모두 대문자로 선언한다
- 열거형 변수들을 선언한 후 마지막에 세미콜론(;)은 찍지 않는다! (단, 상수와 연관된 문자를 연결시킬 경우에는 찍는다 (아래 참고).
enum DevType {
//상수("연관시킬 문자") <- 이땐 줄 끝에 세미콜론 (;) 붙이기.
BACKEND("백엔드"),
FRONTEND("프론드엔드"),
FULLSTACK("풀스택");
}
public class Developer {
public String name;
public int career;
public DevType type;
public static void main(String[] args) {
Developer developer = new Developer();
developer.name = "홍길동";
developer.career = 3;
developer.type = DevType.BACKEND;
System.out.println("개발자 이름 : " + developer.name);
System.out.println("개발자 경력 : " + developer.career);
System.out.println("직무파트 : " + developer.type);
}
}
enum DevType {
BACKEND, FRONTEND, FULLSTACK
}
출력 결과 =
개발자 이름 : 홍길동
개발자 경력 : 3
직무파트 : BACKEND
Enum 활용
if 문 사용 줄이기
enum을 활용하여 if 문의 사용을 줄여보자. 예를 들어 어떤 값에 대한 반대 값을 반환하고, 그 값에 따라 무언가를 수행하는 코드를 작성해야한다고 가정하자. (Ex_ 스위치를 on/off 해야하는 예제에서, on이면 전원을 off 로 바꾸고, off면 전원을 on로 바꿔야 함)
이 경우 많은 if ~ else 구문을 사용하게 되는데 코드가 지저분하고 가독성이 떨어진다. 이것을 enum 으로 간단하게 만들어보자!
아래 코드를 살펴보자.
public enum PowerSwitch {
ON("켜짐"),
OFF("꺼짐");
private String Name;
private PowerSwitch(){
}
private PowerSwitch(String Name){
this.Name = Name;
}
public String getName(){
return Name;
}
public PowerSwitch opposite(){
if (this == PowerSwitch.ON){
return PowerSwitch.OFF;
} else {
return PowerSwitch.ON;
}
}
}
public class PowerSwitchMain{
public static void main(String[] args) {
PowerSwitch powerSwitch = PowerSwitch.ON;
displayByPowerSwitch(powerSwitch.opposite());
// opposite() 메소드가 없다면 if 구문을 사용해서 PowerSwitch 변수에 있는 값을 확인하는 작업을 수행해야 한다.
}
publuc static void displayByPowerSwitch (PowerSwitch powerSwitch){
if (powerSwitch == PowerSwitch.ON){
System.out.println("전원 ON");
} else {
System.out.println("전원 OFF");
}
}
}
enum을 통해 ON, OFF라는 이름의 상수를 정의했고,
opposite() 메소드를 통해 반댓값(OFF -> ON, ON -> OFF)을 반환할 수 있다. 확실히 실 사용부인 Main 메소드가 깔끔하다!
[참고]
'𝑷𝒓𝒐𝒈𝒓𝒂𝒎𝒎𝒊𝒏𝒈 > 𝐽𝐴𝑉𝐴' 카테고리의 다른 글
[JAVA] Optional 개념 및 사용법 (0) | 2022.12.16 |
---|---|
[JAVA] Stream API 살펴보기 - findFirst() vs findAny() + 병렬 처리 (0) | 2022.12.01 |
[JAVA] Collection - Set (LinkedHashSet + HashSet) (0) | 2022.11.24 |
[JAVA] Collection - Set (SortedSet, TreeSet) (0) | 2022.11.24 |
[JAVA] Collection - Set (HashSet) (0) | 2022.11.23 |