Post

[Java] 열거형(Enum)

인프런 얄코의 제대로 파는 자바, 생활코딩의 유튜브 상수와 enum 강의를 듣고 정리한 글입니다.

열거형이란?


열거형(enum)은 서로 관련된 상수들을 하나의 그룹으로 묶어 정의할 수 있는 특수한 클래스이다. 주로 상수값 집합을 표현할 때 사용되며, 타입 안정성과 코드 가독성을 높이는 데 유용하다.

1
2
3
4
String mode = "LIGHT";
mode = "DARK";

mode = "liGhT"; // 💥

지정된 선택지 내(LIGHT, DARK)의 값을 받을 변수 사용 시
잘못된 범위의 값 입력에 대해 대응하기가 번거롭기 때문에 실수를 방지할 방법이 없다.

이를 해결하기 위해 enum 이라는 클래스가 나온 것이다.

enum 탄생 배경


1️⃣ 클래스 내부의 상수

1
2
3
4
5
6
7
8
9
10
11
public class Demo {
    // 과일
    private final static int APPLE = 1;
    private final static int PEACH = 2;
    private final static int BANANA = 3;

    // 회사
    private final static int GOOGLE = 1;
    // private final static int APPLE = 2; // ⚠️ 상수 중복
    private final static int ORACLE = 3;
}

상수명의 규칙을 통해서 에러를 제거

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Demo {
    private final static int FRUIT_APPLE = 1;
    private final static int FRUIT_PEACH = 2;
    private final static int FRUIT_BANANA = 3;

    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;
        }
    }
}

2️⃣ 인터페이스로 분리

인터페이스라고 하는 문법적인 것을 이용하여 가독성을 해결

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 💡 인터페이스 안의 멤버는 `public final static`을 암시
interface FRUIT {
    int APPLE = 1,
    int PEACH = 2,
    int BANANA = 3
}

interface COMPANY {
    int GOOGLE = 1,
    int APPLE = 2,
    int ORACLE = 3
}

public class Demo {

    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;
        }
    }
}

💥 문제점

1
2
3
4
5
6
7
8
9
10
11
interface FRUIT {
    int APPLE = 1,
    int PEACH = 2,
    int BANANA = 3
}

interface COMPANY {
    int GOOGLE = 2,
    int APPLE = 1,
    int ORACLE = 3
}
1
2
3
4
5
public static void main(String[] args) {
    if (FRUIT.APPLE == COMPANY.APPLE) { // true
        System.out.println("과일애플과 기업애플은 같습니다.");
    }
}

FRUIT.APPLECOMPANY.APPLE 은 비교를 할 수 없는 대상이지만 true 가 나오는 것… (데이터타입이 int 이기 때문)
이것은 컴파일 에러가 발생하지 않기 때문에 앱을 구동하는 과정에서
에러가 발생하면 이 에러는 찾기가 매우 어렵다.

3️⃣ 클래스로 분리

데이터타입이 각각 다르기 때문에 비교가 불가능하도록 해결했지만, switch문에서 사용할 수 없다.

switch 문의 조건으로는 몇가지 제한된 데이터 타입만을 사용할 수 있다.
byte, short, char, int, enum, String, Character, Byte, Short, Integer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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 Demo {
    public static void main(String[] args) {
        // ✅ 데이터타입이 다르므로 컴파일 에러 발생
        if (FRUIT.APPLE == COMPANY.APPLE) {
            System.out.println("과일애플과 기업애플은 같습니다.");
        }

        Fruit 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;
        }
    }
}

4️⃣ enum

위 문제들을 해결하기 위해 자바에서 문법적으로 지원

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
enum Fruit {
    APPLE, PEACH, BANANA
}

enum Company {
    GOOGLE, APPLE, ORACLE
}

