ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Java 항해일지 11. Enum
    공부일기/자바 스터디 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

Designed by Tistory.