www.youtube.com/watch?v=HLnMuEZpDwU
목표
자바의 열거형에 대해 학습하세요.
학습할 것 (필수)
- enum 정의하는 방법
- enum이 제공하는 메소드 (values()와 valueOf())
- java.lang.Enum
- EnumSet
추천 도서
www.yes24.com/Product/Goods/7516911
www.acornpub.co.kr/book/jpa-programmig
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
'Study > java' 카테고리의 다른 글
[백기선님의 자바 라이브 스터디] 10주차 - 멀티쓰레드 프로그래밍 (0) | 2021.03.02 |
---|---|
[백기선님의 자바 라이브 스터디] 9주차 - 예외 처리 (0) | 2021.02.24 |
[백기선님의 자바 라이브 스터디] 8주차 - 인터페이스 (0) | 2021.02.21 |
[백기선님의 자바 라이브 스터디] 7주차 - 패키지 (0) | 2021.02.19 |
[백기선님의 자바 라이브 스터디] 6주차 - 상속 (0) | 2021.02.17 |