공부일기/자바 스터디

Java 항해일지 11. Enum

Youngbin Kim 2021. 1. 26. 15:42

목표

자바의 열거형에 대해 학습하세요.

학습할 것 (필수)

  • enum 정의하는 방법
  • enum이 제공하는 메소드 (values()와 valueOf())
  • java.lang.Enum
  • EnumSet

enum이란?

enum은 열거형(enumerated type)이라고 부른다. 열거형은 서로 연관된 상수들의 집합이라고 할 수 있다.

열거형은 연관된 값들을 저장하며, 저장된 값들이 변경되지 않도록 보장한다. 또한 enum의 경우 열거형 그 자체이면서 클래스이기 때문에 enum내부에 생성자나 필드, 메서드를 가질 수 있어 다양한 역할을 할 수 있다.

상수 목록이 필요해 상수목록만 적어놓은 class를 활용한 적이 있는데, 이런 사용은 자제하고 enum을 활용할 수 있다.

 

 

enum 정의하는 방법

1. 별도의 java 파일로 정의

따로 Enum클래스를 만들 수 있다.
Enum 클래스를 이용한 정의.

 

2. 클래스 내부에서 정의

public class MonthClass {
    public enum Month {
        JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC;
    }

    public static void main(String[] args) {
        Month jan = Month.JAN;
        System.out.println(jan);
    }
}

 

 

3. 클래스 외부에서 정의

enum Month {
    JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC;
}

public class MonthClass {
    public static void main(String[] args) {
        Month jan = Month.JAN;
        System.out.println(jan);
    }
}

 

몇몇 블로그에서 ;(세미콜론)은 생략할 수 없고 반드시 사용해야 한다는 것을 봤는데 이는 잘못된 사실이다. 상수로 사용할 목록만 정의하는 경우 세미콜론은 생략 가능하고 실제로 인텔리제이에서 세미콜론을 붙여봤는데 아래와 같이 불필요한 세미콜론을 제거할 것을 추천했다.

 

enum이 유용하게 사용될 수 있는 상황은 어떤 상황이 있을까?

예를 들어 숫자만 알고, 영어는 아예 모르는 사람이 '26, JAN'과 같이 표기된 달력을 본다고 가정해보자. 이 사람은 26이 날짜인 건 알고 JAN은 모른다. 이때, JAN을 입력했을 때 숫자로 바꿔서 알려주는 프로그램의 코드는 아래와 같을 것이다. (예제는 Enum을 별도의 파일로 정의했다.)

 

public class MonthClass {
    public static void main(String[] args) {
        // JAN 입력 받음
        MonthEnum month = MonthEnum.JAN;
        int numOfMonth = 0;

        switch (month) {
            case JAN: numOfMonth = 1; break;
            case FEB: numOfMonth = 2; break;
            case MAR: numOfMonth = 3; break;
            case APR: numOfMonth = 4; break;
            case MAY: numOfMonth = 5; break;
            case JUN: numOfMonth = 6; break;
            case JUL: numOfMonth = 7; break;
            case AUG: numOfMonth = 8; break;
            case SEP: numOfMonth = 9; break;
            case OCT: numOfMonth = 10; break;
            case NOV: numOfMonth = 11; break;
            case DEC: numOfMonth = 12; break;
        }

        System.out.println(month + ": " + numOfMonth + "월 입니다.");
    }
}

 

enum 작성에 switch문까지 작성할 코드가 굉장히 많다. enum을 제대로 활용하고 있는 코드라고 할 수는 없다.

enum의 각 상수를 대표하는 정수를 지정해서 사용한다면? 코드는 다음과 같이 간결해 질 수 있다.

먼저 enum을 아래와 같이 수정한다.

public enum MonthEnum {
    JAN(1),
    FEB(2),
    MAR(3),
    APR(4),
    MAY(5),
    JUN(6),
    JUL(7),
    AUG(8),
    SEP(9),
    OCT(10),
    NOV(11),
    DEC(12);

