Enum

컴퓨터 프로그래밍에서 Enumerated Type(열거형 타입)은 요소, 멤버라 불리는 명명된 값의 집합을 이루는 자료형이다. 열거자 이름들은 일반적으로 해당 언어의 상수 역할을 하는 식별자이다. -wikipedia

  • enum 은 상수 값 멤버들로 집합을 이루는 열거형 타입이다. 상수는 변하지 않는 값으로, 따라서 enum 에는 정수뿐 아니라, 문자열 등 그외 자료형도 정의할 수 있다.
  • JAVA의 enum은 변수, 메소드 및 생성자를 추가할 수도 있다. 주요 목적은 개별적인 데이터 유형 (Enumerate Data Types)을 정의하는 데 있다.
  • Enum 선언은 클래스 외부 또는 클래스 내부에서 선언할 수 있지만 메서드 내부에서는 선언할 수 없다.
  • JDK5 부터 추가되었으며 그 이전에는 상수를 이용하여 사용하였는데,
    • 상수를 이용하는 방법
    public class Constants {
        public static final String MALE = "MALE";
        public static final String FEMALE = "FEMALE";
    }
    public static void main(String[] args) {
        String gender;
        gender = Constants.MALE;
        gender = Constants.FEMALE;
    }

위와 같은 방식으로 사용하는데, 위 방법의 문제점은 gender = "메롱"; 이렇게 수행되어도 코드상 전혀 문제가 되지 않아, 실행할때 원했던 값이 MALE 또는 FEMALE이 아닌 전혀 다른 값이 들어오게 되므로 문제를 발생시킬 수 있다.

그러므로, 특정값만 가져야 한다면 열거형을 사용하여 작성하는 것이 좋다. (컴파일 타임에 가능한 모든 값을 알고 있을 때)

또 상수를 사용하였을때 문제점은 TYPE-SAFETY 하지 않는다는 것이다.

    public static void main(String[] args) {
        System.out.println("hello");  // 오타 발생 여지 있음
    }

위와같이 코딩할때는 hello를 helo 로 쓴다든지 하는 오타가 발생할 여지가 있어 type-safety 하지 않는데, enum 을활용하면 type-safety 한 코드를 작성할 수 있다.

    enum Hello {
        HELLO("HELLO");
    
        String message;
    
        Hello(String message) {
            this.message = message;
        }
    
        public String getMessage() {
            return message;
        }
    }
    
    public static void main(String[] args) {
            System.out.println(Hello.HELLO.getMessage());
    
    }

마지막으로, 상수로 정의하여 사용하였을때의 문제점은 컴파일러가 보기에는 int를 비교하는것이기때문에 전혀 다른 의미의 상수여도 같은 상수로 볼 수 있다는 점이다.

class Test1 {
    public static final int APPLE = 1;
}

class Test2 {
    public static final int MONKEY = 1;
}

class App {
    public static void main(String[] args) {
        if (Test1.APPLE == Test2.MONKEY) {
            // ...
        }
    }
}

사과와 원숭이는 비교조차 안되는 개념인데 컴파일러는 알지 못한다. 컴파일러단에서 저런 코드를 비교 못하게 막게 하기 위해서는 ENUM 을 사용하면 된다.

  • 열거형(enum) 정의 방법

      enum 열거형_이름 {
          상수명(),      // 생성자 호출
          상수명;
          private final [데이터타입] [변수이름];
    
          public get변수이름() {
              return 변수이름;
          }
    
          private 생성자(매개변수 ..) {
              변수이름 = 매개변수값;
          }
      }
    

    열거형 상수에 생성자에 primitive type 데이터, reference type 객체도 들어 올 수 있으며, 열거형 상수에 값을 부여할 경우 이에 해당하는 생성자도 함게 정의해줘야 하며, 그냥 () 사용시에는 디폴트 생성자를 사용하는 것이다.

    또, 생성자를 정의한다면 private 로 정의해야 하는데 (다른 접근제어자 사용시 컴파일 에러 발생) enum 타입은 고정된 상수들의 집합으로 컴파일 타임에 고정되어야 하기 때문에 다른 패키지나 클래스에서 동적으로 값을 지정하는 것을 막기 위해 private로 정의해야 한다.

      public enum Gender {
      	MALE, FEMALE;
      }
    
      Gender gender;
      gender = Gender.MALE;
      gender = Gender.FEMALE;
      gender = "테스트";           // 컴파일 에러 발생
    

    Enum 을 정의햇을 때 내부적으로는 아래와 같이 변화된다.

      class Gender {
          public static final Gender MALE = new Gender();
          public static final Gender FEMALE = new Gender();
      }
    
    • 모든 enum 상수들은 enum 타입의 객체로 표현된다.
    • final 이므로 자식 enum 을 만들 수 없다.

    또, enum 내부에 main() 메서드를 선언할 수 있다. → enum을 cmd prompt 에서 호출 가능

      enum Gender {
          MALE, FEMALE;
            
          // Driver method
          public static void main(String[] args) {
                  Gender gender = Gender.MALE;
                  System.out.println(gender);
          }
      }
    
      MALE
    
    • 다중값을 가진 enum 상수
      enum Fruit {
          여름과일("포도", "수박"),
          겨울과일("딸기", "귤")
            
          private final String f1;
          private final String f2;
    
          public String getF1() {
                  return f1;
          }
    
          public String getF2() {
                  return f2;
          }
    
          Fruit(String f1, String f2) {
                  this.f1 = f1;
                  this.f2 = f2;
          }
      }
    
  • enum 상수와 메서드

