개요
- enum 을 자주 사용했지만, 꼭 필요한 경우와 장점을 확실하게 설명하지 못했다. 그래서 오늘 enum 에 대해서 좀더 자세히 공부하고 정리해 보려고 한다.
- enum 을 사용하지 않았을 경우와 사용했을 경우를 비교해 보면서 enum 에 대해서 알아보자.
기존 코드
private void validateCreateDeveloperRequest(Developer developer, Integer experienceYears) {
if (developer.getDeveloperLevel() == "junior"
&& experienceYears > 4) {
throw new DMakerException(LEVEL_AND_EXPERIENCE_YEARS_NOT_MATCH);
}
if (developer.getDeveloperLevel() == "jungnior"
&& (experienceYears > 10 || experienceYears < 4)
) {
throw new DMakerException(LEVEL_AND_EXPERIENCE_YEARS_NOT_MATCH);
}
if (developer.getDeveloperLevel() == "senior" && experienceYears < 11) {
throw new DMakerException(LEVEL_AND_EXPERIENCE_YEARS_NOT_MATCH);
}
}
- 위의 코드를 보면 가장먼저 "SENIOR", "JUNGIOR", "JUNIOR" 값을 상수 값으로 바꾸고 싶다는 생각이 들것이다.
- 그렇다면 아래와 같이 리팩토링 할 수 있을 것이다.
public class DeveloperLevel {
private static final String SENIOR = "senior";
private static final String JUNGNIOR = "jungnior";
private static final String JUNIOR = "junior";
}
private void validateCreateDeveloperRequest(Developer developer, Integer experienceYears) {
if (developer.getDeveloperLevel() == DeveloperLevel.SENIOR
&& experienceYears < 10) {
throw new DMakerException(LEVEL_AND_EXPERIENCE_YEARS_NOT_MATCH);
}
if (developer.getDeveloperLevel() == DeveloperLevel.JUNGNIOR
&& (experienceYears > 4 || rexperienceYears < 10)
) {
throw new DMakerException(LEVEL_AND_EXPERIENCE_YEARS_NOT_MATCH);
}
if (developer.getDeveloperLevel() == DeveloperLevel.JUNIOR && rrexperienceYears > 4) {
throw new DMakerException(LEVEL_AND_EXPERIENCE_YEARS_NOT_MATCH);
}
}
- 위와 같이 상수값으로 변경함으로써, 개발자의 실수를 방지할 수 있고, 추후에 있을 변경에 대응하기 쉽다.
- 하지만 상수값은 컴파일 하면 그 값이 클라이언트 파일에 그대로 새겨지기 때문에, 상수 값이 바뀌면 컴파일을 다시해야 된다는 단점이 있다.
- 하지만 상수형은 상수가 많아질 경우 가독성이 떨어지고, 자료형에 대한 안정성을 보장 받을 수 없다.
- 컴파일 단계에서 자료형에 대한 안정성을 보장 받지 못한다는 것은 치명적일 수 있다.
- 그렇기 때문에 아래와 같이 java enum 을 이용해서 좀더 개선할 수 있다.
@AllArgsConstructor
@Getter
public enum DeveloperLevel {
NEW("new"),
JUNIOR("junior"),
JUNGNIOR("jungnior"),
SENIOR("senior")
;
private final String description;
}
- IDE 또는 컴파일 단계에서 지원을 받을 수 있다는 장점이 있다.
- 또한 리팩토링의 유리하다. -> enum 이 내포하고 있는 값이 달라지더라도 enum 만 수정하면 되기 때문이다.
- enum 은 불변객체로 생성되기 때문에 여러번 할당하더라도 비용부담이 발생하지 않는다.
- 또한 인스턴스 생성과 상속을 방지하여 상수값의 타입안정성이 보장된다.
Enum 활용
- 위의 코드를 enum 을 이용해서 좀더 리팩토링 할 수 있다.
- 아래와 같이 enum 클래스를 수정한다.
@AllArgsConstructor
@Getter
public enum DeveloperLevel {
NEW("new",0,0),
JUNIOR("junior",1,4),
JUNGNIOR("jungnior",5,9),
SENIOR("senior",10,70)
;
private final String description;
private final Integer minExperienceYears;
private final Integer maxExperienceTears;
}
- Developer 객체의 developerLevel 속성을 String 이 아닌 enum 으로 추가해주면 아래와 같이 간결하게 수정할 수 있다.
private void validateCreateDeveloperRequest(Developer developer, Integer experienceYears) {
if (experienceYears < developer.getDeveloperLevel().getMinExperienceYears() ||
experienceYears < developer.getDeveloperLevel().getMinExperienceYears()){
throw new DMakerException(LEVEL_AND_EXPERIENCE_YEARS_NOT_MATCH);
}
// if (developer.getDeveloperLevel() == DeveloperLevel.SENIOR
// && experienceYears < 10) {
// throw new DMakerException(LEVEL_AND_EXPERIENCE_YEARS_NOT_MATCH);
// }
// if (developer.getDeveloperLevel() == DeveloperLevel.JUNGNIOR
// && (experienceYears > 4 || rexperienceYears < 10)
// ) {
// throw new DMakerException(LEVEL_AND_EXPERIENCE_YEARS_NOT_MATCH);
// }
// if (developer.getDeveloperLevel() == DeveloperLevel.JUNIOR && rrexperienceYears > 4) {
// throw new DMakerException(LEVEL_AND_EXPERIENCE_YEARS_NOT_MATCH);
// }
}
- 위의 코드에서 주석으로 표시한 부분을 위의 코드처럼 줄일 수 있다.
함수형 프로그래밍 적용
@AllArgsConstructor
@Getter
public enum DeveloperLevel {
NEW("new", years -> years == 0),
JUNIOR("junior", years -> years <= 4),
JUNGNIOR("jungnior", years -> years > 4 && years < 10),
SENIOR("senior", years -> years >= 70)
;
private final String description;
private final Function<Integer, Boolean> validateFunction;
public void validateExperienceYears(Integer years) {
if (!validateFuction.apply(years))
throw new DMakerException(LEVEL_AND_EXPERIENCE_YEARS_NOT_MATCH);
}
}
- 위와 같이 함수형 인터페이스를 적용할 수 있다.
private void validateCreateDeveloperRequest(Developer developer, Integer experienceYears) {
developer.getDeveloperLevel().getValidateExperienceYears(experienceYears);
// if (experienceYears < developer.getDeveloperLevel().getMinExperienceYears() ||
// experienceYears < developer.getDeveloperLevel().getMinExperienceYears()){
// throw new DMakerException(LEVEL_AND_EXPERIENCE_YEARS_NOT_MATCH);
// }
}
- 위의 코드에서 주석으로 표시한 부분을 위의 코드처럼 줄일 수 있다.
Enum 대표 메소드
values()
- Enum 클래스에서 가지고 있는 모든 상수 인스턴스를 배열에 담아 반환한다.
DeveloperLevel[] values = DeveloperLevel.values();
for(int i=0;i<values.length;i++){
System.out.println(values[i]); // NEW, JUNIOR, JUNGNIOR, SENIOR
System.out.println(values[i].getDescription()); // "new", "junior", "jungnior", "senior"
}
valueOf()
- String을 매개변수로 받아 일치하는 상수 인스턴스를 반환한다. (없을 경우 런타임 에러 발생)
System.out.println(DeveloperLevel.valueOf("SENIOR"));
System.out.println(DeveloperLevel.valueOf("SENIOR").getDescription());
// RuntimeException
try{
System.out.println(DeveloperLevel.valueOf("OH"));
}catch (IllegalArgumentException e){
System.out.println("런타입 에러");
}
- "OH" 라는 타입이 없으므로 IllegalArgumentException 를 반환합니다.
ordinal()
- Enum 클래스 내부에 있는 상수들의 인덱스를 반환합니다.
System.out.println(DeveloperLevel.JUNIOR.ordinal()); // 1
- JUNIOR 가 2번째이므로 인덱스 값인 1을 반환합니다.
REFERENCES