본문 바로가기

연구실/연구실

[JAVA 연구실] ENUM 파헤치기


안녕하세요 Wickies입니다.

한동안 네이버 플랫폼을 이용했었는데 

블로그 개편을 하면서 이사를 하게 되었습니다.

그리고 이사 오는 과정에서 연구실 카테고리가 새로 생겼습니다. 

아직은 부족한 부분이 많지만 차차 나아지리라 믿어봅니다.


개발 공부를 시작한 지 3개월 정도가 지났는데

공부를 하다 보면 예상치 못한 부분에서 어려움을 느낄 때가 참 많습니다.

하지만 어려움을 해결하는 순간 느껴지는 짜릿함이 있지요.


그 어려움을 나눠볼까 싶어서 연구실 카테고리를 만들어보게 되었습니다.

해결된 부분은 나누고 해결하지 못한 부분은 함께 고민할 수 있기를 바래봅니다.



첫 번째 주제는 ENUM 입니다.


한창 자바 배울 때 이런 게 있구나 정도로 넘어갔던 부분인데

우아한 형제들 기술 블로그를 보면서 관심이 생겨서 실습을 하게 되었습니다.


인터넷에 좋은 글이 많아서 참고하면서 실습을 진행했습니다.


참고 사이트

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

https://docs.oracle.com/javase/7/docs/api/java/lang/Enum.html

https://hyeonstorage.tistory.com/174

https://opentutorials.org/module/1226/8025



1. ENUM이란?


1) 특징

- 관련 상수들을 묶어놓은 것

- C/C++과 달리 Java의 Enum은 완전한 기능을 갖춘 클래스

- 비교시 상수값이 같아도 열거형 타입이 다르면 false 반환


2) 장점

- 안정성 향상: 객체 범위 제한

- 소스 가독성 향상: 상수의 사용 목적 및 해석 용이

- IDE의 적극적인 지원: 자동완성, 오타검증, 텍스트 리팩토링



1
2
3
4
5
6
    //public abstract class Enum<E extends Enum<E>>
    //        extends Object
    //        implements Comparable<E>, Serializable
    enum Test {
        TEST1, TEST2, TEST3, TEST4, TEST5
    }
cs


enum도 하나의 클래스입니다.

오라클 홈페이지에서 참조문서를 확인해보니 위 주석과 같이 정의되어 있습니다.

Object를 상속받고 Comparable과 Serializable을 구현한 추상클래스입니다.

즉 enum은 비교와 직렬화가 가능한 '객체'입니다.


그럼 본격적으로 메소드를 확인해볼까요?



2. 메소드


1) values()


1
2
3
4
5
6
7
8
9
        /*
         1. values() 메소드
          - 객체 배열 반환
          - public static T[] values()
        */
            Test test[] = Test.values();
            for (Test i:test)
                System.out.printf("%s ", i);
            // 출력 결과: TEST1 TEST2 TEST3 TEST4 TEST5
cs


values() 메소드 입니다.

enum 객체의 상수들을 객체 배열로 반환합니다.

즉 Test 객체의 상수 TEST1~TEST5 까지가 배열에 담겨있으며

Test 클래스 자체의 타입도 Test

개별적인 객체(ex. TEST1)의 타입 또한 Test입니다.

약간 혼란스러울 수 있는 부분입니다.


// 추가

enum 클래스의 상수들은 해당 클래스를 상속받은 익명 클래스라고 합니다.

즉 TEST1~5 까지의 상수들은 Test 클래스를 상속받은 익명 객체이기 때문에 타입이 같습니다.



2) valueOf()


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
        /*
         2. valueOf() 메소드
          - String 값과 일치하는 enum 타입 객체 반환
          - 'Enum'.'Enum객체'와 동일한 역할
          - public static <T extends Enum<T>> T valueOf(String name)
          - public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)
        */
            System.out.println(Test.valueOf("TEST1"));
            System.out.println(Test.valueOf("TEST1").equals(Test.TEST1));
            // 출력결과: TEST1, true
 
            // getTest 메소드를 통해 enum 클래스 지정
            // static Test getTest(String value){return Test.valueOf(Test.class, value);}
            System.out.println(getTest("TEST1"));
            // 출력결과: TEST1
