Study/java

[백기선님의 자바 라이브 스터디] 11주차 - Enum

에디개발자 2021. 3. 5. 10:29
반응형

www.youtube.com/watch?v=HLnMuEZpDwU 

나를 닮았다고 한다...

목표

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

학습할 것 (필수)

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

추천 도서

www.yes24.com/Product/Goods/7516911

 

토비의 스프링 3.1 세트

『토비의 스프링 3.1』은 스프링을 처음 접하거나 스프링을 경험했지만 스프링이 어렵게 느껴지는 개발자부터 스프링을 활용한 아키텍처를 설계하고 프레임워크를 개발하려고 하는 아키텍트에

www.yes24.com

www.acornpub.co.kr/book/jpa-programmig

 

자바 ORM 표준 JPA 프로그래밍

JPA 기초 이론과 핵심 원리, 그리고 실무에 필요한 성능 최적화 방법까지 JPA에 대한 모든 것

www.acornpub.co.kr

Enum

enum은 열거형입니다. jdk-1.5부터 도입된 개념입니다.

먼저 Enum이 없었다면 어떻게 상수를 선언하는지 살펴보겠습니다.

public static void main(String[] args) {
        switch_test("1");
    }
    
    private static void demoTest(String param) {
        if (param.equals("1")) {
            System.out.println("print 1");
        } else if (param.equals("2")) {
            System.out.println("print 2");
        } else {
            System.out.println("print else");
        }
    }

위 코드는 메서드에서 넘어오는 파라미터의 값으로 분기 처리하는 코드입니다.

이 코드는 type-safety하지 않습니다. 이렇게 하드코딩하여 작성하게되면 개발자의 실수로 오타가 발생하면 예상하는 다른 결과를 발생시킵니다. 그렇다면 상수 처리를 해보겠습니다.

 

public class Store {

    public static final String ONE = "1";
    public static final String TWO = "2";

    public static void main(String[] args) {
        switch_test("1");
    }

    private static void demoTest(String param) {
        if (param.equals(ONE)) {
            System.out.println("print 1");
        } else if (param.equals(TWO)) {
            System.out.println("print 2");
        } else {
            System.out.println("print else");
        }
    }

}

상수를 사용하여 type-safety한 코드를 작성하였습니다. 하지만 프로젝트가 커지면서 상수값도 많아질 것이고 동일한 값을 가지는 상수들도 생기게 될 것입니다. 다시 코드를 살펴보겠습니다.

 

public class Store {

    public static final String ONE = "1";
    public static final String TWO = "2";
    
    public static final String SEQUENCE_FIRST = "1";
    public static final String SEQUENCE_SECOND = "2";

    public static void main(String[] args) {
        switch_test("1");
    }

    private static void demoTest(String param) {
        if (param.equals(ONE)) {
            System.out.println("print 1");
        } else if (param.equals(SEQUENCE_SECOND)) {
            System.out.println("print 2");
        } else {
            System.out.println("print else");
        }
    }

}

내가 의도한 상태는 ONE 상수와 TWO 상수로 조건 분기를 태우고 싶었습니다. 하지만 SECOND를 실수로 넣는 코드를 작성하였습니다. 하지만 이 코드는 정상적으로 작동할 것입니다. 그리고 다음 개발자가 이 소스를 본다면 혼동이 올 것입니다. 상수를 그룹화시켜보겠습니다.

 

public class StoreNumberConstant {
    public static final String ONE = "1";
    public static final String TWO = "2";
}
public class StoreSequenceConstant {
    public static final String FIRST = "1";
    public static final String SECOND = "2";
}
public class Store {
    public static void main(String[] args) {
        switch_test("1");
    }

    private static void demoTest(String param) {
        if (param.equals(StoreNumberConstant.ONE)) {
            System.out.println("print 1");
        } else if (param.equals(StoreNumberConstant.TWO)) {
            System.out.println("print 2");
        } else {
            System.out.println("print else");
        }
    }
}

여기까지 왔으면 뭔가 그럴싸하게 완성된 것 같습니다. 이정도만 되도 될 것 같은데? 라고 생각할 수도 있지만 다음 코드를 살펴보겠습니다.

 

public class StaffAgeConstant {
    public static int ONE = 1;
    public static int TWO = 2;
}
public class StaffSequenceConstant {
    public static int ONE = 1;
    public static int TWO = 2;
}
public class Staff {
    public static void main(String[] args) {
        if (StaffAgeConstant.ONE == StaffSequenceConstant.ONE) {
            System.out.println("실행되면 안된다.");
        }
    }
}