enum EnumExam {
    상수 {
        [접근제어자] [리턴타입][메서드이름](매개변수) {
            // ...
        }
    }
}

EnumExam.상수.메서드이름(); 이런 식으로 사용 할 수 있다.

  • switch 문에서의 enum 사용
enum Day {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY, 
    THURSDAY, FRIDAY, SATURDAY;
}
public class EnumTest {

    Day day;

    public EnumTest(Day day) {
        this.day = day;
    }

    public void dayIsLike() {
        switch(day) {
            case MONDAY:
                System.out.println("Mondays are bad.");
                break;
            case FRIDAY:
                System.out.println("FRIDAY are best.");
                break;
            case SUNDAY:
                System.out.println("SUNDAY are depressed");
                break;
            default:
                System.out.println("so-so");
                break;
        }
    }

    public static void main(String[] args) {
        String str = "FRIDAY";
        EnumTest t1 = new EnumTest(Day.valueOf(str));
        t1.dayIsLike();
    }
}

java.lang.Enum 클래스 상속

모든 enum 은 암묵적으로 java.lang.Enum.class 를 상속받기 때문에 다른 클래스를 상속받을 수 없다. 그러나 다른 인터페이스들은 구현할 수 있다.

Enum 클래스는 모든 자바 열거체의 공통된 조상클래스로, 열거체를 조작하기 위한 다양한 메소드가 포함되어 있다.

바이트코드

// class version 58.0 (58)
// access flags 0x4031
// signature Ljava/lang/Enum<LFruit;>;
// declaration: Fruit extends java.lang.Enum<Fruit>
public final enum Fruit extends java/lang/Enum {

  // compiled from: Fruit.java

  // access flags 0x4019
  public final static enum LFruit; GRAPE

  // access flags 0x4019
  public final static enum LFruit; APPLE

  // access flags 0x4019
  public final static enum LFruit; BANANA