cs


valueOf() 메소드의 매개변수로 String 값을 입력하면

해당하는 문자열과 일치하는 상수가 있을 경우에는 해당 객체를 반환하고

없을 경우에는 Exception이 발생합니다.

Test.TEST1 과 Test.valueOf("TEST1")의 비교 결과가 true입니다.

즉 두 코드는 같은 역할을 합니다.

public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)

그리고 위 형태로 오버로딩이 되어있습니다.

첫 번째 인자로 enum 클래스를,

두 번째 인자로는 동일하게 조회할 문자열을 입력합니다.


static Test getTest(String value){
return Test.valueOf(Test.class, value);
}

메소드를 하나 만들었습니다.

메소드 내에 enum 클래스가 지정되어 있는데

이 부분을 자유롭게 활용 가능할 것 같습니다.

상황에 맞게 클래스를 지정할 수도 있고 제한을 둘 수도 있을 것 같습니다.

활용 예제는 한참을 검색해도 찾아볼 수가 없었습니다.



3) name()


1
2
3
4
5
6
7
8
9
10
11
        /*
         3. name() 메소드
          - 호출된 값의 이름을 String 값으로 리턴
          - public final String name()
        */
            String testString="TEST1";
            Test test1=test[0];
            System.out.println(test1.name());
            System.out.println(test1.equals(test1.name()));
            System.out.println(testString instanceof String);
            // 출력결과: TEST1, false, true
cs


name() 메소드는 활용도가 높은 메소드입니다.

enum 객체를 String 객체로 변환해주기 때문입니다.


원활한 테스트를 위해 enum 객체와 String 객체를 정성스레 변수에 담았습니다.

그리고 테스트를 해보니 name() 메소드를 사용하면 정상적으로 형변환이 되는걸 확인할 수 있었습니다.



혹시나 싶어서 강제 형변환 테스트를 해봤는데 

이러한 에러메시지를 만났습니다.



4) ordinal()


1
2
3
4
5
6
7
        /*
         4. ordinal() 메소드
          - 해당 값의 인덱스값 리턴
          - public final int ordinal()
        */
            System.out.println(test1.ordinal());
            // 출력결과: 0
cs

 

ordinal() 메소드는  enum 클래스의 순위를 기반으로 인덱스를 반환합니다.

배열에서 사용하는 indexOf() 메소드와 비슷한 역할이라고 생각하시면 될 것 같습니다.


생각해보니 enum 자체가 원론적으로 상수인데

이 메소드가 있는 것 자체가 아이러니하다는 생각이 드네요.


5) compareTo()


1
2
3
4
5
6
7
8
9
10
         /*
          5. compareTo() 메소드
           - 해당 값들의 인덱스 오차 반환
           - public final int compareTo(E o)
         */
 
            System.out.println(test[0].compareTo(test[0]));
            System.out.println(test[0].compareTo(test[2]));
            System.out.println(test[3].compareTo(test[2]));
            // 출력결과: 0, -2, 1
cs


Comparable 인터페이스로 구현된 compareTo() 메소드입니다.

compareTo() 메소드는 인덱스의 차이값을 반환합니다.

즉 a.compareTo(b) 메소드는 a.ordinal()-b.ordinal() 을 반환합니다.

a-b라고 생각하시면 마음이 편할 것 같습니다.



이 외에도 메소드가 몇 개 더 있지만

Object 객체에서 상속받은 메소드가 대부분이라 이 정도로 마무리하겠습니다.


