2021. 1. 29.

11주차 과제: Enum

https://github.com/whiteship/live-study/issues/11

목표

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

학습할 것 (필수)

  • enum 정의하는 방법
  • enum이 제공하는 메소드 ( values()와 valueOf() )
  • java.lang.Enum
  • EnumSet


enum 정의하는 방법

public enum Season {
    SPRING(3, 4, 5),
    SUMMER(6, 7, 8),
    AUTUMN(9, 10, 11),
    WINTER(12, 1, 2);

    Season(int i, int i1, int i2) {
        this.start = i;
        this.middle = i1;
        this.end = i2;
    }
    private final int start;
    private final int middle;
    private final int end;

    public int getStart() {
        return start;
    }

    public int getMiddle() {
        return middle;
    }

    public int getEnd() {
        return end;
    }
}
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class MySeasonTest {
    Season season;

    public MySeasonTest(Season season) {
        this.season = season;
    }

    public void showMeTheSeason() {
        System.out.println(season.getStart());
        System.out.println(season.getMiddle());
        System.out.println(season.getEnd());
    }

    @Test
    @DisplayName("main")
    public static void main(String[] args) {
        MySeasonTest mySeasonTest
                = new MySeasonTest(Season.WINTER);
        mySeasonTest.showMeTheSeason();
    }
}
> Task :MySeasonTest.main()
12
1
2

BUILD SUCCESSFUL in 415ms
3 actionable tasks: 2 executed, 1 up-to-date
  • Java의 enum 타입은 클래스
  • 상수이기에 변수명은 대문자
  • 암시적으로 java.lang.Enum을 상속(extends)
  • enum 생성자는 반드시 package-private, private 접근
  • enum 시작 부분에 정의된 상수를 자동으로 생성
  • enum 생성자를 직접 호출 불가능
  • 암시적으로 public static final

출처, 참고:
https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html

https://docs.oracle.com/javase/specs/jls/se13/html/jls-8.html#jls-8.9.2

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


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

    @Test
    @DisplayName("main")
    public static void main(String[] args) {
        MySeasonTest mySeasonTest
                = new MySeasonTest(Season.WINTER);

        // valueOf()
        Season tempSeason 
                = mySeasonTest.season.valueOf("SUMMER");
        System.out.println("tempSeason.getStart(): " 
                + tempSeason.getStart());

        // values()
        for (Season s :
                Season.values()) {
            System.out.println(s + ", " 
                    + s.getStart() + ", " 
                    + s.getMiddle() + ", " 
                    + s.getEnd());
        }
        
        // ordinal()
        for (Season s :
                Season.values()) {
            System.out.println(
                    s + " at index " + s.ordinal());
        }
    }
> Task :MySeasonTest.main()

// valueOf()
tempSeason.getStart(): 6

// values()
SPRING, 3, 4, 5
SUMMER, 6, 7, 8
AUTUMN, 9, 10, 11
WINTER, 12, 1, 2

// ordinal()
SPRING at index 0
SUMMER at index 1
AUTUMN at index 2
WINTER at index 3

BUILD SUCCESSFUL in 411ms
3 actionable tasks: 2 executed, 1 up-to-date
  • valueOf(): 지정한 이름의 enum 상수를 반환
  • values(): 해당 enum의 모든 상수를 저장한 배열을 생성하여 반환
  • ordinal(): enum 상수의 순서값 반환
  • toString(): enum 상수의 이름을 반환
  • name(): enum 상수의 이름을 반환

toString() 메소드가 사용자에게 더 친숙한 이름을 반환하므로 name() 메소드보다 우선적으로 사용해야 합니다. (Most programmers should use the toString() method in preference to this one, as the toString method may return a more user-friendly name.)

출처: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Enum.html#name()


Enum 바이트코드(ByteCode)

public enum Day {
    SUNDAY, MONDAY, TUESDAY
}
javap -c Day.class

Compiled from "Day.java"
public final class com.testcompany.Day extends java.lang.Enum<com.testcompany.Day> {
  public static final com.testcompany.Day SUNDAY;

  public static final com.testcompany.Day MONDAY;

  public static final com.testcompany.Day TUESDAY;

  public static com.testcompany.Day[] values();
    Code:
       0: getstatic       #13  // Field $VALUES:[Lcom/testcompany/Day;
       3: invokevirtual   #17  // Method "[Lcom/testcompany/Day;".clone:()Ljava/lang/Object;
       6: checkcast       #18  // class "[Lcom/testcompany/Day;"
       9: areturn

  public static com.testcompany.Day valueOf(java.lang.String);
    Code:
       0: ldc             #1  // class com/testcompany/Day
       2: aload_0
       3: invokestatic    #22 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
       6: checkcast       #1  // class com/testcompany/Day
       9: areturn