전혀 상관없는 상수끼리 비교했을 때에도 true처리되어 print를 실행하는 코드입니다. 이러한 다양한 문제점들을 보완하기 위해 Enum을 사용합니다.

 

public enum Age {
    ONE, TWO
}
public enum Number {
    ONE, TWO
}
public class Animals {
    public static void main(String[] args) {
        if (Age.ONE == Number.ONE) {  // 컴파일 에러 발생
            System.out.println("컴파일 에러난다~");
        }
    }
}

위처럼 Enum은 Class에서 상수처리한 것과 다르게 사용할 수 있습니다.

 

enum 정의하는 방법

public enum Age {
    ONE, TWO
}

class대신 enum을 선언하여 사용하고 상수를 선언하여 사용할 수 있습니다.

 

public enum Age {
    ONE(1), TWO(2);

    private int age;

    Age(int age) {
        this.age = age;
    }

    public int getAge() {
        return this.age;
    }
}

이런식으로도 상수에 괄호를 사용하면 생성자를 생성하여 변수를 추가할 수 있습니다. 그리고 이번에 학습하면서 Enum도 추상화 메서드를 선언하여 사용할 수 있다는 것을 알았습니다.

 

public enum Age {
    ONE(1, 2) {
        @Override
        int calculateAge(int index) {
            return age * index;
        }
    }

    , TWO(2, 3) {
        @Override
        int calculateAge(int index) {
            return age * index;
        }
    };

    protected final int age;
    protected final int CALCULATE_AGE;

    Age(int age, int calculateAge) {
        this.age = age;
        this.CALCULATE_AGE = calculateAge;
    }

    public int getAge() {
        return this.age;
    }

    abstract int calculateAge(int index);    // age에 곱하기 해보자
}
public static void main(String[] args) {
    System.out.println(Age.ONE.calculateAge(2));

    System.out.println(Age.ONE.calculateAge(3));

    System.out.println(Age.TWO.calculateAge(2));

    System.out.println(Age.TWO.calculateAge(3));
}

// 결과값은 2, 3, 4, 6

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

먼저 values()와 valueOf()를 비교하기 전에 Enum 을 생성하면 Enum Class를 상속받는 것을 알아야합니다. enum을 생성하고 바이트코드로 알아보겠습니다.

public enum MoveEnum {
    TEST1, TEST2
}
// class version 52.0 (52)
// access flags 0x4031
// signature Ljava/lang/Enum<Lcom/example/practice/moves/MoveEnum;>;
// declaration: com/example/practice/moves/MoveEnum extends java.lang.Enum<com.example.practice.moves.MoveEnum>
public final enum com/example/practice/moves/MoveEnum extends java/lang/Enum {

  // compiled from: MoveEnum.java

  // access flags 0x4019
  public final static enum Lcom/example/practice/moves/MoveEnum; TEST1

  // access flags 0x4019
  public final static enum Lcom/example/practice/moves/MoveEnum; TEST2