public class Demo {
    public static void main(String[] args) {
        Fruit type = Fruit.APPLE;
        switch (type) {
            // 💡 switch문으로 전달한 데이터타입을
            //    switch문은 알고있기 때문에 상수명만 작성
            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 과 생성자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum Fruit {
    APPLE, PEACH, BANANA;

    // 생성자
    Fruit() {
        System.out.println("Call Constructor " + this);
    }
}

public class Demo {
    public static void main(String[] args) {
        Fruit type = Fruit.APPLE;
    }
}

enum&constructor1개만 호출해도 3개 모두 생성됨

예제1️⃣


ButtonMode.java

1
2
3
public enum ButtonMode {
    LIGHT, DARK
}

ButtonSpace.java

1
2
3
public enum ButtonSpace {
    SINGLE, DOUBLE, TRIPLE
}

Button.java

1
2
3
4
5
6
7
8
9
10
11
12
public class Button {
    private ButtonMode buttonMode = ButtonMode.LIGHT;
    private ButtonSpace buttonSpace = ButtonSpace.SINGLE;

    public void setButtonMode(ButtonMode buttonMode) {
        this.buttonMode = buttonMode;
    }

    public void setButtonSpace(ButtonSpace buttonSpace) {
        this.buttonSpace = buttonSpace;
    }
}

Main.java

1
2
3
4
5
6
7
Button button1 = new Button();

button1.setButtonMode(ButtonMode.DARK);
button1.setButtonSpace(ButtonSpace.TRIPLE);

// ⚠️ 아래와 같은 오용이 방지됨
// button1.setButtonMode(ButtonSpace.DOUBLE);

예제2️⃣ (클래스 내부 작성)


Button.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Button {
    enum Mode { LIGHT, DARK }
    enum Space { SINGLE, DOUBLE, TRIPLE }

    private Mode mode = Mode.LIGHT;
    private Space space = Space.SINGLE;

    public void setMode(Mode mode) {
        this.mode = mode;
    }

    public void setSpace(Space space) {
        this.space = space;
    }
}

Main.java

1
2
3
4
Button button1 = new Button();

button1.setMode(Button.Mode.DARK);
button1.setSpace(Button.Space.DOUBLE);

예제3️⃣ (추가 기능)

클래스처럼 필드, 생성자, 메소드를 가질 수 있다.

YalcoChickenMenu.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public enum YalcoChickenMenu {
    FR("후라이드", 10000, 0),
    YN("양념치킨", 12000, 1),
    GJ("간장치킨", 12000, 0),
    RS("로제치킨", 14000, 0),
    PP("땡초치킨", 13000, 2),
    XX("폭렬치킨", 13000, 3);

    private String name;
    private int price;
    private int spicyLevel;

    YalcoChickenMenu(String name, int price, int spicyLevel) {
        this.name = name;
        this.price = price;
        this.spicyLevel = spicyLevel;
    }

    public String getName() { return name; }
    public int getPrice() { return price; }

    public void setPrice(int price) {
        this.price = price;
    }

    public String getDesc () {
        String peppers = "";
        if (spicyLevel > 0) {
            peppers = "🌶️".repeat(spicyLevel);
        }

        return "%s %s원 %s"
                .formatted(name, price, peppers);
    }
}

Main.java

1
2
3
4
5
6
7
YalcoChickenMenu menu1 = YalcoChickenMenu.YN;
YalcoChickenMenu menu2 = YalcoChickenMenu.RS;
YalcoChickenMenu menu3 = YalcoChickenMenu.XX;

String menu1Name = menu1.getName(); // 양념치킨
int menu2Price = menu2.getPrice(); // 14000
String menu3Desc = menu3.getDesc(); // 폭렬치킨 13000원 🌶️🌶️🌶️
1
2
menu2.setPrice(16000);
int menu2NewPrice = menu2.getPrice(); // 16000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//  ⭐️ 열거형의 메소드들

// valueOf()
// 문자열로부터 enum 객체 반환
// 없다면 런타임 에러 발생
YalcoChickenMenu[] byNames = new YalcoChickenMenu[] {
        YalcoChickenMenu.valueOf("FR"),
        YalcoChickenMenu.valueOf("PP"),
        YalcoChickenMenu.valueOf("GJ"),
        // YalcoChickenMenu.valueOf("NN"), // ⚠️ 런타임 에러
};

//  💡 name 메소드 : 각 항목의 이름 반환
// names: ["YN", "RS", "XX"]
String[] names = new String[] {
        menu1.name(), menu2.name(), menu3.name()
};

//  💡 ordinal 메소드 : 순번 반환
// orders: [1, 3, 5]
int[] orders = new int[] {
        menu1.ordinal(), menu2.ordinal(), menu3.ordinal()
};

//  💡 values 메소드 : 전체 포함된 배열 반환
//  YalcoChickenMenu[] 자료형
YalcoChickenMenu[] menus = YalcoChickenMenu.values();

for (YalcoChickenMenu menu : menus) {
    System.out.println(menu.getDesc());
}

예제4️⃣


YalcoChicken.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class YalcoChicken {
    static YalcoChickenMenu[] menus = YalcoChickenMenu.values();

    public void takeOrder (String menuName) {
        YalcoChickenMenu ordered = null;

        for (YalcoChickenMenu menu : menus) {
            if (menu.getName().equals(menuName)) {
                ordered = menu;
            }
        }

        if (ordered == null) {
            System.out.println("해당 메뉴가 없습니다.");
            return;
        }

        System.out.println(
                ordered.getPrice() + "원입니다."
        );
    }
}

Main.java

1
2
3
4
5
6
7
System.out.println("\n- - - - -\n");

YalcoChicken store1 = new YalcoChicken();

for (String menuName : "양념치킨,능이백숙,땡초치킨".split(",")) {
    store1.takeOrder(menuName);
}

image