Java Enum 기본

김민우·2022년 11월 11일
0

TIL

목록 보기
10/10

📌 열거형이란?

열거형 혹은 이넘(Enum, Enumeratation type)은 요소, 멤버라 불리는 명명된 값의 집합을 이루는 자료형이다. 서로 관련된 상수를 편리하게 선언하기 위한 것으로 여러 상수를 정의할 때 사용하면 유용하다. jdk 1.5 버전에서 새로 추가되었다.

아래의 코드들을 통해 enum을 어떻게 활용하는지 알아보자.

class Example {
    static final int WOO = 0;
    static final int WA = 1;
    static final int TECH = 2;
    static final int COURSE = 3;
}

// Effective JAVA에서는 이 기법을 int enum 패턴이라고 한다.
// 안전성 관점과 편의성 관점에서 단점이 많다고 한다.

위와 같은 코드는 Enum을 사용하면 아래와 같은 코드가 된다.

class Example {
    enum Kind {WOO, WA, TECH, COURSE} // 차례대로 0, 1, 2, 3의 값을 갖는다.

    Kind kind;
}

❗ 위의 코드는 예시이다. 열거형을 서순(Ordinal) 그대로 사용하는 것은 바람직한 방법이 아니다. 열거형에 멤버를 추가하는 것은 뒤에서 다룬다.

C언어 같은 경우에는 타입이 달라도 값이 같으면 조건식결과가 참이였으나, 자바의 열거형은 타입에 안전한 열거형(typesafe enum)이라서 실제 값이 같아도 타입이 다르면 컴파일 에러가 발생한다. 즉, 자바의 열거형은 타입도 관리하기 때문에 논리적인 오류를 줄일 수 있다.

다음 예시 코드를 봐보자.

Kind.WOO는 0이란 값을 갖지만, 타입이 다르기에 컴파일 에러가 발생한다.

또한, 매직 넘버를 사용 할 경우 상수의 값이 바뀌면 해당 상수를 참조하는 모든 소스를 다시 컴파일해야 한다.
하지만, 열거형 상수를 사용하면, 기존의 소스를 다시 컴파일하지 않아도 된다.

💡 매직 넘버?
프로그래밍에서 상수(static final)로 선언하지 않은 숫자를 매직 넘버, 문자열을 매직 리터럴이라 한다.

  • 이를 정적(static)이고 변경 불가능(final)한 상수로 선언하여 사용하자. 코드에서 상수로 선언되어 있지 않은 숫자, 문자열은 무엇을 의미하는지 확신할 수 없다.

- 열거형의 정의와 사용

열거형을 정의할 때는 다음과 같이 괄호안에 상수의 이름을 나열하면 된다.
enum Kind {WOO, WA, TECH, COURSE}

이 열거형에 정의된 상수를 사용하기 위해선 열거형이름.상수명을 사용한다. 클래스의 static 변수를 참조하는 것과 동일하다.

enum Woowa {WOO, WA, TECH, COURSE}

class Test {
    Woowa woowa; // 열거형을 인스턴스 변수로 선언하였다.
    
    void init() {
        woowa = Woowa.TECH;
    }
}

열거형 상수간의 비교에는 ==를 사용할 수 있다. equals()가 아닌 ==로 비교 가능하다는 것은 그만큼 빠른 성능을 제공한다는 얘기다.
그러나, <, >와 같은 비교연산자는 사용할 수 없고 compareTo()는 사용가능하다.

if (woowa > Woowa.WA) {
// 에러가 발생한다.
    }
if(woowa.compareTo(Woowa.WA) > 0) {
// compareTo는 사용가능하다.
	}

- 열거형에 멤버 추가하기

Enum클래스에 정의된 ordinal()이 열거형 상수가 정의된 순서를 반환하지만, 이 값을 열거형 상수의 값으로 사용하지 않는 것이 좋다. 이 값은 내부적인 용도로만 사용된다.

열거형 상수의 값이 불연속적인 경우에는 다음과 같이 열거형 상수의 이름 옆에 원하는 값을 괄호()와 함께 적을 수 있다.

enum Wowwa {WOO(3), WA(2), TECH(4), COURSE(6)}

그리고 지정된 값을 저장할 수 있는 인스턴스 변수와 생성자를 새로 추가해야 한다.
이 때 주의할 점은 다음과 같다.

  • 먼저 열거형 상수를 모두 정의한 다음에 다른 멤버들을 추가해야한다.
  • 열거형 상수의 마지막에 ;도 잊지 말아야 한다.