  // access flags 0x101A
  private final static synthetic [Lcom/example/practice/moves/MoveEnum; $VALUES

  // access flags 0x9
  public static values()[Lcom/example/practice/moves/MoveEnum;
   L0
    LINENUMBER 3 L0
    GETSTATIC com/example/practice/moves/MoveEnum.$VALUES : [Lcom/example/practice/moves/MoveEnum;
    INVOKEVIRTUAL [Lcom/example/practice/moves/MoveEnum;.clone ()Ljava/lang/Object;
    CHECKCAST [Lcom/example/practice/moves/MoveEnum;
    ARETURN
    MAXSTACK = 1
    MAXLOCALS = 0

  // access flags 0x9
  public static valueOf(Ljava/lang/String;)Lcom/example/practice/moves/MoveEnum;
    // parameter mandated  name
   L0
    LINENUMBER 3 L0
    LDC Lcom/example/practice/moves/MoveEnum;.class
    ALOAD 0
    INVOKESTATIC java/lang/Enum.valueOf (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
    CHECKCAST com/example/practice/moves/MoveEnum
    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
    // parameter synthetic  $enum$name
    // parameter synthetic  $enum$ordinal
   L0
    LINENUMBER 3 L0
    ALOAD 0
    ALOAD 1
    ILOAD 2
    INVOKESPECIAL java/lang/Enum.<init> (Ljava/lang/String;I)V
    RETURN
   L1
    LOCALVARIABLE this Lcom/example/practice/moves/MoveEnum; L0 L1 0
    MAXSTACK = 3
    MAXLOCALS = 3

  // access flags 0x8
  static <clinit>()V
   L0
    LINENUMBER 4 L0
    NEW com/example/practice/moves/MoveEnum
    DUP
    LDC "TEST1"
    ICONST_0
    INVOKESPECIAL com/example/practice/moves/MoveEnum.<init> (Ljava/lang/String;I)V
    PUTSTATIC com/example/practice/moves/MoveEnum.TEST1 : Lcom/example/practice/moves/MoveEnum;
    NEW com/example/practice/moves/MoveEnum
    DUP
    LDC "TEST2"
    ICONST_1
    INVOKESPECIAL com/example/practice/moves/MoveEnum.<init> (Ljava/lang/String;I)V
    PUTSTATIC com/example/practice/moves/MoveEnum.TEST2 : Lcom/example/practice/moves/MoveEnum;
   L1
    LINENUMBER 3 L1
    ICONST_2
    ANEWARRAY com/example/practice/moves/MoveEnum
    DUP
    ICONST_0
    GETSTATIC com/example/practice/moves/MoveEnum.TEST1 : Lcom/example/practice/moves/MoveEnum;
    AASTORE
    DUP
    ICONST_1
    GETSTATIC com/example/practice/moves/MoveEnum.TEST2 : Lcom/example/practice/moves/MoveEnum;
    AASTORE
    PUTSTATIC com/example/practice/moves/MoveEnum.$VALUES : [Lcom/example/practice/moves/MoveEnum;
    RETURN
    MAXSTACK = 4
    MAXLOCALS = 0
}

바이트 코드를 살펴보면 Enum Class를 상속받는 것을 확인할 수 있고 valueOf 메서드가 생성된 것을 확인할 수 있습니다. 결론을 내리면 아래와 같습니다.

  • valueOf()
    • Enum Class 내에 존재
    • parameter로 넘기는 Enum 상수를 리턴합니다.
  • values()
    • 컴파일러가 추가 
    • Enum 내 모든 상수를 리턴합니다.
public class Move {
    public static void main(String[] args) {

        MoveEnum[] values = MoveEnum.values();
        for (MoveEnum value : values) {
            System.out.println(value);   
        }

        MoveEnum test1 = MoveEnum.valueOf("TEST1");
        System.out.println(test1);
    }
}

// results
// TEST1
// TEST2

// TEST1

java.lang.enum

앞 단락에서 설명했듯이 enum은 java.lang.enum 클래스를 상속받고 있습니다. 그럼 어떤 메서드가 있는지 살펴보겠습니다.

name()

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

상수를 String 타입으로 리턴합니다.

ordinal()

public static void main(String[] args) {
    System.out.println(MoveEnum.TEST1.ordinal());
    System.out.println(MoveEnum.TEST2.ordinal());
}
0
1

Enum에서 선언한 상수값들의 순서롤 리턴합니다. ordinal() 메서드를 사용할 때는 주의할 점이 있습니다. Enum의 순서로 조건을 태워 로직을 실행하는 코드는 위험 가능성이 있습니다.

public static void main(String[] args) {
    if ( MoveEnum.TEST1.ordinal() == 0) {
        System.out.println("0");
    }
}

이런 코드는 다른 개발자가 Enum 클래스에 상수를 추가할 때 ordinal을 고려하지 못하고 순서를 뒤죽박죽으로 바꿔서 추가할 수 있습니다. 그럼 이 코드는 생각했던 방향과는 다른 방향으로 로직을 실행할 것 입니다.

 

values(), valueOf() 메서드도 있지만 위에서 언급하여 생각하겠습니다.

EnumSet

Enum을 Set 자료구조형으로 만들 수 있습니다. 

  • allOf() : Enum Class를 추가하면 선언된 모든 상수를 Set 자료구조형으로 변환
  • of() : Enum Class에서 추가하고 싶은 항목만 선언하면 선언된 값들로 Set 자료구조형으로 변환
  • noneOf : 아무것도 추가하지 않음
public static void main(String[] args) {
    EnumSet<MoveEnum> moveEnumsAll = EnumSet.allOf(MoveEnum.class);
    for (MoveEnum moveEnum : moveEnumsAll) {
        System.out.println("allOf : " + moveEnum.name());
    }

    EnumSet<MoveEnum> moveEnums = EnumSet.of(MoveEnum.TEST1, MoveEnum.TEST2);
    for (MoveEnum moveEnum : moveEnums) {
        System.out.println("of : " + moveEnum.name());
    }

    EnumSet<MoveEnum> moveEnumsNone = EnumSet.noneOf(MoveEnum.class);
    for (MoveEnum moveEnum : moveEnumsNone) {
        System.out.println("noneOf : " + moveEnum.name());
    }
}
allOf : TEST1
allOf : TEST2
of : TEST1
of : TEST2
반응형