    private int numOfMonth;
    MonthEnum(int numOfMonth) {
        this.numOfMonth = numOfMonth;
    }

    public int getNumOfMonth() {
        return numOfMonth;
    }
}

각 상수를 대표하는 정수 값을 지정해주고, 생성자를 만들고, 정수  값을 반환받는 메서드도 만들어줬다.

 

 

enum이 수정됐다면 메인메서드는 아래와 같이 간결해질 수 있다.

public class MonthClass {
    public static void main(String[] args) {
        // JAN 입력 받음
        MonthEnum month = MonthEnum.JAN;
        System.out.println(month + ": " + MonthEnum.JAN.getNumOfMonth() + "월 입니다.");
    }
}

훨씬 간결한 코드가 같은 값을 출력해주고 있다.

 

이번에는 숫자를 모르고 영어밖에 모르는 사람이 있다고 가정해보자. 이 사람은 JAN과 같이 축약된 단어는 알아볼 수가 없고, January같이 full name을 이용해서만 달력을 읽을 수 있다. 이런 사람을 위한 코드는 아래와 같다.

public enum MonthEnum {
    JAN(1, "January"),
    FEB(2, "February"),
    MAR(3, "March"),
    APR(4, "April"),
    MAY(5, "May"),
    JUN(6, "June"),
    JUL(7, "July"),
    AUG(8, "August"),
    SEP(9, "September"),
    OCT(10, "October"),
    NOV(11, "November"),
    DEC(12, "December");

    private int numOfMonth;
    private String fullNameOfMonth;

    MonthEnum(int numOfMonth) {
        this.numOfMonth = numOfMonth;
    }
    MonthEnum(int numOfMonth, String fullNameOfMonth) {
        this.numOfMonth = numOfMonth;
        this.fullNameOfMonth = fullNameOfMonth;
    }

    public int getNumOfMonth() {
        return numOfMonth;
    }

    public String getFullNameOfMonth() {
        return fullNameOfMonth;
    }
}

생성자 오버라이딩을 통한 enum을 수정하고 메인메서드를 다음과 같이 수정했다.

 

public class MonthClass {
    public static void main(String[] args) {
        // JAN 입력 받음
        MonthEnum month = MonthEnum.JAN;
        System.out.println(month + ": '" + MonthEnum.JAN.getFullNameOfMonth() + "' 입니다.");
    }
}

 

만약 숫자와 full name을 같이 보고 싶다면 메인메서드를 다음과 같이 수정할 수 있을 것이다.

public class MonthClass {
    public static void main(String[] args) {
        // JAN 입력 받음
        MonthEnum month = MonthEnum.JAN;
        System.out.println(month + ": " + MonthEnum.JAN.getNumOfMonth() +
                            "월, full-name은 '" + MonthEnum.JAN.getFullNameOfMonth() + "' 입니다.");
    }
}

 

이러한  기능 뿐만 아니라 함수형 인터페이스를 사용할 수도 있다. 아래는 각 달의 주제를 함수형 인터페이스를 통해 출력한 예제이다. 

"January", "February" 등 일반 문자열을 출력하는 것과 같은 기능이지만, 어떻게 구현했는지가 다르다.

import java.util.function.Supplier;

public enum MonthEnum {
    JAN(1, "January", () -> "주제가 있는 달"),
    FEB(2, "February", () -> "생각의 달"),
    MAR(3, "March", () -> "생명의 달"),
    APR(4, "April", () -> "과학의 달"),
    MAY(5, "May", () -> "가정의 달"),
    JUN(6, "June", () -> "호국 보훈의 달"),
    JUL(7, "July", () -> "자연과 환경의 달"),
    AUG(8, "August", () -> "환상의 달"),
    SEP(9, "September", () -> "독서의 달"),
    OCT(10, "October", () -> "문화의 달"),
    NOV(11, "November", () -> "불조심 강조의 달"),
    DEC(12, "December", () -> "나눔 달");

    private int numOfMonth;
    private String fullNameOfMonth;
    private Supplier<String> monthInfo;

