자바 스터디 11주차 : Enum

hwanse·2021년 2월 22일
1

Java

목록 보기
12/14

이 글은 백기선님의 라이브 스터디 참여 및 학습 내용에 관한 정리한 글입니다.





학습 목표

  1. Enum 정의하는 방법
  2. Enum이 제공하는 메소드(values() 와 valueOf())
  3. java.lang.Enum
  4. EnumSet




Enum을 정의하는 방법

Enum 클래스

열거체(enumeration type) 라고도 부른다. JDK 1.5 이전 자바 버전들에서는 Enum을 사용할 수 없었고, JDK 1.5부터 열거체를 정의한 Enum을 사용할 수 있게 되었다. Enum은 다음과 같은 특징을 가지고 있다.


정리

  • 클래스처럼 보이게 하는 상수
  • 서로 관련 있는 상수들을 모아 심볼릭한 명칭의 집합으로 정의
  • Enum 클래스형을 기반으로 한 클래스형 선언
  • 새로운 열거형을 선언하면, 내부적으로 Enum 클래스형 기반의 새로운 클래스형이 만들어짐
  • 열거체를 비교할 때 실제 값뿐만 아니라 타입 비교 체크가 가능하다 (Type-Safety)
  • 열거체의 상수값이 재정의되더라도 다시 컴파일할 필요가 없다.

선업방법

enum [열거체명]{
  상수1, 상수2, 상수3...
}
// ---- 예시 ----
enum Color {
  RED, BLUE, GREEN, YELLOW, WHITE
}

Enum의 상수값 정의 및 추가

열거체에 정의된 상수값들은 내부적으로 선언된 순서대로 첫 상수값 0부터 1씩 증가하는 순서로 기본인 값을 가지고 있다. 여기에 추가적인 상수값을 정의하는 것이 가능하며, 개발자가 원하는 커스텀한 상수값을 명시할 수 있으며 이를 정의하기 위해서는 인스턴스 변수 및 생성자를 반드시 추가해주어야한다.

상수 정의 추가

enum Color {
  RED(55), GREEN(21), BLUE(10), YELLOW(65);
  
  private final int value;
  
  Color(int value) {
    this.value = value;
  }
  
  public int getValue() {
    return value;
  }
}

단, Enum 의 생성자는 접근지시자를 public, protected는 사용이 불가하며 default로 private생성자이다. 이유는 Enum 타입은 고정된 상수 집합이기도 하며 런타임이 아닌 컴파일 타임에 모든 값을 알고 있어야한다. 따라서 다른 곳에서 enum 타입에 접근하여 동적으로 어떤 값으로 변경할 수 없도록 막는 것이고 컴파일 시점부터 타입 안정성이 보장되는 것이다.


Enum 특성

Type-Safty
Enum을 사용하면 상수들을 좀 더 효율적으로 활용할 수 있고, Type-Safety하게 컴파일 시점에서 비교 체크가 가능해지는 장점이 있다. Enum을 사용했을 때와 사용하지 않았을 때를 비교해보자.

public class App {

  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    String input = sc.nextLine();
  
  	if ("INSERT".equals(input)) {
      // insert 로직 실행
    }
    
    if ("UPDATE".equals(input)) {
      // update 로직 실행
    }
  
  }
}

이와 같이 class에 필드 변수로 static final로 선언된 RED,BLUE,GREEN을 애플리케이션에서 사용한다고 가정하자, 그러면 사용자에서 넘어온 데이터 값이 String타입이라고 가정하고 INSERT인지 UPDATE인지 등의 비교하여 각 상황에 맞는 로직을 처리하도록 분기 처리를 했다고 생각해보자. 일단 위 코드의 문제점은 개발자가 if문 마다 타이핑으로 비교할 문자열을 정확하게 입력해주어야 하는데 만약에 오타를 냈다면 저 코드는 버그이며 컴파일 시점에서 확인이 불가능하다.

public enum Type {
  INSERT, UPDATE
}

public class App {
  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    String input = sc.nextLine();
    
    Type enumType = Type.valueOf(input); // 문자열과 일치한 타입명을 가진 Enum객체
    
  	if (Type.INSERT == enumType) {
      // insert 로직 실행
    }
    