혹시나 직접 테스트해보고 싶으신 분이 있을까 싶어서 전체 코드를 첨부합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
    //public abstract class Enum<E extends Enum<E>>
    //        extends Object
    //        implements Comparable<E>, Serializable
    enum Test {
        TEST1, TEST2, TEST3, TEST4, TEST5
    }
 
    public class TestClass01 {
        public static void main(String args[]){
 
 
        /*
         1. values() 메소드
          - enum 타입 객체 배열 반환
          - public static T[] values()
        */
            Test test[] = Test.values();
            for (Test i:test)
                System.out.printf("%s ", i);
            // 출력 결과: TEST1 TEST2 TEST3 TEST4 TEST5
 
 
 
        /*
         2. valueOf() 메소드
          - String 값과 일치하는 enum 타입 객체 반환
          - 'Enum'.'Enum객체'와 동일한 역할
          - public static <T extends Enum<T>> T valueOf(String name)
          - public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)
        */
            System.out.println(Test.valueOf("TEST1"));
            System.out.println(Test.valueOf("TEST1").equals(Test.TEST1));
            // 출력결과: TEST1, true
 
            // getTest 메소드를 통해 enum 클래스 지정
            // static Test getTest(String value){return Test.valueOf(Test.class, value);}
            System.out.println(getTest("TEST1"));
            // 출력결과: TEST1
 
 
 
        /*
         3. name() 메소드
          - 호출된 값의 이름을 String 값으로 리턴
          - public final String name()
        */
            String testString="TEST1";
            Test test1=test[0];
            System.out.println(test1.name());
            System.out.println(test1.equals(test1.name()));
            System.out.println(testString instanceof String);
            // 출력결과: TEST1, false, true
            
 
        /*
         4. ordinal() 메소드
          - 해당 값의 인덱스값 리턴
          - public final int ordinal()
        */
            System.out.println(test1.ordinal());
            // 출력결과: 0
 
 
 
         /*
          5. compareTo() 메소드
           - 해당 값들의 인덱스 오차 반환
           - public final int compareTo(E o)
         */
 
            System.out.println(test[0].compareTo(test[0]));
            System.out.println(test[0].compareTo(test[2]));
            System.out.println(test[3].compareTo(test[2]));
            // 출력결과: 0, -2, 1
 
        }
 
        static Test getTest(String value){
            return Test.valueOf(Test.class, value);
        }
    }
 
 
 
cs




3. ENUM 활용


이제 사용법을 배웠으니 간단하게 활용을 해볼 시간입니다.

사실 그리 간단하지는 않았습니다.

자바스크립트는 에러가 나질 않아서 갑갑하고

자바는 걸핏하면 에러가 나서 갑갑합니다.


소스 코드는 우아한 형제들 기술 블로그에서 참고했고

저 나름의 방식으로 수정해서 사용했습니다.


1) Customer 클래스


1
2
3
4
5
enum Customer{
    NORMAL("일반손님", Arrays.asList("홀""테이크아웃")),
    FAMILY("제휴사회원", Arrays.asList("CJ회원""신세계회원""GS회원")),
    STAFF("직원", Arrays.asList("김자바""이자바""박자바""남궁자바")),
    BOSS("사장", Arrays.asList("사장""바지사장""이사"));
cs


Customer 클래스를 생성했습니다.


2장에서 실습했던 enum Test{TEST1, TEST2, TEST3, TEST4, TEST5} 의 형태에서

각 객체마다 괄호가 하나씩 생겨났습니다.

그리고 괄호에는 무슨 값이 들어가나 보니 

String과 List<String>을 매개변수로 받습니다.

List는 new 연산자로 생성하면 에러가 발생하니 꼭 Arrays.asList를 사용하시길 바랍니다.

다른 방법도 한 가지를 찾았으나 asList 메소드를 사용하는게 훨씬 편한 방법입니다.


첫 번째 매개변수에는 String 형태의 타이틀,

두 번째 매개변수는 List<String> 형태로 데이터들을 입력합니다.

다 입력하고나면 에러가 발생합니다.

입력 형태에 알맞게 생성자를 만들어줘야 합니다.

(저는 만들지 않았지만 필요에 따라 Getter Setter도 만들어줄 수 있습니다.)


1
2
3
4
5
6
7
    private String title;
    private List<String> list;
 