    MonthEnum(int numOfMonth, String fullNameOfMonth, Supplier<String> monthInfo) {
        this.numOfMonth = numOfMonth;
        this.fullNameOfMonth = fullNameOfMonth;
        this.monthInfo = monthInfo;
    }

    public void printMonthInfo() {
        System.out.println(monthInfo.get());
    }
}
public class MonthClass {
    public static void main(String[] args) {
        // JAN 입력 받음
        MonthEnum month = MonthEnum.JAN;
        month.printMonthInfo();
    }
}

 

 

enum이 제공하는 메소드 (values()와 valueOf())

enum이 제공하는 기본적인 메서드들은 values()와 valueOf() 외에도 ordinal(), name() 등이 있다. 예제를 통해 살펴보자.

 

values()

enum 안에 있는 모든 상수들을 배열로 반환한다. 

import java.util.Arrays;

public class MonthClass {
    public static void main(String[] args) {
        Arrays.stream(MonthEnum.values()).forEach(System.out::println);
    }
}

import java.util.Arrays;

public class MonthClass {
    public static void main(String[] args) {
        Arrays.stream(MonthEnum.values()).forEach(m -> m.printMonthInfo());
    }
}

 

 

valueOf()

String타입을 매개변수로 받아 해당 매개변수와 동일한 이름의 상수를 찾아 반환한다. 없는 경우 IllegalArgumentException을 발생시킨다.

public class MonthClass {
    public static void main(String[] args) {
        System.out.println(MonthEnum.valueOf("Jan")); //JAN인 경우 JAN, Jan은 아래와 같이 예외 출력
    }
}

 

java.lang.Enum

enum 클래스는 java.lang.Enum 클래스를 상속받았으므로 다른 클래스를 상속받을 수 없다.(다중 상속 불가)

아래는 Enum 클래스에서 사용할 수 있는 다양한 메서드들의 예시다.

 

ordinal()

해당 상수가 Enum 클래스 내에 몇 번째로 선언됐는지를 반환한다. 

public class MonthClass {
    public static void main(String[] args) {
        MonthEnum month = MonthEnum.JAN;
        System.out.println(month.ordinal());
    }
}

 

 

name()

상수의 값을 반환한다. 

public class MonthClass {
    public static void main(String[] args) {
        MonthEnum month = MonthEnum.JAN;
        System.out.println(month.name());
    }
}

 

 

compareTo(E o)

비교하고자 하는 상수와 위치 값의 차이를 반환한다. (AUG = 7, JAN = 0)

public class MonthClass {
    public static void main(String[] args) {
        MonthEnum month = MonthEnum.AUG;
        System.out.println(month.compareTo(MonthEnum.JAN));
    }
}

 

 

 

EnumSet

EnumSet 클래스는 java.util 패키지에 정의되어 있으며, 우리가 흔히 알고 있는 Set 자료구조의 특징을 따른다.

 

import java.util.EnumSet;

public class MonthClass {
    public static void main(String[] args) {
        EnumSet<MonthEnum> enumSet = EnumSet.allOf(MonthEnum.class);
        // 모든 요소를 포함한 EnumSet 반환

        EnumSet<MonthEnum> winterEnumSet = EnumSet.of(MonthEnum.DEC, MonthEnum.JAN, MonthEnum.FEB);
        // 특정 상수만 포함한 EnumSet 반환

        EnumSet<MonthEnum> exceptWinterSet = enumSet.complementOf(winterEnumSet);
        // 특정 상수를 제외한 EnumSet 반환

        EnumSet<MonthEnum> springEnumSet = EnumSet.range(MonthEnum.MAR, MonthEnum.MAY);
        // 특정 범위의 EnumSet 반환, MAR, APR, MAY


        System.out.println(enumSet);
        System.out.println(winterEnumSet);
        System.out.println(exceptWinterSet);
        System.out.println(springEnumSet);
    }
}

 

 

 

 

참고.

docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Enum.html

www.tcpschool.com/java/java_api_enum

gowoonsori.site/java/enum/

blog.naver.com/hsm622/22221825174