  // access flags 0x101A
  private final static synthetic [LFruit; $VALUES

  // access flags 0x9
  public static values()[LFruit;
   L0
    LINENUMBER 1 L0
    GETSTATIC Fruit.$VALUES : [LFruit;
    INVOKEVIRTUAL [LFruit;.clone ()Ljava/lang/Object;
    CHECKCAST [LFruit;
    ARETURN
    MAXSTACK = 1
    MAXLOCALS = 0

  // access flags 0x9
  public static valueOf(Ljava/lang/String;)LFruit;
   L0
    LINENUMBER 1 L0
    LDC LFruit;.class
    ALOAD 0
    INVOKESTATIC java/lang/Enum.valueOf (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
    CHECKCAST Fruit
    ARETURN
   L1
    LOCALVARIABLE name Ljava/lang/String; L0 L1 0
    MAXSTACK = 2
    MAXLOCALS = 1

  // access flags 0x2
  // signature ()V
  // declaration: void <init>()
  private <init>(Ljava/lang/String;I)V
   L0
    LINENUMBER 1 L0
    ALOAD 0
    ALOAD 1
    ILOAD 2
    INVOKESPECIAL java/lang/Enum.<init> (Ljava/lang/String;I)V
    RETURN
   L1
    LOCALVARIABLE this LFruit; L0 L1 0
    MAXSTACK = 3
    MAXLOCALS = 3

  // access flags 0x8
  static <clinit>()V
   L0
    LINENUMBER 2 L0
    NEW Fruit
    DUP
    LDC "GRAPE"
    ICONST_0
    INVOKESPECIAL Fruit.<init> (Ljava/lang/String;I)V
    PUTSTATIC Fruit.GRAPE : LFruit;
    NEW Fruit
    DUP
    LDC "APPLE"
    ICONST_1
    INVOKESPECIAL Fruit.<init> (Ljava/lang/String;I)V
    PUTSTATIC Fruit.APPLE : LFruit;
    NEW Fruit
    DUP
    LDC "BANANA"
    ICONST_2
    INVOKESPECIAL Fruit.<init> (Ljava/lang/String;I)V
    PUTSTATIC Fruit.BANANA : LFruit;
   L1
    LINENUMBER 1 L1
    ICONST_3
    ANEWARRAY Fruit
    DUP
    ICONST_0
    GETSTATIC Fruit.GRAPE : LFruit;
    AASTORE
    DUP
    ICONST_1
    GETSTATIC Fruit.APPLE : LFruit;
    AASTORE
    DUP
    ICONST_2
    GETSTATIC Fruit.BANANA : LFruit;
    AASTORE
    PUTSTATIC Fruit.$VALUES : [LFruit;
    RETURN
    MAXSTACK = 4
    MAXLOCALS = 0
}
  • values() 와 valueOf()는 자바의 모든 열거형에 컴파일러가 추가해주는 것을 알 수 있다.

Enum 클래스 메소드

  • values() : enum에 선언된 모든 값들을 저장한 배열을 생성하여 리턴
  • ordinal() : enum constant 의 배열 인덱스를 리턴 (0부터 시작)

    Most programmers will have no use for this method. It is designed for use by sophisticated enum-based data structures, such as EnumSet and EnumMap

    순서이기때문에 바뀔 수 있는 것을 감안해야 하기 때문에, ordinal 메소드에 의존한 코드를 작성하면 안된다.

  • valueOf() : 찾고자 하는 특정 string value 가 enum constant 로 있으면 해당 값 리턴
    • Throws IllegalArgumentException, NullPointerException
enum Color {
    BLUE, PINK, YELLOW;
}

public class App {
	public static void main(String[] args) {
        Color colors[] = Color.values();
        for (Color col : colors) {
            System.out.println(col + " at index " + col.ordinal());
        }
        System.out.println(Color.valueOf("BLUE"));
	}
}

valueOf() 사용할 때 없는 값을 부르게 되면?

public class App {
	public static void main(String[] args) {
        System.out.println(Color.valueOf("RED"));
	}
}


  • name() : 해당 enum constant 이름을 리턴
  • compareTo() : 전달인자와 enum 객체의 순서를 비교한다.
    • 지정된 객체보다 작은경우 : 음의정수,
    • 동일하면 : 0,
    • 크면 : 양의 정수를 리턴
enum Tutorials {
    TOPIC_1, TOPIC_2, TOPIC_3;
}

public class App {
    public static void main(String[] args) {
        Tutorials t1, t2, t3;

        t1 = Tutorials.TOPIC_1;
        t2 = Tutorials.TOPIC_2;
        t3 = Tutorials.TOPIC_3;

        if (t1.compareTo(t2) > 0) {
            System.out.println(t2 + " completed before " + t1);
        }

        if (t1.compareTo(t2) < 0) {
            System.out.println(t1 + " completed before " + t2);
        }

        if (t1.compareTo(t3) == 0) {
            System.out.println(t1 + " completed with " + t3);
        }
    }
}

  • getDeclaringClass() : enum constants 의 enum type에 상응하는 클래스 객체 리턴
enum Tutorials {
    TOPIC_1, TOPIC_2, TOPIC_3;
}

public class App {
    public static void main(String[] args) {
        /*
            returns the Class object corresponding to
            this enum constant's enum type
        */
        System.out.println(Tutorials.TOPIC_1.getDeclaringClass());
    }
}

  • Object 클래스 메소드 오버라이딩
    • toString() : enum constant name을 리턴한다.
    • protected void finalize() : 해당 enum 클래스가 final 메소드를 가질 수 없게 됨
    • equals() : 전달인자와 enum constant를 비교해서 동일하면 true 아니면 false 리턴
    • hashCode() : enum constant의 hash code를 리턴
    • clone()
      • Object 클래스 clone()

        복제하는 메소드로, 여러 정보를 담고 있는 객체가 있을때 그 객체의 clone 메소드를 사용하면 같은 정보를 담고있는 또 다른 하나의 객체를 만들 수 있는 메소드

          class Student{
              String name;
              Student(String name) {
                  this.name = name;
              }
          }
        

        Student 클래스 인스턴스를 하나 생성 후
        (Student s1 = new Student("yesol");)

        s1.clone() 을 하면 컴파일 에러가 발생한다. clone 메서드를 사용하려면 JVM 에게 복제 가능한 클래스라는것을 알려줘야 하기때문이다. 그럴러면 Cloneable 인터페이스를 구현해주면 된다.

          class Student implements Cloneable{
              String name;
              Student(String name){
                  this.name = name;
              }
              protected Object clone() throws CloneNotSupportedException{
                  return super.clone();
              }
          }
                    
          public class App {
              public static void main(String[] args) {
                  Student s1 = new Student("yesol");
                  try {
                      Student s2 = (Student)s1.clone();
                      System.out.println(s1.name);
                      System.out.println(s2.name);
                  } catch (CloneNotSupportedException e) {
                      e.printStackTrace();
                  }
              }
          }
        
          yesol
          yesol
        
        • Cloneable 인터페이스를 들여다보면 아무것도 없는것을 볼 수 있는데 단지 JVM에게 복제 가능한 클래스라는것을 알려주기 위한 구분자의 역할을 하는 것이다. (일종의 복제 할 수 있냐 없냐의 약속)
        • Object는 java.lang 패키지에 있고, clone 메소드는 protected(같은 패키지내에서만 사용 가능) 이기 때문에 오버라이딩 하여 사용해야 한다.
      • enum은 각 값당 인스턴스는 하나여야 하기 때문에 clone을 제공하지 않는다.

        • 동일한 object를 리턴한다면 clone (복제)가 아니다.
        enum Mobile {
        	Samsung(400), LG(250);
              
        	int price;
        	Mobile(int price) {
        		this.price = price;
        	}
        	int showPrice() {
        		return price;
        	}
        }
              
        public class App {
            public static void main(String[] args) {
                System.out.println("Enums can never be cloned ...");
                App a = new App() {
                    @Override
                    protected final Object clone() throws CloneNotSupportedException {
                        throw new CloneNotSupportedException();
                    }
                };
              
                System.out.println("CellPhone List: ");
                for (Mobile m: Mobile.values()) {
                    System.out.println(m + " costs " + m.showPrice() + " won");
                }
            }
        }
      

EnumSet

  • 자료구조 set
    • 데이터를 비순차적(unordered)으로 저장하는 순열 자료구조(collection)
    • Array, List 와 마찬가지로 Set은 수정이 가능하다.
    • 중복을 허용하지 않아 같은 값을 삽입하면 마지막에 삽입한 값 하나만 저장된다.
    • 빠른 Lookup (데이터 탐색) 에 주로 쓰임

      1. 저장할 데이터의 Hash 값을 구한 후
      2. Hash 값에 해당하는 공간 (Bucket)에 값을 저장한다.
    • 해시 테이블을 사용해서 해시값을 기반으로 데이터를 저장하기 때문에 특정 값을 포함하는지 확인하는 작업이 빠르다.
    • 사용 목적
      • 중복된 값을 골라야 할 때
      • 특정 값이 있는지 빠르게 확인해야 할 때
      • 순서가 상관없을 때

https://www.geeksforgeeks.org/enumset-class-java/

enum 클래스에서 작업하는 특별한 형태의 set collection 이다. 열거형 타입으로 지정해 놓은 요소들을 쉽고 빠르게 배열처럼 요소를 다룰 수 있는 기능을 제공한다.

  • AbstractSet 클래스를 확장하고 Set interface를 구현한다.
  • HashSet 보다 빠르다
    • 원소갯수가 64개를 넘지 않을 경우에 겉은 set 기반이지만 내부적으로 long 데이터형의 비트필드를 사용하게 된다. 비트필드는 2의 제곱수들로 이루어져 메모리 공간을 적게 차지하고 속도도 빠른데, 이 원리를 기반으로 하는 것이 EnumSet이다.
      HashSet의 경우 배열과 해쉬코드를 이용하는데 상황에 따라 다르겠지만 통상 비트연산을 사용하는 EnumSet보다 속도면에서 뒤떨어진다.
  • enum값만 포함할 수 있으며 모든 값은 동일한 enum타입에 속해야 한다.
  • null값을 추가 할 수 없다.
  • thread-safe 하지 않다. → 동기화 하여 사용해야 한다.
  • enum안에 선언된 순서에 따라 요소들이 저장된다.
  • It uses a fail-safe iterator that works on a copy, so it won’t throw a ConcurrentModificationException if the collection is modified when iterating over it

계층구조

E : 저장된 요소의 타입

예제

1.

enum Food {
    SALAD, KIMCHI, RICE, BREAD;
}

public class App {
    public static void main(String[] args) {
        EnumSet<Food> set1, set2, set3, set4;

        set1 = EnumSet.of(Food.RICE, Food.BREAD, Food.SALAD);
        set2 = EnumSet.complementOf(set1);
        set3 = EnumSet.allOf(Food.class);
        set4 = EnumSet.range(Food.SALAD, Food.RICE);

        System.out.println("set1 = " + set1);
        System.out.println("set2 = " + set2);
        System.out.println("set3 = " + set3);
        System.out.println("set4 = " + set4);
    }
}

  • of(E e1, E e2 ...) : Creates an enum set initially containing the specified element.
  • complementOf(EnumSet<E> s) : Creates an enum set with the same element type as the specified enum set, initially containing all the elements of this type that are not contained in the specified set.
  • allOf(Class<E> elementType) : Creates an enum set containing all of the elements in the specified element type.
  • range(E from , E to) : Creates an enum set initially containing all of the elements in the range defined by the two specified endpoints.

2.

enum Food {
	SALAD, KIMCHI, RICE, BREAD;
}

public class App {
    public static void main(String[] args) {
        EnumSet<Food> allOfset, noneOfSet;

        allOfset = EnumSet.allOf(Food.class);
        noneOfSet = EnumSet.noneOf(Food.class);

        noneOfSet.add(Food.KIMCHI);
        System.out.println("EnumSet Using add() : " + noneOfSet);

        noneOfSet.addAll(allOfset);
        System.out.println("EnumSet Using addAll() : " + noneOfSet);
    }
}

  • noneOf(Class<E> elementType) : Creates an empty enum set with the specified element type
enum Food {
	SALAD, KIMCHI, RICE, BREAD;
}

public class App {
	public static void main(String[] args) {
		EnumSet<Food> foods = EnumSet.allOf(Food.class);
		Iterator<Food> iterate = foods.iterator();
		System.out.print("EnumSet: ");
		while (iterate.hasNext()) {
			System.out.print(iterate.next());
			System.out.print(", ");
		}
	}
}

EnumSet 객체 생성

EnumSet은 추상클래스이기 때문에 직접 인스턴스를 만들 수 없다.

  • 인스턴스를 만드는데 필요한 많은 static factory 메소드를 갖고 있다.

JDK 가 제공하는 EnumSet을 구현한 클래스가 두개 있는데,

  • RegularEnumSet
  • JumboEnumSet

RegularEnumSet은 EnumSet의 요소를 single long object로 저장한다. long 타입의 element 의 각 bit값은 Enum 값을 나타낸다. long 의 크기는 64bit 이므로 최대 64개의 다른 요소를 저장할 수 있다.

JumboEnumSet은 긴 요소의 배열을 사용하여 EnumSet의 요소를 저장한다. RegularEnumSet과의 차이점은 64개 이상의 값을 허용한다는 것이다. (그 외는 다른 것 없음)

if (universe.length <= 64) 
		return new RegularEnumSet<>(elementType, universe);
else 
		return new JumboEnumSet<>(elementType, universe);

EnumMap

  • JAVA MAP
    • Map
      • 탐색 가능한 key, value로 이루어지는 쌍의 묶음(Collection)
      • 똑같은 key 를 가진 entry 가 존재하지 않는다.
        • key는 반드시 unique한 값이어야 한다.
      • key가 entry를 저장하게 될 공간을 결정하여 associative store 라고 부르기도 함
    • List-based map
      • unsorted, node list로 구현
      • 데이터 탐색에 선형탐색알고리즘(linear search algorithm)이 사용된다.
        • 원하는 데이터를 찾기 위해 순차적으로 탐색하는 방법
      • get, remove, put
      • Big-O : O(n)
      • 구현하기 쉽다는 장점이 있으나 데이터 개수가 적을때에만 효율적이다.
    • Hash table

      • entry 저장하기 위한 주소로 key를 사용한다.
      • Big-O : 대부분 O(1)

Map 인터페이스에서 키를 특정 enum 타입만을 사용하도록 하는 구현체