    Customer(String title, List<String> list) {
        this.title = title;
        this.list = list;
    }
cs


이렇게 변수와 생성자를 선언하면

각 상수에서 타이틀과 데이터들을 받아올 수 있습니다.


그럼 활용을 해봐야겠지요?


1
2
3
4
5
6
7
8
9
10
    public static Customer who(String code){
        return Arrays.stream(Customer.values())
                .filter(title -> title.check(code))
                .findAny()
                .orElse(NORMAL);
    }
 
    public boolean check(String code){
        return list.stream().anyMatch(list -> list.equals(code));
    }
cs


활용할 수 있게 스트림을 이용해서 메소드를 생성했습니다.

메소드를 대략적으로 설명하면


1) code 문자열을 매개변수로 입력받는다.

2) values(), stream()을 이용해서 enum 객체를 순회한다.

3) check()를 이용해서 enum 객체의 list를 순회하면서 code와 일치하는 부분을 찾는다.

4) code와 일치하는 객체를 찾으면 해당 enum 객체를 반환하고 없으면 NORMAL 객체를 반환한다.


자 그럼 잘 작동하는지 테스트를 해볼 차례입니다..


1
2
3
4
5
        System.out.println(Customer.who("홀")); // NORMAL
        System.out.println(Customer.who("신세계회원")); // FAMILY
        System.out.println(Customer.who("이자바")); // STAFF
        System.out.println(Customer.who("바지사장")); // BOSS
        System.out.println(Customer.who("GS회원")); // FAMILY
cs


우측에 주석이 출력 내용입니다.

각각 올바른 결과를 출력하는걸 확인할 수 있습니다.



2) Payment 클래스


이번엔 다른 방법으로 활용을 해볼 Payment 클래스입니다.

enum 객체와 람다식을 통해 추상메소드로 활용하는 방법입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum Payment{
    NORMAL(value -> value),
    FAMILY(value -> value - value / 5),
    STAFF(value -> value / 2),
    BOSS(value -> (long0);
 
    private Function<Long, Long> expression;
 
    Payment(Function<Long, Long> expression) {
        this.expression = expression;
    }
 
    public long pay(long value){
        return expression.apply(value);
    }
}
cs


꼭 그럴 필요는 없지만 편의상 Customer 클래스와 같은 이름으로 지정해주었습니다.

서류나 협업 과정에서 혼란이 생길 수도 있으니 개인의 상황에 맞춰서 선택하시기를 바랍니다.


Customer 클래스와 다른 부분이 있다면 Fucntion 인터페이스를 이용합니다.



요 녀석입니다. 

@FunctionalInterface 애너테이션을 통해

추상메소드가 하나인, 즉 람다식을 사용할 수 있는 인터페이스를 명시합니다.


그리고 그 추상메소드는 enum 상수를 통해 구현이 되고있습니다.

이 정도까지 되면 상수라는 표현은 어딘가 어색하게 느껴집니다.

자바에서 추가되는 기술을 공부하다 보면 

자바는 타입 안정성을 정말 중요하게 여기는 것 같습니다.


아무튼 코드로 돌아오자면 

각각의 enum 객체들은 결제 금액을 연산합니다.

NORMAL은 100%

FAMILY는 80%

STAFF는 50%

BOSS는 무료입니다.


그리고 이 연산들은 pay 메소드를 통해 구현됩니다.


이제 Customer 클래스와 Payment 클래스를 합쳐서 테스트를 해보겠습니다.


1
2
3
4
5
6
7
8
        class Human{
            long invoice;
            Human(String div, long fee){
                Payment payment = Payment.valueOf(Customer.who(div).name());
                this.invoice= payment.pay(fee);
            }
            public long getInvoice() { return invoice; }
        }
cs


테스트 케이스를 찍어내기 위해 간단하게 휴먼 객체를 하나 만들었습니다.

나름 휴먼 객체인데 속성이 청구서밖에 없다니 약간 슬프군요.


코드를 설명하자면

1) 휴먼 객체 생성시 받은 div를 입력해서 Customer 객체에서 분류를 합니다.