enum Woowa {
    WOO(3), WA(2), TECH(4), COURSE(6);

    private final int value; // 정수를 저장할 필드(인스턴스 변수)를 추가한다.

    Woowa(int value) { // 생성자를 추가한다.
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

열거형 Woowa에 새로운 생성자가 추가되었지만, 열거형의 객체는 외부에서 생성할 수 없다. 열거형의 생성자는 제어자가 묵시적으로 private이기 때문이다.

하나의 열거형 상수에 여러 값을 지정할 수도, 문자열을 지정 할 수도 있다. 다만 그에 맞게 인스턴스 변수와 생성자 등을 새로 추가해주어야 한다.

문자열을 지정한다면 다음과 같이 작성할 수 있다.

enum Woowa {
    WOO("WOO"), WA("WA"), TECH("TECH"), COURSE("COURSE");

    private final String value; // 정수를 저장할 필드(인스턴스 변수)를 추가한다.

    Woowa(String value) { // 생성자를 추가한다.
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}

여러 값을 지정한다면 다음과 같이 작성할 수 있다.

enum Woowa {
    WOO(3, "WOO"), WA(2, "WA"), TECH(4, "TECH"), COURSE(6, "COURSE");

    private final int value; // 정수를 저장할 필드(인스턴스 변수)를 추가한다.
    private final String symbol;

    Woowa(int value, String symbol) { // 생성자를 추가한다.
        this.value = value;
        this.symbol = symbol;
    }

    public int getValue() {
        return value;
    }

    public String getSymbol() {
        return symbol;
    }
}

- 열거형 뜯어보기 (심화)

열거형이 내부적으로 어떻게 구현되있는지 알아보자.
다음과 같은 열거형 Woowa가 정의되어 있을 때, 열거형 상수 하나하나가 Woowa 객체이다.

enum Woowa {WOO, WA, TECH, COURSE}

위의 문장을 클래스로 정의한다면 다음과 같다.

class Woowa {
	static final Woowa WOO = new Woowa("WOO");
    static final Woowa WA = new Woowa("WA");
    static final Woowa TECH = new Woowa("TECH");
    static final Woowa COURSE = new Woowa("COURSE");
    
    private String name;
    
    private Woowa(String name) {
    	this.name = name;
    }
}

Woowa클래스의 static상수 WOO, WA, TECH, COURSE의 값은 객체의 주소이고, 이 값은 바뀌지 않는 값이므로 ==로 비교가 가능한 것이다.

모든 열거형은 추상 클래스 Enum의 자손이므로, Enum을 흉내 내어 MyEnum을 작성하면 다음과 같다.

abstract class MyEnum<T extends MyEnum<T>> implements Comparable<T> {
    static int id = 0; // 객체에 붙일 일련번호 (0부터 시작)
    
    int ordinal;
    String name = "";
    
    public int ordinal() {
        return ordinal;
    }
    
    MyEnum(String name) {
        this.name = name;
        ordinal = id++;
    }
    
    public int compareTo(T t) {
        return ordinal - t.ordinal();
    }
}

만일 클래스를 MyEnum<T>와 같이 작성한다면 타입 T에 ordinal()이 정의되어 있는지 확인할 수 없기에 compareTo()를 위와 같이 작성할 수 없었을 것이다.

빨간 줄에 마우스 오버를 해보면 다음과 같은 메시지를 볼 수 있다.

그래서 MyEnum<T extends MyEnum<T>>와 같이 선언한 것이며, 이것은 타입 T가 MyEnum<T>의 자손이어야 한다는 것이다. 타입 T가 MyEnum의 자손이므로 ordinal()이 정의되어 있는 것은 분명하므로 형변환 없이도 에러가 나지 않는다.

그리고 추상 메서드를 새로 추가하면, 클래스 앞에도 abstract를 붙여줘야 하고, 각 static상수들도 추상 메서드를 구현해주어야 한다.


REF.

자바의 정석(남궁성 저)
https://metalbird.tistory.com/entry/Java-String-equals-%EC%86%8D%EB%8F%84-%EB%B9%84%EA%B5%90
https://javabom.tistory.com/28

profile
Pay it forward.

0개의 댓글