    if (Type.UPDATE == enumType) {
      // update 로직 실행
    }
  
  	// 아래와 같이 switch 구문 사용도 편리해졌다.
    swith (enumType) {
      case INSERT:
        break;
      case UPDATE:
        break;
      default:
        break;
    }
  
  }
}

Enum 타입을 사용하게 되면 위와 같이 == 비교 연산이 가능해지고 switch 구문까지 사용이 가능하다. 이렇게 비교하게 된다면 개발자가 컴파일 시점전에 타입 체크를 강제하는 것이 가능해져 문자열 비교시 발생할 수 있는 실수를 줄일 수 있다.


싱글톤
Enum 클래스는 유일하게 하나의 인스턴스를 생성하고 있어 애플리케이션 전역에서 하나의 인스턴스를 바라보게끔 되어 있다. 이러한 점으로 유일한 상수 값들을 애플리케이션으로 효율적으로 관리할 수 있는 장점이 있으나 멀티 쓰레드 환경에서는 유의해야한다. 지난번 챕터에서 멀티 쓰레드 환경과 같은 예시를 들어보자

public class App {

  enum Pay {
    CARD, COUPON;

    private int amount;

    private Pay() {
      amount = 1000;
    }

    public void subtractAmount(int amount) {
      if (this.amount >= amount) {
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        this.amount -= amount;
      }
    }

    public int getAmount() {
      return amount;
    }

  }

  public static void main(String[] args) {
    PayClient pc = new PayClient(Pay.CARD);

    new Thread(pc).start();
    new Thread(pc).start();

  }

}

class PayClient implements Runnable {
  private Pay payType;

  public PayClient(Pay type) {
    this.payType = type;
  }

  @Override
  public void run() {
    while (payType.getAmount() > 0) {
      int money = (int) (Math.random() * 3 + 1) * 100;
      payType.subtractAmount(money);
      System.out.println(Thread.currentThread().getName() + " amount : " + payType.getAmount());
    }
  }
}

console 출력 :
Thread-1 amount : 600
Thread-0 amount : 600
Thread-0 amount : 400
Thread-1 amount : 400
Thread-1 amount : 100
Thread-0 amount : 100
Thread-0 amount : 100
Thread-0 amount : 100
Thread-1 amount : 100
Thread-0 amount : 100
Thread-0 amount : 100
Thread-1 amount : -100
Thread-0 amount : -100

출력 결과를 보면 알다시피 Enum은 싱글톤이기 때문에 Pay라는 Enum 클래스에 정의한 amount를 멀티 쓰레드 환경에서 참조하고 있고, run메소드에 amount > 0 조건이 있음에도 불구하고 다음과 같은 의도치 못한 결과값이 나오게 되었다. 따라서 멀티 쓰레드 환경에서 위와 같은 방식으로 이용되고 있다면 주의해서 사용해야 한다.


이외에도 Enum 사용 시 장점들이 여러가지 있다
Enum을 통해서 취할 수 있는 장점들이 몇가지 여러 방식들이 있는데, 해당 방식들에 대한 내용은 이해가 되었으나 다음 글에서 좀 더 구체적인 상황과 실무에서 활용한 사례 등 Enum을 통한 여러 장점들에 대해서 구체적인 예시와 장점에 대해서 더욱 명확하게 확인할 수 있다.

https://woowabros.github.io/tools/2017/07/10/java-enum-uses.html




Enum이 제공하는 메소드

values()

해당 Enum 타입에 정의된 상수들에 대한 배열을 반환

enum Color {
  RED, GREEN, WHITE, YELLOW, BLUE, BLACK

}
public class App {

  public static void main(String[] args) {
    System.out.println(Arrays.toString(Color.values()));
  }

}

console 출력 :
[RED, GREEN, WHITE, YELLOW, BLUE, BLACK]

valueOf()

해당 Enum 타입에 상수들 중에서 name과 일치하는 상수를 반환

public class App {

  public static void main(String[] args) {
    System.out.println(Arrays.toString(Color.values()));
  }

}

console 출력 :
GREEN

name()

Enum 상수의 이름을 문자열로 반환

public class App {

  public static void main(String[] args) {
    System.out.println(Color.BLACK.name());
  }

}

console 출력 :
BLACK

ordinal()