2) 분류에 맞게 Payment 객체에서 할인율을 결정합니다.

3) Payment 객체의 pay 메소드를 통해 금액을 연산 후 청구서에 담습니다.

4) Invoice Getter를 이용해서 청구서를 발송합니다.


자 그럼 휴먼들을 생성해보겠습니다.



1
2
3
4
        Human h1 = new Human("테이크아웃"1000); 
        Human h2 = new Human("신세계회원"1000); 
        Human h3 = new Human("남궁자바"1000); 
        Human h4 = new Human("바지사장"1000); 
cs


가진 속성이라고는 청구서밖에 없는 휴먼 4명을 찍어냈습니다.

그리고 전부 동일하게 1000원짜리를 팔았습니다.


1
2
3
4
        System.out.println(h1.getInvoice()); 
        System.out.println(h2.getInvoice()); 
        System.out.println(h3.getInvoice()); 
        System.out.println(h4.getInvoice()); 
cs


자 그럼 테스트를 해볼까요??




쨘~


각각 다른 금액이 청구된 걸 확인할 수 있습니다.


이번 ENUM 실습은 여기까지입니다.

그럼 전체 코드를 끝으로 포스팅을 마무리하도록 하겠습니다.


부족한 점이 많은 포스팅이니 혹시 잘못된 부분이 있다면 거침없는 지적 부탁드리겠습니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
 
enum Customer{
    NORMAL("일반손님", Arrays.asList("홀""테이크아웃")),
    FAMILY("제휴사회원", Arrays.asList("CJ회원""신세계회원""GS회원")),
    STAFF("직원", Arrays.asList("김자바""이자바""박자바""남궁자바")),
    BOSS("사장", Arrays.asList("사장""바지사장""이사"));
 
    private String title;
    private List<String> list;
 
    Customer(String title, List<String> list) {
        this.title = title;
        this.list = list;
    }
 
    public static Customer who(String code){
        return Arrays.stream(Customer.values())
                .filter(title -> title.check(code))
                .findAny()
                .orElse(NORMAL);
    }
 
    public boolean check(String code){
        return list.stream().anyMatch(list -> list.equals(code));
    }
 
}
 
enum Payment{
    NORMAL(value -> value),
    FAMILY(value -> value - value / 5),
    STAFF(value -> value / 2),
    BOSS(value -> (long0);
 
    private Function<Long, Long> expression;
 
    Payment(Function<Long, Long> expression) {
        this.expression = expression;
    }
 
    public long pay(long value){
        return expression.apply(value);
    }
}
 
public class TestClass02 {
    public static void main(String args[]){
        System.out.println(Customer.who("홀")); // NORMAL
        System.out.println(Customer.who("신세계회원")); // FAMILY
        System.out.println(Customer.who("이자바")); // STAFF
        System.out.println(Customer.who("바지사장")); // BOSS
        System.out.println(Customer.who("GS회원")); // FAMILY
 
        class Human{
            long invoice;
            Human(String div, long fee){
                Payment payment = Payment.valueOf(Customer.who(div).name());
                this.invoice = payment.pay(fee);
            }
            public long getInvoice() { return invoice; }
        }
 
        Human h1 = new Human("테이크아웃"1000);
        Human h2 = new Human("신세계회원"1000);
        Human h3 = new Human("남궁자바"1000);
        Human h4 = new Human("바지사장"1000);
 
        System.out.println(h1.getInvoice()); // 1000
        System.out.println(h2.getInvoice()); // 800
        System.out.println(h3.getInvoice()); // 500
        System.out.println(h4.getInvoice()); // 0
 
 
    }
}
cs