이번 포스팅에서는 열거형 상수인 Enum 이 필요한 이유에 대해 자세히 알아보고자 합니다.
먼저 상수에 대해 간단히 짚고 넘어가자면, 상수는 변하지 않는 값입니다.
int x = 1; //좌항 변수, 우항 상수
1 = 2; // 1은 2가 될 수 없다.
주석
이러한 상수의 특성과 switch 문을 사용하여 과일의 칼로리 값을 알아내는 코드는 아래와 같습니다.
public class ConstantDemo {
public static void main(String[] args) {
/*
* 1. 사과
* 2. 복숭아
* 3. 바나나
*/
int type = 1;
switch(type){
case 1:
System.out.println(57+" kcal");
break;
case 2:
System.out.println(34+" kcal");
break;
case 3:
System.out.println(93+" kcal");
break;
}
}
}
위와 같은 코드에서 숫자 1에 해당하는 과일은 언제나 사과여야 합니다. 하지만 주석이 지워지거나, 주석과 코드 사이가 멀어지는 등의 경우가 생긴다면 위의 코드를 이해하기 어려울 것입니다.
위의 경우에 이름이 있다면 좋을 것 같습니다. 변수도 상수가 될 수 있습니다. 변수를 지정하고 그 변수를 final로 처리하면 가능합니다. 또한 바뀌지 않는 값이라면 인스턴스 변수가 아니라 클래스 변수(static)로 지정하는 것이 더 좋을 것입니다.
public class ConstantDemo {
private final static int APPLE = 1;
private final static int PEACH = 2;
private final static int BANANA = 3;
public static void main(String[] args) {
int type = APPLE;
switch(type){
case APPLE:
System.out.println(57+" kcal");
break;
case PEACH:
System.out.println(34+" kcal");
break;
case BANANA:
System.out.println(93+" kcal");
break;
}
}
}
주석을 없애도 코드를 이해할 수 있게 되었습니다. 그런데 프로그램이 커지면서 아래와 같이 기업에 대한 상수가 추가되었습니다.
public class ConstantDemo {
// fruit
private final static int APPLE = 1;
private final static int PEACH = 2;
private final static int BANANA = 3;
// company
private final static int GOOGLE = 1;
//private final static int APPLE = 2; error
private final static int ORACLE = 3;
public static void main(String[] args) {
int type = APPLE;
switch(type){
case APPLE:
System.out.println(57+" kcal");
break;
case PEACH:
System.out.println(34+" kcal");
break;
case BANANA:
System.out.println(93+" kcal");
break;
}
}
}
Namespace
이 경우 과일 APPLE과 기업 APPLE 이 같은 이름을 가지게 됩니다. 이렇게 되면 상수의 개념에 어긋나므로 오류가 발생하게 됩니다. 이러한 문제를 해결하기 위해 접두사(Namespace)를 붙여볼 수 있습니다.
public class ConstantDemo {
// fruit
private final static int FRUIT_APPLE = 1;
private final static int FRUIT_PEACH = 2;
private final static int FRUIT_BANANA = 3;
// company
private final static int COMPANY_GOOGLE = 1;
private final static int COMPANY_APPLE = 2;
private final static int COMPANY_ORACLE = 3;
public static void main(String[] args) {
int type = FRUIT_APPLE;
switch(type){
case FRUIT_APPLE:
System.out.println(57+" kcal");
break;
case FRUIT_PEACH:
System.out.println(34+" kcal");
break;
case FRUIT_BANANA:
System.out.println(93+" kcal");
break;
}
}
}
인터페이스
네임스페이스를 사용하게 되면 이름이 중복될 확률을 낮출 수 있지만, 상수가 너무 지저분하다는 느낌이 들 수 있습니다. 깔끔하게 바꾸기 위해 interface를 사용해 보겠습니다.
interface FRUIT{
int APPLE=1, PEACH=2, BANANA=3;
}
interface COMPANY{
int GOOGLE=1, APPLE=2, ORACLE=3;
}
public class ConstantDemo {
public static void main(String[] args) {
int type = FRUIT.APPLE;
switch(type){
case FRUIT.APPLE:
System.out.println(57+" kcal");
break;
case FRUIT.PEACH:
System.out.println(34+" kcal");
break;
case FRUIT.BANANA:
System.out.println(93+" kcal");
break;
}
}
}
인터페이스에서 선언된 변수는 무조건 public static final 속성을 갖기 때문에 위와 같이 사용이 가능합니다.
그런데 type 값으로 누군가 COMPANY_GOOGLE을 사용했다면 어떻게 될까요?
int type = COMPANY.GOOGLE;
구글은 57Kcal라는 결과가 나옵니다(?).
우리 코드는 과일과 기업이라는 두 개의 상수 그룹이 존재합니다. 다른 범주에 있는 값끼리 비교할 수 없어야 하는데 두 개의 상수 그룹의 데이터 타입(int)이 같아 비교가 가능해집니다. 컴파일러가 잡지 못한 에러를 어떻게 잡을 수 있을까요?
class Fruit{
public static final Fruit APPLE = new Fruit();
public static final Fruit PEACH = new Fruit();
public static final Fruit BANANA = new Fruit();
}
class Company{
public static final Company GOOGLE = new Company();
public static final Company APPLE = new Company();
public static final Company ORACLE = new Company();
}
public class ConstantDemo {
public static void main(String[] args) {
if(Fruit.APPLE == Company.APPLE){
System.out.println("과일 애플과 회사 애플이 같다.");
}
}
}
Fruit와 Company 클래스를 만들고 클래스 변수로 해당 클래스의 인스턴스를 사용하고 있습니다. 각각의 변수가 final이기 때문에 불변이고, Static이므로 인스턴스로 만들지 않아도 됩니다. 바라던 대로, 결과는 compile 에러가 발생합니다. 서로 다른 카테고리의 상수에 대해서는 비교조차 금지하게 되었습니다. 언제나 오류는 컴파일 시에 나타나도록 하는 것이 바람직합니다.
그런데 위의 코드처럼 구현하게 된다면, switch 문에서 사용이 불가능하고, 선언이 너무 복잡하다는 문제가 있습니다.
Enum
이러한 문제들을 해결할 수 있는 것이 Enum입니다. Enum은 열거형(enumerated type)으로써, 서로 연관된 상수들의 집합이라고 할 수 있습니다. 위의 예제에서는 Fruit와 Company가 열거인 셈입니다. 이전 코드를 enum으로 바꿔보면 다음과 같습니다.
enum Fruit{
APPLE, PEACH, BANANA;
}
enum Company{
GOOGLE, APPLE, ORACLE;
}
public class ConstantDemo {
public static void main(String[] args) {
/*
if(Fruit.APPLE == Company.APPLE){
System.out.println("과일 애플과 회사 애플이 같다.");
}
*/
Fruit type = Fruit.APPLE;
switch(type){
case APPLE:
System.out.println(57+" kcal");
break;
case PEACH:
System.out.println(34+" kcal");
break;
case BANANA:
System.out.println(93+" kcal");
break;
}
}
}
위의 코드에서
enum Fruit{
APPLE, PEACH, BANANA;
}
enum은 class, interface와 동급의 형식을 가지는 단위입니다. 하지만 enum은 사실상 class입니다. 편의를 위해서 enum 만을 위한 문법적 형식을 가지고 있기 때문에 구분하기 위해서 enum이라는 키워드를 사용하는 것입니다. 위의 코드는 아래 코드와 같은 의미입니다.
class Fruit{
public static final Fruit APPLE = new Fruit();
public static final Fruit PEACH = new Fruit();
public static final Fruit BANANA = new Fruit();
private Fruit(){}
}
테스트를 진행해 보면 위에서 다뤄봤던 서로 다른 상수 그룹에 대한 비교를 했을 때 컴파일 에러가 나는 것을 확인할 수 있습니다. enum이 서로 다른 상수 그룹에 대한 비교를 컴파일 시점에서 차단할 수 있다는 것을 의미하고 상수 그룹 별로 클래스를 만든 것의 효과를 enum 도 갖는다는 것을 알 수 있습니다.
정리해 본다면 Enum을 사용하면 다음과 같은 장점을 얻을 수 있습니다.
- 코드가 단순해진다
- 인스턴스 생성과 상속을 방지한다.
- 키워드 enum을 사용하기 때문에 구현의 의도가 열거임을 분명하게 나타낼 수 있다
참고
'Java' 카테고리의 다른 글
JAVA POI 라이브러리에 Reflection을 더해보자! (0) | 2023.06.03 |
---|---|
Java 직렬화(Serialization)에 대해 알아보자! (0) | 2022.06.10 |
Java Stream API를 사용해보자! (0) | 2022.05.29 |