상수가 정의된 순서를 반환

public class App {

  public static void main(String[] args) {
    System.out.println(Color.YELLOW.ordinal());
  }

}

console 출력 :
3

이 ordinal 메소드는 사용하지 않는 것을 권장한다. 이 Enum의 순서를 이용해 개발 로직을 짜는 것을 위험요소가 있으며 이 메소드는 Enum 내부에서 사용하기 위해서 만든 것이라 개발자가 이 메소드에 의존된 코드를 작성하는 것은 피해야한다. 이 내용은 JAVA API 문서에서도 말하고 있다

getDeclaringClass()

해당 Enum의 객체 정보를 반환

public class App {

  public static void main(String[] args) {
    System.out.println(Color.RED.getDeclaringClass());
  }

}

console 출력 :
class com.practice.practicejava.Color




java.lang.Enum

Enum 클래스는 모든 자바 열거형들의 조상이다. 모든 열거형은 Enum 클래스를 상속받고 있고 Enum Type은 별도로 상속을 받을 수 없다. 또한 대부분의 메소드가 final 이라서 오버라이딩 또한 안되는 메소드들이 많다.




EnumSet

EnumSet은 열거형과 함께 사용하기 위한 Set이라고 볼 수 있다. 열거형 집합은 내부적으로 비트 벡터로 표시 된다고 한다.

특징

  • 열거형 상수만을 가지고 있으며 같은 Enum Type에 대한 집합으로만 구성된다.
  • Java Collections Framework의 멤버다.
  • AbstractSet 클래스를 상속하고 Set 인터페이스를 구현화했다.
  • HashSet보다 빠른 성능을 가지고 있다.
  • 대부분의 Collection 구현체와 마찬가지로 EnumSet은 동기화가 되지 않으며, Collections.synchronizedSet() 를 활용하여 래핑하거나, 외부에서 동기화를 구현해야한다.
  • Null 요소를 허용하지 않는다. Null을 삽입하려 하면 NPE를 발생시킨다. 그러나 Null 요소를 포함여부를 테스트하거나 제거하는 기능은 제대로 작동한다.
  • EnumSet을 순회할 때에는 odinal 순서대로 순회한다.

관계 구성도

- 이미지 출처 : https://www.geeksforgeeks.org/enumset-class-java

EnumSet의 메소드와 활용 예제

enum Color {
  RED, GREEN, WHITE, BLACK, YELLOW, BLUE;
}

public class App {

  public static void main(String[] args) {

    EnumSet<Color> set1, set2, set3, set4, set5;

    set1 = EnumSet.allOf(Color.class);
    set2 = EnumSet.of(Color.BLUE, Color.RED, Color.GREEN);
    set3 = EnumSet.complementOf(set2);
    set4 = EnumSet.range(Color.RED, Color.YELLOW);
    set5 = EnumSet.noneOf(Color.class);


    // 해당 Enum의 모든 상수를 Set에 등록
    System.out.println("set1 : " + set1);
    // 같은 Enum 타입의 상수들에서 인자로 주어진 상수들로만 EnumSet 구성
    System.out.println("set2 : " + set2);
    // 인자로 지정된 Set 집합을 본래 Enum타입 집합에서 반대 요소를 가진 Set 반환
    System.out.println("set3 : " + set3);
    // 범위로 지정한 요소들의 Set을 반환 (ordinal 순서 기준)
    System.out.println("set4 : " + set4);
    // noneOf는 해당 Enum 타입에 집합군에서 비어있는 상태의 Set 반환
    System.out.println(set5.isEmpty());

    set5.add(Color.WHITE);
    set5.add(Color.BLACK);
    System.out.println("set5 : " + set5);
    // Set에 해당 요소를 포함여부를 반환
    System.out.println(set5.contains(Color.RED));
  }

}

console 출력 :
set1 : [RED, GREEN, WHITE, BLACK, YELLOW, BLUE]
set2 : [RED, GREEN, BLUE]
set3 : [WHITE, BLACK, YELLOW]
set4 : [RED, GREEN, WHITE, BLACK, YELLOW]
true
set5 : [WHITE, BLACK]
false





참고

profile
만사가 귀찮은 ISFP가 쓰는 학습 블로그

0개의 댓글