ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 11주차 과제: Enum
    Programming/Java live study 2021. 1. 27. 16:58
     

    11주차 과제: Enum · Issue #11 · whiteship/live-study

    목표 자바의 열거형에 대해 학습하세요. 학습할 것 (필수) enum 정의하는 방법 enum이 제공하는 메소드 (values()와 valueOf()) java.lang.Enum EnumSet 마감일시 2021년 1월 30일 토요일 오후 1시까지.

    github.com

    목표

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

    학습할 것

     - enum 정의하는 방법

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

     - java.lang.Enum

     - EnumSet


    1. Enum

    참고 :

    velog.io/@pop8682/Enum-27k067ns4a

    www.geeksforgeeks.org/enum-in-java/

    wisdom-and-record.tistory.com/52

    1.1 enum

     사계절을 뜻하는 봄, 여름, 가을, 겨울과 같이 한정된 값만을 가진 데이터 타입을 열거 타입 (Enumuration type)이라고 한다. 이처럼 프로그래밍에서 상수의 그룹을 나타내기 위해 사용된다. 

     

     Java 1.5 버전 부터 enum을 enum 데이터 타입으로 표시되었다. 특히 Java에서는 C/C++과 다르게 변수, 메소드, 생성자를 추가할 수 있다. 기존에 인터페이스나 클래스 내에서 상수를 선언하여 사용하였는데 상수를 관리하는데 있어서 나오는 단점들과 타입의 안정성, IDE의 지원을 받을 수 있도록 보완하여 나온 것이 Enum이다.

     

     또한 Type Safety를 보장한다. 밑의 코드는 type safety가 보장되는 것을 확인하는 간단한 코드이다.

     

    Type Safety 가 보장되지 않는 예

    public static void main(String[] args) {
        System.out.println("hello");
    }

     

     - 위 코드를 보면 "hello" 메시지는 일반적인 문자열이기 때문에 Type safe 하지 못하다. 그렇기 때문에 오타가 나더라도 컴파일러는 알 방법이 없다. 

     

    Type Safety 가 보장되는 예

    public class EnumExample {
        
        enum Greet {
            Hello(“hello”);
    
            Greet(String message) {
                this.message = message;
            }
            
            String message;
    
            public String getMessage() {
                return message;
            }
        }
        
        public static void main(String[] args) {
            System.out.println(Greet.Hello.getMessage());
        }
    }

     

     - Type safe 하지 못하는 단순한 문자열을 Greet라는 열거형의 으로 정의하고, type safe한 방식으로 message를 출력하였다. enum의 HELLO는 항상 "hello"가 출력되는 것을 보장한다.

     

     그 밖에 대표적인 예로는 SQL의 type safety를 보장하는 queryDSL이 있다! http://www.querydsl.com/

    1.2 Enum의 등장 배경

     밑의 코드를 살펴보며 Enum의 탄생 배경에 대하여 알아보았다. enum 이전에 상수를 사용하던 때에는 몇 가지 문제점을 가지고 있었다.

      - 상수에 부여된 리터럴은 상수와 관련이 없다.

      - 이름의 충돌이 발생할 수 있다.

     

    public class Season {
    
        public static final int SPRING = 1;
        public static final int SUMMBER = 2;
        public static final int FALL = 3;
        public static final int WINTER = 4;
    
        public static void main(String[] args) {
            int season = SPRING;
    
            switch (season) {
                case SPRING:
                    System.out.println("봄");
                    break;
                case SUMMBER:
                    System.out.println("여름");
                    break;
                case FALL:
                    System.out.println("가을");
                    break;
                case WINTER:
                    System.out.println("겨울");
                    break;
            }
        }
    }    

     

     

     첫 번째 문제를 확인하기 위해 간단하게 봄, 여름, 가을, 겨울을 1, 2, 3, 4라는 리터럴로 구분하여 부여하였다. 해당 리터럴은 단순히 상수를 구분하는 용도이고 논리적으로는 아무 의미가 없다. 즉 season 변수에 1이라는 값을 넣어도 SPRING을 넣었을 때랑 값을 결과가 나올 수 있다. 결국 상수와 상수에 부여된 리터럴들은 연관성이 없다는 것을 알 수 있었다.

     

    public class Season {
    
        public static final int SPRING = 1;
        public static final int SUMMBER = 2;
        public static final int FALL = 3;
        public static final int WINTER = 4;
    
        public static void main(String[] args) {
            int season = 1;
    
            switch (season) {
                case SPRING:
                    System.out.println("봄");
                    break;
                case SUMMBER:
                    System.out.println("여름");
                    break;
                case FALL:
                    System.out.println("가을");
                    break;
                case WINTER:
                    System.out.println("겨울");
                    break;
            }
        }
    }

     

     

     1을 저장하여도 동일한 결과가 나온다. 또한 다른 클래스의 이름이 같은 상수도 비교해보았다.

     

    public class Season {
    
        // season
        public static final int SPRING = 1;
        public static final int SUMMBER = 2;
        public static final int FALL = 3;
        public static final int WINTER = 4;
    }

     

    public class Framework {
    
        // framework
        public static final int SPRING = 1;
        public static final int DJANGO = 2;
        public static final int RUBY_ON_RAILS = 3;
        public static final int VUE_JS = 4;
        public static final int REACT_JS = 5;
        public static final int EXPRESS = 6;
    }

     

    public class Exam02 {
    
        public static void main(String[] args) {
    
            System.out.println(Season.SPRING == Framework.SPRING); // true
        }
    }

     

     서로 다른 SPRING을 의미하고 있지만, 1이라는 리터럴을 비교하기 때문에 true을 출력한다. 

     

     두번째 문제는 이름의 충돌이 발생하는 것이다. Season의 SPRING과 Framework의 SPRING을 비교해보았다. 같은 클래스에 상수를 선언하면 Seanson의 SPRING과 Framework의 SPRING이 서로 같은 변수명으로 충돌하게 된다. 그렇기 때문에 추가적으로 앞에 구분하는 문자를 붙여줘야 한다.

     

     - SEANSON_SPRING

     - FRAMEWOKR_SPRING

     

    public class Exam03 {
        // season
        static final int SEASON_SPRING = 1;
    
        // framework
        static final int FRAMEWORK_SPRING = 1;
    
        public static void main(String[] args) {
    
            System.out.println(SEASON_SPRING == FRAMEWORK_SPRING); // true
        }
    }

     

     계절의 SPRING과 framework의 SPRING은 서로 비교할 수 없는 완전히 다른 개념이다. 그렇기 때문에 작성할 수 없게 컴파일 과정에서 막아야 한다.

     

     비교조차 하지 못하도록 하기 위해서는 서로 다른 객체로 생성해주면 된다.

     

    public class Season {
    
        // season
        public static final Season SPRING = new Season();
        public static final Season SUMMBER = new Season();
        public static final Season FALL = new Season();
        public static final Season WINTER = new Season();
    }

     

    public class Framework {
    
        // framework
        public static final Framework SPRING = new Framework();
        public static final Framework DJANGO = new Framework();
        public static final Framework RUBY_ON_RAILS = new Framework();
        public static final Framework VUE_JS = new Framework();
        public static final Framework REACT_JS = new Framework();
        public static final Framework EXPRESS = new Framework();
    }

     

    컴파일 에러 발생

     하지만 객체로 생성하게 되면 switch문의 조건에 들어갈 수 없다.

     

    컴파일 에러 발생

     Enum은 위처럼 상수를 클래스로 정의하여 관리할 때 얻을 수 있는 이점을 모두 모아 간단하게 선언하여 사용할 수 있도록 하기 위해서 만들어졌다.


    2. enum 정의하는 방법

    참고 : velog.io/@kyle/%EC%9E%90%EB%B0%94-Enum-%EA%B8%B0%EB%B3%B8-%EB%B0%8F-%ED%99%9C%EC%9A%A9

    blog.naver.com/hsm622/222218251749

     

     가장 기본적은 enum 선언이다.

     

    public enum  Phone {
    
        GALAXY_S21,
        GALAXY_S21_PLUS,
        GALAXY_S21_ULTRA,
        GALAXY_Z_FLIP,
        GALAXY_Z_FOLD2
    }

     

     인스턴스만 생성한다면 ;이 생략 가능하다.

     

     또한 생성자 및 메소드를 추가할 수 있다.

     

    public enum Phone {
        GALAXY_S21(999_900, "SM-G991NZIEKOO"),
        GALAXY_S21_PLUS(1_119_900, "SM-G996NZVEKOO"),
        GALAXY_S21_ULTRA(1_452_000, "SM-G998NZKEKOO"),
        GALAXY_Z_FLIP(1_650_000, "SM-F707NZNAKOO"),
        GALAXY_Z_FOLD2(2_398_000, "SM-F916NZKAKOO");
    
        private final int price;
        private final String modelName;
    
        Phone(int price, String modelName) {
            this.price = price;
            this.modelName = modelName;
        }
    
        public int getPrice() {
            return this.price;
        }
    
        public String getModelName() {
            return this.modelName;
        }
    }

     

     스마트폰의 가격과 모델명이 들어있는 enum 타입을 작성하였다. 서로 관련 있는 상수 값들을 모아 enum으로 구현하는 경우 유용하게 사용할 수 있다. 또한 클래스와 같이 작성이 가능하다. 생성자가 존재하지만 Default 생성자는 private로 되어 있다. public으로 변경하면 컴파일 에러가 발생한다. 

     

    컴파일 에러가 발생한다.

     

     즉 다른 클래스나 인터페이스에서 상수 선언이 클래스 로드 시점에서 생성되는 것처럼 Enum 또한 생성자가 존재하지만 클래스가 로드되는 시점에 생성되기 때문에 임의로 생성하여 사용할 수 없다.

     

    public class Main {
    
        public static void main(String[] args) {
    //        Phone phone = new Phone(); 임의로 생성할 수 없다.
    
            Phone phone = Phone.GALAXY_S21;
            System.out.println(phone.toString());
        }
    }

     

     Enum 클래스에 선언된 상수들은 클래스 로드 시점에서 모두 생성되고, signleton 형태로 애플리케이션 전체에서 사용할 수 있다. Singleton Pattern이 궁금하다면? hyeonic.tistory.com/23

     

     signleton으로 사용되기 때문에 값을 유지하는 필드가 들어 있는 것은 매우 위험하기 때문에 조심해야 한다. 

     

    public enum Phone {
        GALAXY_S21(999_900, "SM-G991NZIEKOO"),
        GALAXY_S21_PLUS(1_119_900, "SM-G996NZVEKOO"),
        GALAXY_S21_ULTRA(1_452_000, "SM-G998NZKEKOO"),
        GALAXY_Z_FLIP(1_650_000, "SM-F707NZNAKOO"),
        GALAXY_Z_FOLD2(2_398_000, "SM-F916NZKAKOO");
    
        private final int price;
        private final String modelName;
        private int count;
    
        Phone(int price, String modelName) {
            this.price = price;
            this.modelName = modelName;
        }
    
        public void addCount(int count) {
            this.count += count;
        }
    }

     

     count 변수는 멀티쓰레드 환경에서 공유되고 있기 때문에 값을 유지하는 인스턴스 변수의 사용을 막거나 thread safe하게 유지해야 한다.

     

     또한 상속을 지원하지 않는다. 모든 enum은 내부적으로 java.lang.Enum 클래스에 의해 상속된다. Java에서는 다중 상속을 지원하지 않기 때문에 enum 클래스는 다른 클래스를 상속 받을 수 없다. 상속을 지원하지 않지만 다양한 인터페이스들은 구현할 수 있다.

     

    public final enum me/hyeonic/week11/Phone extends java/lang/Enum {
    
      // compiled from: Phone.java
    
      // access flags 0x4019
      public final static enum Lme/hyeonic/week11/Phone; GALAXY_S21
    
      // access flags 0x4019
      public final static enum Lme/hyeonic/week11/Phone; GALAXY_S21_PLUS
    
      // access flags 0x4019
      public final static enum Lme/hyeonic/week11/Phone; GALAXY_S21_ULTRA
    
      // access flags 0x4019
      public final static enum Lme/hyeonic/week11/Phone; GALAXY_Z_FLIP
    
      // access flags 0x4019
      public final static enum Lme/hyeonic/week11/Phone; GALAXY_Z_FOLD2
      
      ...

     

     바이트코드를 살펴보면 java.lang.Enum을 상속 받을 것을 알 수 있다. 또한 각각 인스턴스들은 public final static으로 선언되어 있다. 그렇기 때문에 각각의 객체의 주소 값은 바뀌지 않기 때문에 "==" 비교가 가능해진다.

     

     그렇다면 enum 안에 인스턴스는 어느 시점에 생성되는지 확인해보았다.

     

    public enum Phone {
        GALAXY_S21(999_900, "SM-G991NZIEKOO"),
        GALAXY_S21_PLUS(1_119_900, "SM-G996NZVEKOO"),
        GALAXY_S21_ULTRA(1_452_000, "SM-G998NZKEKOO"),
        GALAXY_Z_FLIP(1_650_000, "SM-F707NZNAKOO"),
        GALAXY_Z_FOLD2(2_398_000, "SM-F916NZKAKOO");
    
        private final int price;
        private final String modelName;
    
        Phone(int price, String modelName) {
            System.out.println(this.name() + " -> " + price + ", " + modelName);
            this.name();
            this.price = price;
            this.modelName = modelName;
        }
    
        public int getPrice() {
            return this.price;
        }
    
        public String getModelName() {
            return this.modelName;
        }
    
        @Override
        public String toString() {
            return "Phone{" +
                    "price=" + price +
                    ", modelName='" + modelName + '\'' +
                    '}';
        }
    }

     

     enum Phone이다. 생성자에 간단한 생성 정보를 출력하는 출력문을 추가하였다.

     

    public class Exam01 {                                                                   
                                                                                            
        public static void main(String[] args) {                                            
            System.out.println("==================== main ====================");           
            System.out.println("================ enum 변수 선언 ================");             
            Phone phone;                                                                    
            System.out.println("============== enum 변수에 값 할당 ==============");              
            phone = Phone.GALAXY_S21;                                                       
            System.out.println("=============== enum 변수 값 사용 ===============");             
            System.out.println(phone.name() + "의 가격은 " + phone.getPrice() + "이다.");         
            System.out.println("===================== end =====================");          
        }                                                                                   
    }                                                                                       

     

    ===================== main =====================
    ================ enum 변수 선언 =================
    ============== enum 변수에 값 할당 ==============
    GALAXY_S21 -> 999900, SM-G991NZIEKOO
    GALAXY_S21_PLUS -> 1119900, SM-G996NZVEKOO
    GALAXY_S21_ULTRA -> 1452000, SM-G998NZKEKOO
    GALAXY_Z_FLIP -> 1650000, SM-F707NZNAKOO
    GALAXY_Z_FOLD2 -> 2398000, SM-F916NZKAKOO
    =============== enum 변수 값 사용 ===============
    GALAXY_S21의 가격은 999900이다.
    ====================== end =====================

     

     enum 변수를 단순히 선언만 하면 내부의 상수들이 선언되지 않는다. 직접적으로 값을 할당하거나 사용할 때 비로소 모든 값들이 생성되는 것을 알 수 있다.


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

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

    3.1 values()

    public class Main {
    
        public static void main(String[] args) {
            Phone[] phones = Phone.values();
    
            for (Phone phone : phones) {
                System.out.println(phone.ordinal() + " " + phone.name());
                System.out.println("model name: " + phone.getModelName() + " price: " + phone.getPrice());
    
                System.out.println();
            }
        }
    }

     

    0 GALAXY_S21
    model name: SM-G991NZIEKOO price: 999900
    
    1 GALAXY_S21_PLUS
    model name: SM-G996NZVEKOO price: 1119900
    
    2 GALAXY_S21_ULTRA
    model name: SM-G998NZKEKOO price: 1452000
    
    3 GALAXY_Z_FLIP
    model name: SM-F707NZNAKOO price: 1650000
    
    4 GALAXY_Z_FOLD2
    model name: SM-F916NZKAKOO price: 2398000

     

     Enum 클래스가 가지고 있는 상수 값을 배열의 형태로 리턴한다. String 형태의 name을 반환하는게 아니라 enum phone이 가지고 있는 모든 상수의 인스턴스를 배열에 담아 반환한다. 그렇기 때문에 상속 받은 Enum 클래스의 메소드 뿐만 아니라 Phone에 있는 get메소드 또한 사용이 가능하다.

     

     한 가지 의문인 점은 상속받은 Enum 클래스에도 values()는 찾아볼 수 없었다.

     

     

     바이트코드를 살펴보면 컴파일타임에 삽입된 것을 확인할 수 있었다.

     

    values()와 valueOf()

    3.2 valueOf()

     valueOf() 메소드는 인자로 들어온 값과 일치하는 상수 인스턴스가 존재하면 해당 인스턴스를 반환한다. 단순히 문자열 반환이 아닌 인스턴스 자체를 반환한다.

     

    public class Main {
    
        public static void main(String[] args) {
        
            Phone galaxy21 = Phone.valueOf("GALAXY_S21");
    
            System.out.println(galaxy21.name());
            System.out.println(galaxy21.ordinal());
            System.out.println(galaxy21.getModelName());
            System.out.println(galaxy21.getPrice());
        }
    }

     

    GALAXY_S21
    0
    SM-G991NZIEKOO
    999900

     

     valueOf() 메소드는 IllegalArgumentException과 NullPointException을 던질 수 있다.

      - IllegalArgumentException: 지정된 enum 유형에 지정된 이름의 상수가 없거나 지정된 클래스 객체가 enum의 유형을 나타내지 않는 경우

     

    public class Main {
    
        public static void main(String[] args) {
        
            Phone galaxy20 = Phone.valueOf("GALAXY_S20");
    
            System.out.println(galaxy20.name());
            System.out.println(galaxy20.ordinal());
            System.out.println(galaxy20.getModelName());
            System.out.println(galaxy20.getPrice());
        }
    }

     

    Exception in thread "main" java.lang.IllegalArgumentException: No enum constant me.hyeonic.week11.Phone.GALAXY_S20
    	at java.base/java.lang.Enum.valueOf(Enum.java:240)
    	at me.hyeonic.week11.Phone.valueOf(Phone.java:3)
    	at me.hyeonic.week11.Main.main(Main.java:15)

     

     GALAXY_S20을 나타내는 상수는 없기 때문에 해당 예외를 던진다.

     

     - NullPointException: enumTpye 혹은 name이 null인 경우 

     

    public class Main {
    
        public static void main(String[] args) {
    
            Phone galaxy20 = Phone.valueOf(null); // 의도적으로 null을 넣는다.
    
            System.out.println(galaxy20.name());
            System.out.println(galaxy20.ordinal());
            System.out.println(galaxy20.getModelName());
            System.out.println(galaxy20.getPrice());
        }
    }

     

    Exception in thread "main" java.lang.NullPointerException: Name is null
    	at java.base/java.lang.Enum.valueOf(Enum.java:238)
    	at me.hyeonic.week11.Phone.valueOf(Phone.java:3)
    	at me.hyeonic.week11.Main.main(Main.java:15)

     

     name 값이 null이기 때문에 NullPointException을 던지는 것을 알 수 있다.


    4. java.lang.Enum

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

     

      java.lang.Enum 클래스는 Java의  enum 타임의 기반이 되는 클래스이다. enum type은 내부적으로 모두 java.lang.Enum 클래스를 상속한다. 또한 Enum 클래스의 생성자는 유일한 생성자로, 개발자는 해당 생성자를 호출할 수 없다. 

     

    public final enum me/hyeonic/week11/Phone extends java/lang/Enum {
    
      // compiled from: Phone.java
    
      // access flags 0x4019
      public final static enum Lme/hyeonic/week11/Phone; GALAXY_S21
    
      // access flags 0x4019
      public final static enum Lme/hyeonic/week11/Phone; GALAXY_S21_PLUS
    
      // access flags 0x4019
      public final static enum Lme/hyeonic/week11/Phone; GALAXY_S21_ULTRA
    
      // access flags 0x4019
      public final static enum Lme/hyeonic/week11/Phone; GALAXY_Z_FLIP
    
      // access flags 0x4019
      public final static enum Lme/hyeonic/week11/Phone; GALAXY_Z_FOLD2
      
      ...

     

     바이트코드를 살펴보면 java.lang.Enum을 상속한 것을 확인 할 수 있다. 

    4.1 메소드

    Modifier and Type method
    protected Object clone()
    int compareTo(E o)
    boolean equals(Object other)
    protected void finalize()
    Class<E> getDeclaringClass()
    int hashCode()
    String name()
    int ordinal()
    String toString()
    static <T extens Enum<T>> T valueOf(Class<T> enumType, String name)

     

     values()와 valueOf()는 위에서 살펴보았기 때문에 제외한 다른 메소드를 살펴보았다.

     

    4.1.1 public final String name()

     열거형 선언에서 선언한 대로 상수의 이름을 반환한다. 대부분의 개발자는 toString() 메소드를 사용하는 것을 추천한다. toString() 메소드를 오버라이딩 하여 사용하면 사용자 친화적인 이름을 반환할 수 있기 때문이다. name() 메소드는 릴리스마다 달라지지 않는 정확한 이름을 반환하기 때문에 정확도가 중요한 특수한 상황에서 사용한다.

     

    4.1.2 public final int ordinal()

     열거형 상수의 순서를 반환한다. 선언된 위치를 기준으로 0부터 부여된다. 개발자가 직접 사용하기 보다는, EnumSet과 EnumMap과 같은 정교한 Enum 기반 데이터 구조에서 사용할 수 있도록 설계되었다.

     

    import java.util.EnumSet;
    import java.util.Set;
    
    public class Main {
    
        enum Fruit {
            Apple,
            Banana,
            melon
        }
    
        public static void main(String[] args) {
    
            Set<Fruit> fruits = EnumSet.allOf(Fruit.class);
    
            for (Fruit fruit : fruits) {
                System.out.println(fruit.name() + " -> " + fruit.ordinal());
                if (fruit.ordinal() == 0) {
                    System.out.println("사과");
                }
            }
        }
    }

     

    Apple -> 0
    사과
    Banana -> 1
    melon -> 2

     

     if문의 조건 처럼 ordinal() 메소드를 기반으로 코드를 작성하는 것은 위험하다. 후에 enum에 상수 인스턴스가 추가되면, 해당 index가 유지되는 것을 보장하지 않는다.

     

    import java.util.EnumSet;
    import java.util.Set;
    
    public class Main {
    
        enum Fruit {
            kiwi, // kiwi 추가
            Apple,
            Banana,
            melon
        }
    
        public static void main(String[] args) {
    
            Set<Fruit> fruits = EnumSet.allOf(Fruit.class);
    
            for (Fruit fruit : fruits) {
                System.out.println(fruit.name() + " -> " + fruit.ordinal());
                if (fruit.ordinal() == 0) { // 0번 index가 사과이길 기대한는 기존 코드
                    System.out.println("사과");
                }
            }
        }
    }

     

    kiwi -> 0
    사과
    Apple -> 1
    Banana -> 2
    melon -> 3

     

     kiwi 부터 0, 1, 2, 3 순으로 부여되는데, 기존의 사과를 가리키던 0번 index는 현재 kiwi를 가리키게 된다. 이러한 문제 때문에 메소드의 직접적인 사용은 위험한 것을 확인할 수 있었다.

     

     각각의 인스턴스에 매칭되는 정수값을 추가하고 싶다면 인스턴스 필드에 저장하여 사용하면 된다.

     

    import java.util.EnumSet;
    import java.util.Set;
    
    public class Main {
    
        enum Fruit {
            kiwi(10), 
            Apple(20),
            Banana(30),
            melon(40);
            
            int index;
            
            Fruit(int index) {
                this.index = index;
            }
            
            public int getIndex() {
                return this.index;
            }
        }
    
        public static void main(String[] args) {
    
            Set<Fruit> fruits = EnumSet.allOf(Fruit.class);
    
            for (Fruit fruit : fruits) {
                System.out.println(fruit.name() + " -> " + fruit.getIndex());
            }
        }
    }

     

    4.1.3 public String toString()

     열거형 상수의 이름을 반환한다. 필요에 따라 오버라이딩하여 사용할 수 있다. 개발자에 친화적인 문자열 양식이 있는 경우 toString() 메소드를 오버라이딩 하여 사용하면 된다.

     

    public enum Phone {
        GALAXY_S21(999_900, "SM-G991NZIEKOO"),
        GALAXY_S21_PLUS(1_119_900, "SM-G996NZVEKOO"),
        GALAXY_S21_ULTRA(1_452_000, "SM-G998NZKEKOO"),
        GALAXY_Z_FLIP(1_650_000, "SM-F707NZNAKOO"),
        GALAXY_Z_FOLD2(2_398_000, "SM-F916NZKAKOO");
    
        private final int price;
        private final String modelName;
    
        Phone(int price, String modelName) {
            this.price = price;
            this.modelName = modelName;
        }
    
        public int getPrice() {
            return this.price;
        }
    
        public String getModelName() {
            return this.modelName;
        }
    
        @Override
        public String toString() {
            return "Phone{" +
                    "price=" + price +
                    ", modelName='" + modelName + '\'' +
                    '}';
        }
    }

     

    public class Main {
    
        public static void main(String[] args) {
            Phone[] phones = Phone.values();
    
            for (Phone phone : phones) {
                System.out.println(phone.name());
                System.out.println(phone.ordinal());
                System.out.println(phone.toString());
                System.out.println();
            }
        }
    }

     

    GALAXY_S21
    0
    Phone{price=999900, modelName='SM-G991NZIEKOO'}
    
    GALAXY_S21_PLUS
    1
    Phone{price=1119900, modelName='SM-G996NZVEKOO'}
    
    GALAXY_S21_ULTRA
    2
    Phone{price=1452000, modelName='SM-G998NZKEKOO'}
    
    GALAXY_Z_FLIP
    3
    Phone{price=1650000, modelName='SM-F707NZNAKOO'}
    
    GALAXY_Z_FOLD2
    4
    Phone{price=2398000, modelName='SM-F916NZKAKOO'}

     

     toString() 메소드를 제외하고 모두 final 메소드 이기 때문에 오버라이딩이 불가능 하다. final이 붙지 않은 toString() 메소드는 의도에 따라 오버라이딩하여 사용할 수 있다.


    5. EnumSet

    참고 :

    docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/EnumSet.html

    johngrib.github.io/wiki/java-enum/

    www.notion.so/Enum-6ffa87530c424d8ab7a1b585bfb26fa2

    parkadd.tistory.com/50

    siyoon210.tistory.com/152

    5.1 EnumSet 활용

     EnumSet은 enum 타임에 사용하기 위한 특수한 Set 인터페이스의 구현이다. 또한 내부적으로 bit vector로 표현되기 때문에 매우 효율적이다. 

     

    public class Main {
    
        public static void main(String[] args) {
    
            System.out.println("========== EnumSet.of     ==========");
    
            Set<Phone> phones = EnumSet.of(Phone.GALAXY_S21, Phone.GALAXY_S21_PLUS);
            for (Phone phone : phones) System.out.println(phone.name());
    
            System.out.println("========== EnumSet.allOf  ==========");
    
            Set<Phone> allPhones = EnumSet.allOf(Phone.class);
            for (Phone allPhone : allPhones) System.out.println(allPhone.name());
    
            System.out.println("========== EnumSet.noneOf ==========");
    
            Set<Phone> nonePhone = EnumSet.noneOf(Phone.class);
            for (Phone phone : nonePhone) System.out.println(phone.name());
    
            System.out.println("========== EnumSet.range  ==========");
    
            Set<Phone> fromToPhone = EnumSet.range(Phone.GALAXY_S21, Phone.GALAXY_S21_ULTRA);
            for (Phone phone : fromToPhone) System.out.println(phone.name());
        }
    }

     

    ========== EnumSet.of     ==========
    GALAXY_S21
    GALAXY_S21_PLUS
    ========== EnumSet.allOf  ==========
    GALAXY_S21
    GALAXY_S21_PLUS
    GALAXY_S21_ULTRA
    GALAXY_Z_FLIP
    GALAXY_Z_FOLD2
    ========== EnumSet.noneOf ==========
    ========== EnumSet.range  ==========
    GALAXY_S21
    GALAXY_S21_PLUS
    GALAXY_S21_ULTRA

     

     동기식으로 사용할 필요가 있다면 Collections.synchronizedSet을 사용할 수 있다.

     

    Set<Phone> synchronizedSet = Collections.synchronizedSet(EnumSet.allOf(Phone.class));

    5.2 생성자를 호출할 수 없는 EnumSet

    EnumSet의 noneOf() 메소드

     EnumSet의 경우 abstract class 이기 때문에 생성자를 호출할 수 없다. 그렇기 때문에 이러한 static 메소드를 활용하여 RegularEnumSet이나 JumboEnumSet을 선택하여 생성한다. 이러한 메소드를 정적 팩토리 메소드 (static factory method) 라고 부른다.

     

     - 사용자의 편의성 1

      사용자는 어떤 구현객체가 적합한지 몰라도 상관없다. RegularEnumSet은 원소의 개수가 적을 때 적합하고, JumboEnumSet은 원소의 개수가 많을 때 적합하지만, 이는 EnumSet의 구현체를 모두 알고 있는 사용자가 아니라면 복잡한 선택지가 될 수 있다. 하지만 EnumSet을 가장 잘 알고 있는 EnumSet을 개발한 개발자가 구현 객체를 반환해 주기 때문에 EnumSet을 사용하는 입장에서 어떤 구현체가 적합한지 고려하지 않아도 된다.

     

     - 사용자의 편의성 2

      사용자는 빈번하게 EnumSet의 초기화 과정을 간단하게 진행할 수 있다. 특히 Enum의 모든 원소를 Set에 담는 행위는 빈번하게 수행될 것으로 예상되기 때문에 EnumSet은 AllOf() 와 같은 메소드를 지원한다.

     

     - EnumSet의 확장성과 유지보수의 이점

      EnumSet을 유지보수 하는 과정에서 RegularEnumSet과 JumboEnumSet이외에 다른 경우를 대비하는 구현 클래스가 추가되어도 내부에 감춰져 있기 때문에 EnumSet을 사용하던 기존의 코드에는 전혀 영향이 없다. 최악의 상황으로 RegularEnumSet이 삭제되더라도 사용하고 있는 사용자는 여전히 EnumSet을 사용할 수 있다.


    6. EnumMap

     enum type의 키와 함꼐 사용하기 위한 특수한 Map 구현체이다. EnumMap의 모든 키는 map이 생성될 때 명시적으로 혹은 암시적으로 지정된 단일 열겨형 타입에서 가져와야 한다. EnumMap 내부적으로 배열로 표시된다. 그렇기 때문에 매우 간결하고 효율적이다.

     

     EnumMap은 열겨형 상수가 선언된 순서로 키의 순서가 유지된다. 이는 컬렉션 뷰 (keySet(), entrySet(), values())의 반환값에도 반영된다.

     

     Null 요소는 허용하지 않는다. null 요소를 삽입하려고 시도하면 NullPointException이 발생한다. 

     

    import java.util.EnumMap;
    import java.util.Map;
    
    public class Main {
    
        public static void main(String[] args) {
    
            Map<Phone, String> map = new EnumMap<Phone, String>(Phone.class);
    
            for (Phone phone : map.keySet()) {
                System.out.println(phone.toString());
            }
        }
    }

     

     Map 인터페이스의 구현체이기 때문에 Map을 사용하는 것 처럼 사용할 수 있다.


    References.

    velog.io/@pop8682/Enum-27k067ns4a

    www.geeksforgeeks.org/enum-in-java/

    velog.io/@kyle/%EC%9E%90%EB%B0%94-Enum-%EA%B8%B0%EB%B3%B8-%EB%B0%8F-%ED%99%9C%EC%9A%A9

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

    docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/EnumSet.html

    johngrib.github.io/wiki/java-enum/

    https://blog.naver.com/hsm622/222218251749

    https://wisdom-and-record.tistory.com/52

    www.notion.so/Enum-6ffa87530c424d8ab7a1b585bfb26fa2

    parkadd.tistory.com/50

    siyoon210.tistory.com/152

    'Programming > Java live study' 카테고리의 다른 글

    13주차 과제: I/O  (0) 2021.02.14
    12주차 과제: 애노테이션  (0) 2021.02.03
    10주차 과제: 멀티쓰레드 프로그래밍  (0) 2021.01.21
    9주차 과제: 예외 처리  (0) 2021.01.13
    8주차 과제: 인터페이스  (0) 2021.01.05

    댓글

Designed by Tistory.