Unicorns

All the things

ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java Study] - enum type
    Java 2021. 10. 20. 13:29

    Enum Type (열거형)

    열거형이란 ?

    관련된 상수들을 같이 묶어 놓은 special class 이다.

     

    JDK 1.5 이후 부터 열거형을 사용 할 수 있었는데,  java 에서는 변수 , 메서드, 생성자를 열거형에 추가할 수 있기 때문에

     C 나 C++ 보다 더욱 향상된 성능으로 Enum 클래스를 사용할 수 있다.  

     

     

    열거형 정의

    enum 열거형이름 {
        상수명1, 상수명2
    }

    class 밖에서 선언

    enum Color {
        RED, GREEN, BLUE;
    }
    
    public class Test {
        public static void main(String[] args) {
            Color c1 = Color.RED;
            System.out.println(c1);
        }
    }

    class 안에서 선언

    public class Test {
        enum Color {
            RED, GREEN, BLUE;
        }
    
        public static void main(String[] args) {
            Color c1 = Color.RED;
            System.out.println(c1);
        }
    }

     

    열거형 이점

    JDK 1.5 이전에  Enum Type 이 탄생하기 전에는

    public class EnumTest {
        private static final int RED = 1;
        private static final int GREEN = 2;
        private static final int BLUE = 3;
    
    
        public static void main(String[] args) {
            System.out.println(RED + GREEN / BLUE); // 의미가 없는 연산일 뿐
    
        }
    }

    상수가 필요할 때마다 임의의 정수를 지정해줬다.

     

    열거된 상수는 정수이기 때문에 산술 계산이 가능한데 

    계산을 하려고 정의한 것이 아니기 때문에 이것은 의미가 없다

     

    또한 , 

    기존의 color 를 의미하는 red 외에 추가적으로 rainbow 라는 정보를 담기 위해 상수를 지정해줬는데 ,

    이때 이름의 충돌이 발생하기 때문에 이를 방지하기 위해 고유 식별자 형식을 앞에 붙어주었다. ( COLOR_ , RAINBOW_)

    public class EnumTest {
        private static final int COLOR_RED = 1;
        private static final int COLOR_GREEN = 2;
        private static final int COLOR_BLUE = 3;
    
        private static final int RAINBOW_RED = 1;
        private static final int RAINBOW_GREEN = 2;
        private static final int RAINBOW_BLUE = 3;
    
    
        public static void main(String[] args) {
            System.out.println(COLOR_RED == RAINBOW_RED); // true
        }
    }

    rainbow 라는 정보를 추가하였지만 여전히 문제가 남아있다.

     

    COLOR_RED == RAINBOW_RED 는 사실 의미상 false 이어야 하지만

    true가 출력된다. 

     

    정수로 지정했기 때문에 컴파일러 입장에서는 정수 값을 비교하는 셈이니 컴파일 에러가 발생하지 않는다.

    즉 서로 다른 개념인데 비교가 가능하다는 문제가 발생한다.

     

    Enum Type 으로 해결 

    Color 와 RainBow 서로 다른 개념이기 때문에 비교가 불가능하고

    namespace 충돌이 발생하지 않는다. 

     

     

    또 다른 이점으로  switch문 인수로 전달이 가능하다는 것이다.

     

    사용자 정의 타입은 switch 문을 사용할 수 없다. 

    컴파일 에러 발생 : 

    Incompatible types. Found: 'step11.Color', required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum 

     

    반면 enum 타입은 사용이 가능하다. 

    enum Color {
        RED, GREEN, BLUE;
    }
    
    public class Test {
        public static void main(String[] args) {
            Test test = new Test();
            test.print(Color.BLUE);
    
        }
        public void print(Color color) {
            switch (color) {
                case RED:
                    System.out.println("빨간색 입니다.");
                    break;
                case BLUE:
                    System.out.println("파란색 입니다.");
                    break;
                case GREEN:
                    System.out.println("초록색 입니다. ");
                    break;
            }
        }
    }

    실행 결과

    파란색 입니다.

     

    java.lang.Enum

    public enum Day {
        SUNDAY , MONDAY , TUESDAY , WEDNESDAY , THURSDAY , FRIDAY , SATURDAY
    }

    위에 있는  Day 클래스의 바이트 코드를 살펴보자

     

    javap -c  Day.class

    모든 field 앞에 static final 키워드가 붙어있기 때문에

    여태껏 , enum 의 이름에 접근 할 수 있었다. ex) Day.SUNDAY

    // Day는 java.lang.Enum 클래스를 상속 받는 final 클래스이다.
    public final class step11.Day extends java.lang.Enum<step11.Day> {
      // 모든 field 는 static final로 상수이다.
      public static final step11.Day SUNDAY;
    
      public static final step11.Day MONDAY;
    
      public static final step11.Day TUESDAY;
    
      public static final step11.Day WEDNESDAY;
    
      public static final step11.Day THURSDAY;
    
      public static final step11.Day FRIDAY;
    
      public static final step11.Day SATURDAY;
    
      // values() 메서드를 구현
      public static step11.Day[] values();
        Code:
           0: getstatic     #1                  // Field $VALUES:[Lstep11/Day;
           3: invokevirtual #2                  // Method "[Lstep11/Day;".clone:()Ljava/lang/Object;
           6: checkcast     #3                  // class "[Lstep11/Day;"
           9: areturn
    
      // valueOf() 메서드 구현
      public static step11.Day valueOf(java.lang.String);
        Code:
           0: ldc           #4                  // class step11/Day
           2: aload_0
           3: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
           6: checkcast     #4                  // class step11/Day
           9: areturn
    
      static {}; // 모든 멤버를 static { } 으로 초기화
        Code:
           0: new           #4                  // class step11/Day
           3: dup
           4: ldc           #7                  // String SUNDAY
           6: iconst_0
           7: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
          10: putstatic     #9                  // Field SUNDAY:Lstep11/Day;
          13: new           #4                  // class step11/Day
          16: dup
          17: ldc           #10                 // String MONDAY
          19: iconst_1
          20: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
          23: putstatic     #11                 // Field MONDAY:Lstep11/Day;
          26: new           #4                  // class step11/Day
          29: dup
          30: ldc           #12                 // String TUESDAY
          32: iconst_2
          33: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
          36: putstatic     #13                 // Field TUESDAY:Lstep11/Day;
          39: new           #4                  // class step11/Day
          42: dup
          43: ldc           #14                 // String WEDNESDAY
          45: iconst_3
          46: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
          49: putstatic     #15                 // Field WEDNESDAY:Lstep11/Day;
          52: new           #4                  // class step11/Day
          55: dup
          56: ldc           #16                 // String THURSDAY
          58: iconst_4
          59: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
          62: putstatic     #17                 // Field THURSDAY:Lstep11/Day;
          65: new           #4                  // class step11/Day
          68: dup
          69: ldc           #18                 // String FRIDAY
          71: iconst_5
          72: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
          75: putstatic     #19                 // Field FRIDAY:Lstep11/Day;
          78: new           #4                  // class step11/Day
          81: dup
          82: ldc           #20                 // String SATURDAY
          84: bipush        6
          86: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
          89: putstatic     #21                 // Field SATURDAY:Lstep11/Day;
          92: bipush        7
          94: anewarray     #4                  // class step11/Day
          97: dup
          98: iconst_0
          99: getstatic     #9                  // Field SUNDAY:Lstep11/Day;
         102: aastore
         103: dup
         104: iconst_1
         105: getstatic     #11                 // Field MONDAY:Lstep11/Day;
         108: aastore
         109: dup
         110: iconst_2
         111: getstatic     #13                 // Field TUESDAY:Lstep11/Day;
         114: aastore
         115: dup
         116: iconst_3
         117: getstatic     #15                 // Field WEDNESDAY:Lstep11/Day;
         120: aastore
         121: dup
         122: iconst_4
         123: getstatic     #17                 // Field THURSDAY:Lstep11/Day;
         126: aastore
         127: dup
         128: iconst_5
         129: getstatic     #19                 // Field FRIDAY:Lstep11/Day;
         132: aastore
         133: dup
         134: bipush        6
         136: getstatic     #21                 // Field SATURDAY:Lstep11/Day;
         139: aastore
         140: putstatic     #1                  // Field $VALUES:[Lstep11/Day;
         143: return
    }

     

     

    위의 Day enum 클래스 처럼 모든 열거형은 Enum 의 자손이다.

    public abstract class Enum<E extends Enum<E>>
            implements Comparable<E>, Serializable {
        /**
         * The name of this enum constant, as declared in the enum declaration.
         * Most programmers should use the {@link #toString} method rather than
         * accessing this field.
         */
        private final String name;
    
        /**
         * Returns the name of this enum constant, exactly as declared in its
         * enum declaration.
         *
         * <b>Most programmers should use the {@link #toString} method in
         * preference to this one, as the toString method may return
         * a more user-friendly name.</b>  This method is designed primarily for
         * use in specialized situations where correctness depends on getting the
         * exact name, which will not vary from release to release.
         *
         * @return the name of this enum constant
         */
        public final String name() {
            return name;
        }
        
        ....
        ...
        ...
        
        }

    보다시피 , Enum 은 추상 클래스이기 때문에, 객체를 생성할 수 없다.

    그렇기 때문에 생성자를 직접적으로 호출할 수 없다. 

    (enum에서  public 또는 protected 생성자를 만들게 되면 컴파일 에러가 발생한다.)

     

    javap -p  Day.class

    Compiled from "Day.java"
    public final class step11.Day extends java.lang.Enum<step11.Day> {
      public static final step11.Day MONDAY;
      public static final step11.Day FRIDAY;
      public static final step11.Day WEDNESDAY;
      public static final step11.Day THURSDAY;
      public static final step11.Day TUESDAY;
      public static final step11.Day SATURDAY;
      public static final step11.Day SUNDAY;
      private static final step11.Day[] $VALUES;
      public static step11.Day[] values();
      public static step11.Day valueOf(java.lang.String);
      private step11.Day();
      static {};
    }

    enum 은 기본적으로 private 생성자를 포함하고 있다.

     

    또한, 내부적으로 java.lang.Enum 클래스를 상속 받고 있기 때문에

    단일 상속 원칙으로 다른 클래스를 상속 받을 수 없다.

    상속 불가

     

    사용자 지정 값 할당하기

    각 enum 에 위와 같이 값을 지정할 수 있다.

     

    하지만 컴파일 에러가 발생하는데 , 

    매개변수가 있는 생성자가 없기 때문이다.

     

    public enum Day {
        MONDAY("월요병"), TUESDAY("화요팅"),
        WEDNESDAY("벌써 수요일"), THURSDAY("목요팅"),
        FRIDAY("불금"), SATURDAY("알고리즘 공부하는 날"), SUNDAY("내일 출근 ㅠ");
    
        private String comment;
    
        Day(String comment) {
            this.comment = comment;
        }
    }

    값을 담을 필드와 초기화시 사용할 생성자를 만들었다.

    public enum Day {
        MONDAY("월요병"), TUESDAY("화요팅"),
        WEDNESDAY("벌써 수요일"), THURSDAY("목요팅"),
        FRIDAY("불금"), SATURDAY("알고리즘 공부하는 날"), SUNDAY("내일 출근 ㅠ");
    
        private String comment;
    
        Day(String comment) {
            this.comment = comment;
        }
        public String getComment() {
            return comment;
        }
    }

    그리고 값을 꺼내기 위해 메서드 또한 생성하였다.

    public class Main {
        public static void main(String[] args) {
            Day sunday = Day.SUNDAY;
            System.out.println(sunday.getComment());
        }
    }

    실행 결과

    내일 출근 ㅠ

     

     

     

    메서드

    메서드 설명
    T values() 해당 열거형의 모든 상수를 저장한 배열을 생성하여 반환한다.
    위의 바이트코드에 보면 나와있듯이 , 자바의 모든 열거형에 컴파일러가 자동으로 추가해 주는 메소드이다. 
    T valueOf(String value) 전달된 문자열과 일치하는 해당 열거형의  상수를 반환한다. 이 메소드 또한 컴파일러가 자동으로 추가해준다.
    int ordinal() 해당 열거형 상수가 정의된 순서를 반환한다.
    이때 반환 값은 열거형 정의에서 해당 상수가 정의된 순서이지, 상수 값이 아님을 명심해야 한다.
    String name() 열거형 상수의 이름을 문자열로 반환

     

    public enum Day {
        SUNDAY , MONDAY , TUESDAY , WEDNESDAY , THURSDAY , FRIDAY , SATURDAY
    }

    values()

    public class Test {
        public static void main(String[] args) {
            Day[] values = Day.values();
            for (Day day : values) {
                System.out.println(day);
            }
        }
    }

    실행 결과

    SUNDAY
    MONDAY
    TUESDAY
    WEDNESDAY
    THURSDAY
    FRIDAY
    SATURDAY

     

    valueOf()

    public class Test {
        public static void main(String[] args) {
            Day day = Day.valueOf("SUNDAY");
            System.out.println(day);
        }
    }

    실행 결과

    SUNDAY

     

    해당 열거형 상수 중에 일치하는 값이 없으면  java.lang.IllegalArgumentException 이 발생한다.

        public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                    String name) {
            T result = enumType.enumConstantDirectory().get(name);
            if (result != null)
                return result;
            if (name == null)
                throw new NullPointerException("Name is null");
            throw new IllegalArgumentException(
                "No enum constant " + enumType.getCanonicalName() + "." + name);
        }

    SUNDAY , MONDAY , TUESDAY , WEDNESDAY , THURSDAY , FRIDAY , SATURDAY 중에 일치하는 상수가 없다.

    public class Test {
        public static void main(String[] args) {
            Day day = Day.valueOf("HOLIDAY");
            System.out.println(day); //  java.lang.IllegalArgumentException
        }
    }

     

    ordinal()

    public enum Day {
        SUNDAY , MONDAY , TUESDAY , WEDNESDAY , THURSDAY , FRIDAY , SATURDAY
        // 0      1        2           3         4           5       6
    }
    public class Test {
        public static void main(String[] args) {
            int ordinal = Day.TUESDAY.ordinal();
            System.out.println(ordinal);
        }
    }

    실행 결과 

    2

     

    여기서 상수를 정의하는 순서를 변경해보자

    public enum Day {
          MONDAY ,FRIDAY , WEDNESDAY , THURSDAY ,  TUESDAY , SATURDAY , SUNDAY
        // 0      1        2           3         4           5       6
    }
    public class Test {
        public static void main(String[] args) {
            int ordinal1 = Day.TUESDAY.ordinal();
            int ordinal2 = Day.SUNDAY.ordinal();
            System.out.println(ordinal1);
            System.out.println(ordinal2);
        }
    }

    실행 결과

    4
    6

     

    name()

    public class Test {
        public static void main(String[] args) {
            System.out.println(Day.MONDAY.name());
            System.out.println(Day.THURSDAY.name());
            System.out.println(Day.WEDNESDAY.name());
            System.out.println(Day.SUNDAY.name());
        }
    }

    실행 결과

    MONDAY
    THURSDAY
    WEDNESDAY
    SUNDAY

     

    열거형 비교

    열거형 상수에 비교 연산자는 사용 할 수 없다.  컴파일 에러 발생

    대신 compareTo() 메서드로 비교 할 수 있다.

    enum Color {
        // 0   1      2       3      4     5
        RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE
    }
    
    public class Test {
        public static void main(String[] args) {
            System.out.println(Color.RED.compareTo(Color.YELLOW)); // -2
            System.out.println(Color.RED.compareTo(Color.PURPLE)); // -5
            System.out.println(Color.BLUE.compareTo(Color.GREEN)); // 1
        }
    }

    실행 결과

    -2
    -5
    1

     

    == 연산자와 equals() 메서드

     

    두 개의 enum 이 일치하는지 확인 할 때 사용한다. 

    enum Color {
        RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE
    }
    
    public class Test {
        Color color;
    
        public Test(Color color) {
            this.color = color;
        }
    
        public static void main(String[] args) {
            Test test = new Test(Color.ORANGE);
            System.out.println(test.color == Color.ORANGE);
            System.out.println(test.color.equals(Color.ORANGE));
            System.out.println(test.color == Color.RED);
            System.out.println(test.color.equals(Color.RED));
        }
    }

    실행 결과

    true
    true
    false
    false

     

     

    그렇다면 == 연산자와 equals() 차이점은 무엇일까?

    enum Color {
        RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE
    }
    
    public class Test {
        Color color;
    
        public Test() {
        }
    
        public static void main(String[] args) {
            Test test = new Test();
            System.out.println(test.color == Color.BLUE); // false
        }
    }

    실행 결과 

    false

    == 연산자는 NullPointerException이 발생하지 않는다.

     

    enum Color {
        RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE
    }
    
    public class Test {
        Color color;
    
        public Test() {
        }
    
        public static void main(String[] args) {
           Test test = new Test();
           System.out.println(test.color.equals(Color.BLUE)); // java.lang.NullPointerException 발생
        }
    }

    반면에 equals() 메서드는 NullPointerException이 발생한다.

     

     

    서로 다른 enum 타입일 때 사용해보면 

     

    == 연산자에서는 컴파일 에러가 발생한다.

     

    반면 eqauls() 메서드는 컴파일 에러가 발생하지 않는다.

    enum Color {
        RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE
    }
    
    enum Rainbow {
        RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
    
    }
    
    public class Test {
        public static void main(String[] args) {
            System.out.println(Color.RED.equals(Rainbow.RED)); // false
        }
    }

    단지 false 만 반환할 뿐이다.

     

     

     

     

    http://tcpschool.com/java/java_api_enum
    https://www.geeksforgeeks.org/enum-in-java/
    https://titanwolf.org/Network/Articles/Article?AID=bec9bb38-9f61-4f30-be4b-3b63e6b3ef7b
    https://www.geeksforgeeks.org/comparing-enum-members-java/?ref=rp
    자바의 정석

     

    댓글

Designed by Tistory.