본문 바로가기

JAVA

[JAVA] 자바 enum 을 활용해서 코드 심플하게 개선하기(이런 것까지 가능하다)

 

저번에 자바 인터페이스를 활용한 경험담을 소개했었는데 그 당시 코드에 보면 if else를 이용해서 클래스를 생성하는 부분이 있었습니다. 

 

2019/05/09 - [JAVA] - [JAVA] 자바 인터페이스(Interface)는 어떤 경우에 사용하나?(실전 프로젝트 경험담)

 

문제가 되는 소스코드

public ApiI getApi(String apiKey){
  ApiI api = null;

  if(apiKey =="API_A"){
    api = new ApiA();

  }else if(apiKey =="API_B"){
    api = new ApiB();

  }else if(apiKey =="API_C"){
    api = new ApiC();

  }
  .....
  
  return api;
}  

 

이런 로직을 사용하는 경우가 많을 겁니다. 보통은 2~3개 이내라서 그냥 if 문을 사용하지만 if else가 10개 20개가 넘어가면 코드가 지저분해지겠죠.  이런 경우 switch를 이용해서 좀 더 깔끔하게 정리하긴 하지만 if else 나 switch나 코드를 읽어나가는데 방해가 되는 건 비슷비슷하죠. 

public ApiI getApi(String apiKey){

  ApiI api = null;
  
  switch (apiKey) {
    case IConstants.API_A :  
      api = new ApiA();
    break;
    case IConstants.API_B:  
      api = new ApiB();
    break;

    ....

    default: 
    break;
  }
  
  return api;
}  

 

Java Enum이란?

관련이 있는 열거형 상수들의 집합을 의미합니다. 

 

솔직히 저도 프로젝트를 하면서 자바의 enum을 실제로 사용한적은 없었는데, 이번에 공부하면서 enum이 이런 일도 가능하구나라고 많이 배웠습니다. 

 

저는 Java Enum을 '사용가능한 값을 정해놓을 때 사용한다.(화이트리스트)' 정도로 알고 있었습니다. 

즉, 위 코드를 예로들면 'apiKey로 넘어올 수 있는 매개변수 값은 'A, B, C'만 가능하다'라고 정의하는 정도로 enum을 활용했겠죠.

public enum ApiEnum {
	API_A, API_B, API_C;
}	

 

하지만 Enum의 다른 기능을 활용해서 앞에서 본 if..else 구문을 개선해보겠습니다.

 

Enum을 이용해서 개선된 소스코드

먼저, 실제로 API를 생성해서 리턴하는 역할을 Enum이 수행하도록 Enum을 작성합니다.

public enum ApiEnum {
  API_A(new ApiA()),
  API_B(new ApiB());

  private ApiI api;

  //순서대로 매핑. 생성자 반드시 필요.
  ApiEnum(ApiI apiParam){		
    this.api = apiParam;		
  }

  public ApiI getApi() {
    return this.api; 
  }	
}

 

ApiEnum을 호출할 때 ApiEnum.API_A를 호출하고 getApi()를 호출하면 ApiA() 클래스가 리턴됩니다. 

ApiEnum apiE = ApiEnum.API_A;
ApiI api = apiE.getApi();
System.out.println(api.getApiUrl());

 

이번 예에서는 'API_A' 라는 값 자체가 가변적으로 넘어오는 매개변수이기 때문에 ApiEnum.API_A처럼 Enum의 요소를 접근해서 가져올 수 없습니다. 이런 경우에는  Enum의 valueOf(String arg)를 사용하면 됩니다. 

if.. else 대신 Enum을 이용해서 좀 더 깔끔하게 개선된 코드

public ApiI getApi(String apiKey){

  return ApiEnum.valueOf(apiKey).getApi();
  
}  

이제 API가 추가될 경우 Enum에 'API_K(new ApiK());' 코드만 추가해주면 됩니다. 

 

주의사항!

Enum은 사용가능한 값이 정해져있기 때문에 사용 불가한 값을 사용하려고 하면 에러가 발생합니다.

java.lang.IllegalArgumentException: No enum constant ApiEnum.API_C

그렇기 때문에 Enum이 valueOf(String arg)를 사용할 때는 반드시 에러가 발생할 경우를 대비해야 합니다. 

public ApiI getApi(String apiKey){
  try{
  
    return ApiEnum.valueOf(apiKey).getApi();
    
  }catch(IllegalArgumentException e) {
  	...에러처리...
  }
}  

 

그리고 만약 Enum의 요소에서 그룹화해서 관리하고자 하는 데이터를 추가하려면 아래처럼 처리하면 됩니다. 

public enum ApiEnum {
  API_A(new ApiA(), true, 'utf-8'),
  API_B(new ApiB(), false, 'euc-kr');

  private ApiI api;
  public boolean isErrorChk;
  public String charset;

  //순서대로 매핑. 생성자 반드시 필요.
  ApiEnum(ApiI api, isErrorChk, charset){		
    this.api = api;		
    this.isErrorChk = isErrorChk;
    this.charset = charset;
  }

  //변수를 private으로 선언한 경우만
  public ApiI getApi() {
    return this.api; 
  }	
}

 

그리고 아래와 같이 호출 가능합니다. 변수를 public으로 선언했기 때문에 ApiEnum.API_A.isErrorCheck;로 바로 접근 가능합니다. 물론 private로 선언하고 getter를 사용해도 됩니다.

Boolean isError = ApiEnum.API_A.isErrorCheck;
System.out.println("isError : " + isError);

String charset = ApiEnum.API_A.charset;
System.out.println("charset : " + charset);

 

참고로 Enum은 values()라는 함수를 제공하는데 Enum에 선언된 요소들을 Enum 배열로 리턴합니다. 

ApiEnum[] arr = ApiEnum.values();
for (int i = 0; i < arr.length; i++) {  
  ApiI api2 = arr[i].getApi();
  System.out.println(api2.getApiUrl());
  System.out.println("isError Arr : " + arr[i].isError);
}