  static {};
    Code:
       0: new             #1  // class com/testcompany/Day
       3: dup
       4: ldc             #32 // String SUNDAY
       6: iconst_0
       7: invokespecial   #33 // Method "<init>":(Ljava/lang/String;I)V
      10: putstatic       #3  // Field SUNDAY:Lcom/testcompany/Day;
      13: new             #1  // class com/testcompany/Day
      16: dup
      17: ldc             #34 // String MONDAY
      19: iconst_1
      20: invokespecial   #33 // Method "<init>":(Ljava/lang/String;I)V
      23: putstatic       #7  // Field MONDAY:Lcom/testcompany/Day;
      26: new             #1  // class com/testcompany/Day
      29: dup
      30: ldc             #35 // String TUESDAY
      32: iconst_2
      33: invokespecial   #33 // Method "<init>":(Ljava/lang/String;I)V
      36: putstatic       #10 // Field TUESDAY:Lcom/testcompany/Day;
      39: invokestatic    #36 // Method $values:()[Lcom/testcompany/Day;
      42: putstatic       #13 // Field $VALUES:[Lcom/testcompany/Day;
      45: return
}
  • 놀랍게도 바이트코드 내부에 'public static final' 선언
  • values(), valueOf()도 미리 준비
  • 메소드의 형태로 static 필드에 선언

바이트 코드 설명

  • getstatic: 클래스의 static 필드값 가져오기
  • invokevirtual: 인스턴스 메서드 호출
  • checkcast: 객체의 타입 확인
  • areturn: 메소드 참조 반환 (Return reference from method)
  • ldc: 런타임 상수 풀에 아이템 푸시
  • aload_<n>: 지역변수 레퍼런스 로드
  • invokestatic: static 메서드 호출
  • new: 새로운 객체 생성
  • dup: 연산자 스택의 맨 위의 값 복사
  • iconst_<i>: int 상수 푸시
  • invokespecial: 생성자, private 메소드, 슈퍼 클래스의 메소드 호출
  • putstatic: static 필드 선언
  • return: Return void from method
여기서 관심을 가질 부분은 "Ljava/lang/String;"과 마지막의 "V"이다. 자바 바이트코드의 표현에서 "L;"은 클래스 인스턴스이다. 즉, addUser() 메서드는 java/lang/String 객체 하나를 파라미터로 받는 메서드이다. 이 사례의 라이브러리에서는 파라미터가 변경되지 않았으므로 이 파라미터는 정상이다. 위 메시지 마지막의 "V"는 메서드의 반환값을 나타낸다. 자바 바이트코드 표현에서 "V"는 반환값이 없음을 의미한다.

참고, 출처:
https://d2.naver.com/helloworld/1230

https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings

https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-6.html


java.lang.Enum

public abstract class Enum<E extends Enum<E>>
        implements Constable, Comparable<E>, Serializable {
    private final String name;
    
    @NotNull
    public final String name() {
        return name;
    }
    
    private final int ordinal;
    
    @Range(from = 0, to = java.lang.Integer.MAX_VALUE)
    public final int ordinal() {
        return ordinal;
    }
    
    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }
    
    public String toString() {
        return name;
    }
    
    ...
}
  • 모든 자바 열거형의 기본 클래스
  • Enum은 추상 클래스. Enum 클래스를 생성할 수 없다.
  • final 메소드로 선언되므로 수정이 불가능


EnumSet

// Creates an enum set containing all of the elements 
// in the specified element type.
// 명시한 요소 타입이 모두 들어간 enum 세트 생성
EnumSet<Season> myEnumSet = EnumSet.allOf(Season.class);
System.out.println("allOf(Season.class): " + myEnumSet);

// Creates an empty enum set with the specified element type.
// 명시한 요소 타입을 사용해 빈 enum 세트 생성
myEnumSet = EnumSet.noneOf(Season.class);
System.out.println("noneOf(Season.class): " + myEnumSet);

// Creates an enum set initially containing the specified element.
// 생성시 명시한 요소가 들어간 enum 세트 생성
myEnumSet = EnumSet.of(Season.SPRING, Season.AUTUMN);
System.out.println("of(Season.SPRING, Season.AUTUMN): " + myEnumSet);

// 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.
// 명시한 세트가 포함되지 않은 enum 세트 생성
myEnumSet = EnumSet.complementOf(myEnumSet);
System.out.println("complementOf(myEnumSet): " + myEnumSet);

// Creates an enum set initially containing all of the elements 
// in the range defined by the two specified endpoints.
// 지정된 두 요소 범위안의 enum 세트를 생성
myEnumSet = EnumSet.range(Season.SUMMER, Season.AUTUMN);
System.out.println("range(Season.SPRING, Season.AUTUMN): " + myEnumSet);
allOf(Season.class): [SPRING, SUMMER, AUTUMN, WINTER]
noneOf(Season.class): []
of(Season.SPRING, Season.AUTUMN): [SPRING, AUTUMN]
complementOf(myEnumSet): [SUMMER, WINTER]
range(Season.SPRING, Season.AUTUMN): [SUMMER, AUTUMN]

Process finished with exit code 0
  • enum 타입과 함께 사용하기 위한 특수 Set의 구현
  • 모든 요소는 enum 셋을 만들 때 명시적으로 또는 암시적으로 지정된 단일 enum 타입을 가져와야 함
  • 내부적으로 bit vector로 표시됨 (매우 간결하고 효율적)
  • 이 클래스의 공간 및 시간 성능은 기존의 int 기반 "비트 플래그"의 대안으로 고품질에 타입 안전으로 사용할 수 있을 만큼 충분히 우수해야 함

출처: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/EnumSet.html


댓글 없음:

댓글 쓰기