본문 바로가기
Java

[JAVA] Enum 내부 동작원리를 중심으로 알아보기

by onejunu 2021. 2. 26.

처음 Enum 에 대해 배울 때 단순히 여러 상수를 정의할 때 사용하였다.

상수를 그냥 쓰다보면 예상치 못한 오류가 생길 수 있으므로 따로 열거형으로 정의해서 사용한다. JAVA 에서는 Enum 안에 함수도 들어가는 거 같고 생각보다 복잡한 느낌이 있어서 이번 기회에 재대로 정리해본다.

 

 

아래의 예시를 보자.

class Korea {
    static final int SEOUL = 0;
    static final int DAEGU = 1;
    static final int BUSAN = 2;
}

class America {
    static final int LA = 0;
    static final int NEWYORK = 1;
}

public class Main{
    private static void main(String[] args) {
        System.out.println(Korea.SEOUL == America.LA ? "TRUE" : "FALSE" );
    }
}

출력결과는 TRUE이다. 0 == 0 과 같은 코드이므로 참이다. 하지만 의미적으로 생각했을 때 SEOUL 과 LA는 같은가?? 

그리고 America 의 상수값들이 변경된다면 또 결과가 달라진다.

class Korea {
    static final int SEOUL = 0;
    static final int DAEGU = 1;
    static final int BUSAN = 2;
}

class America {
    static final int LA = 100;
    static final int NEWYORK = 101;
}

public class Main{
    private static void main(String[] args) {
        System.out.println(Korea.SEOUL == America.LA ? "TRUE" : "FALSE" );
    }
}

우리는 LA , SEOUL 등 똑같이 사용하고 싶은데 상수의 값이 변해버리면 다시 코딩해야 되는 불상사가 발생한다.

그래서 Enum을 사용한다.

Enum을 사용해서 코드를 수정하면 아래처럼 된다.

 

enum Korea { SEOUL , DAEGU , BUSAN }
enum America { LA , NEWYORK }

public class Main{
    private static void main(String[] args) {
    	// compile 에러 !
        System.out.println(Korea.SEOUL == America.LA ? "TRUE" : "FALSE" );
    }
}

 

이처럼 Type에 대해 검사를 하기 때문에 보다 안정적인 코딩이 가능하다.

그리고 Korea.SEOUL 은 내부적으로 0이라는 id 값을 가지고 있다. DAEGU는 1 , BUSAN 은 2를 가진다. America 의 LA는 0 , NEWYORK 는 1 을 가지고 있다. 이러한 값을 ordinal() 이라는 함수를 통해 볼 수 있다. 하지만 ordinal 값을 사용하는 것은 타입의 안정성에 문제가 있으므로 지양하는 바이다.

 

 

- Enum 의 내부 동작원리 ( 자바의 정석 참조 )

enum의 내부 동작 모습을 더 알아보기 위해 MyEnum 을 정의하였다.  

실제 enum 에 compareTo를 구현이 되었있기 때문에 Comparable<MyEnum> 을 implements 하였다.

아래 코드를 보고 설명하겠다.

 

id 는 MyEnum 객체들끼리 공유하는 변수이므로 static으로 선언해야 새로 생성되는 ordinal값을 id를 이용해 초기화 할 수 있다. 즉, 새로 MyEnum 객체가 생성될때 마다 중복되지 않는 고유의 ordinal값을 가질 수 있게 된다. name은 "SEOUL" 과 같은 String 값으로 생성자를 통해 초기화 할 수 있도록 하였다.

 

MyEnum 객체를 제네릭스를 이용해 수정하면 아래와 같다. 

<T extends MyEnum<T>> 라고 한 이유는 compareTo 메소드의 매개변수로 t 를 받는 데, t.ordinal() 에서 ordinal() 함수가 있다는 것을 알려주기 위함이다. MyEnum<T> 을 포함한 자손들이 매개변수로 들어올것을 확정짓는 다면 t.ordinal() 은 컴파일 에러를 낼 이유가 없다. 

 

 

이제 MyEnum을 완성하였다.  MyEnum을 사용하는 예시를 소개한다.

뒤에서 enum으로 바꾼 코드와 비교해볼 것이므로 바로 와닿지 않더라도 넘어가길 권장한다.

MyTransportation2 라고 2를 붙인 이유는 MyTransportation1 은 enum을 사용한 구현으로 뒤에서 보여줄 것이다.

 

먼저 MyTransportation2 는 추상클래스이다. 인스턴스를 생성할 수 없는 클래스를 의미한다.

하지만 추상클래스의 추상메서드를 익명 클래스로 확장(extends) 하여 구현(implements)하는 순간 더 이상 추상클래스가 아니며 인스턴스화 할수 있다.  그래서 다음과 같은 코드가 가능한 것이다.

 

    static final Transportation2 Bus = new Transportation2("BUS",100) {
        @Override
        int fare(int dist) {
            return dist * BASIC_FARE;
        }
    };

 

더 자세한 설명이 필요하다면 스택오버플로우의 링크를 참조하자.

stackoverflow.com/questions/16785922/creating-the-instance-of-abstract-class-or-anonymous-class

 

Creating the instance of abstract class or anonymous class

In this code here is it creating the object of abstract class or anonymous class? Please tell me. I am little bit confused here. public abstract class AbstractDemo { abstract void showMessage...

stackoverflow.com

 

다시 본론으로 와서 Transportation2 는 Bus 와 Train 에 대해 기본요금이 다르고 거리에 따라 fare를 계산할 수 있는 기능이 있다.

메인함수에서 사용은 다음과 같다.

System.out.println("bus 기본요금은 : "+Transportation2.Bus.getBASIC_FARE() + " 이며 20km 이동하면 : " + Transportation1.Bus.fare(20));
System.out.println("Train 기본요금은 : "+Transportation2.Train.getBASIC_FARE() + " 이며 20km 이동하면 : " + Transportation1.Train.fare(20));

 

 

정리하면, enum은 따로 열거형이라는 것이 존재한다기 보다는 자바코드가 맞다고 본다.

이제 enum으로 구현하여 더 간결하게 작성해본다.

 

 

익명 클래스로 추상클래스를 확장 및 구현하여 만든 Bus 와 enum에서 정의한 Bus를 비교해보기 바란다.

 

 

 

댓글