상세 컨텐츠

본문 제목

[Java] 자바 열거형 Enum 예제 관련 ~!

본문

반응형

 

 

 

 

=================================

=================================

=================================

 

 

 

 

 

출처: https://bluepoet.me/2012/07/18/%EB%B2%88%EC%97%AD%EC%9E%90%EB%B0%94-enum%EC%9D%98-10%EA%B0%80%EC%A7%80-%EC%98%88%EC%A0%9C/

 

원본글 : http://javarevisited.blogspot.sg/2011/08/enum-in-java-example-tutorial.html

자바의 Enumeration(Enum)은 JDK 1.5에 소개되었고, J2SE5에서 내가 가장 좋아하는

특징 중 하나이다.

타입처럼 Java Enum은 NEW,PARTIAL,FILL,FILL or CLOSED와 같은 주문의

대표적인 상태의 예와같이 확실한 상황하에서 더욱 적합하다.

Enumeration(Enum)은 원래 자바에서 이용하지 않았고,  C나 C++같은 다른 언어에서

사용했다.

하지만 결국 자바는 깨닫게 되었고 enum keyword에 의해 JDK 5안에 Enum이 소개되었다.

이 Java enum 튜토리얼안에서 우리는 자바안의 다른 enum 예를 볼 수 있을 것이고

자바안의 enum 사용에 대해 배울 수 있다.

자바안에 Enum에 의해 공급된 다른 특징들과 그것들을 어떻게 사용해야 되는지가

이 Java enum 튜토리얼의 초점이 될 것이다.

만약 당신이 자바 enum을 불편하게 느끼지 않는 것보다 전의 C 혹은 C++에서

Enumeration을 사용하고 있다해도,자바안의 Enum에 대한 나의 의견은

어느 다른 언어보다도 더 다채롭고 다용도로 쓰일 것이다.

  • Java enum 없이 어떻게 enumberable을 표현하는가

자바 enum이 자바 1.5로부터 사용된 이래로, 그것의 가치는 JDK1.5이전의 자바 안

enumerable value를 어떻게 보여주어서 사용할지 혹은 그것이 없이 사용할지에 대한

부분에 대해서 논의했다.

나는 behavior같은 enum을 모방해서 public static final 상수를 사용했다.

개념을 더욱 명확히 이해하기 위해 자바안의 enum 예제를 살펴보자

이 예제에서 우리는 PENNY (1) NICKLE (5), DIME (10), and QUARTER (25) 같이

값을 가지는 열거형처럼 US 통화코인을 사용할 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
class CurrencyDenom {
            public static final int PENNY = 1;
            public static final int NICKLE = 5;
            public static final int DIME = 10;
            public static final int QUARTER = 25;
 
      }
 
class Currency {
   int currency; //CurrencyDenom.PENNY,CurrencyDenom.NICKLE,
                 // CurrencyDenom.DIME,CurrencyDenom.QUARTER
}

이것이 우리의 목적을 어느정도 충족해주긴 하지만 몇가지 심각한 한계를 가지고 있다.

(1)No Type-Safety

모든것중에 첫번째는 타입에 대해 안전하지 않다는 것이다. 당신이 99 통화에 대해

어느 유효한 int value값을 할당하려 해도 저 값을 대표하는 coin이 없다.

(2) No Meaningful Printing 

이 상수에 대한 어느것의 값을 출력하는 것은  당신이 “NICKLE “대신에 “5”를 출력할 때

코인의 의미있는 이름 대신에 그것의 numeric 값을 출력하게 될 것이다

(3) No namespace

currencyDenom 상수에 접근하기 위해 우리는 단지 PENNY를 사용하는 대신에

CurrencyDenom.PENNY 클래스이름을 고쳐서 사용할 필요가 있다.

이것은 또한 JDK 1.5안에서 static import를 사용함으로 이루어지게 되었다.

자바 enum은 이 모든 한계에 대한 대답이다. 자바의 enum은 타입에 대해 안전하고,

의미있는 String 이름을 부여하며 자신의 namaspace를 가지고 있다.

지금 위의 예제를 자바 enum을 사용해서 다시 보자.

1
public enum Currency {PENNY, NICKLE, DIME, QUARTER};

여기 Currency는 우리의 enum이고 PENNY, NICKLE, DIME, QUARTER는 enum의 상수다.

enum상수 주변에 중괄호를 주목하라. 왜냐하면 enums는 자바의 클래스나 인터페이스

같은 타입이기 때문이다.

또한 우리는 클래스나 인터페이스처럼 enum을 위한 유사한 naming convention을 따를 것이고,

enum 상수들은 static final을 내재한 이후로 우리는 자바안의 상수처럼 그들을 표시하기 위해

모두 대문자로 사용한다.

  • 자바안의 Enum은 무엇인가?

지금 “자바안의 enum은 무엇인가“에 대한 기본적인 질문으로 돌아가는 것이

자바의 키워드이며, 자바 enum에 대한 더욱 자세한 조항은 클래스나 인터페이스와 같은 타입이고

enum 상수로 정의해 사용할 수 있다.

enum 상수에 static과 final이 내재되어 있고, 당신은 한번 만들어지면 값을 바꿀 수 없다.

자바안의 Enum은 타입에 대해 안전하고 int 변수처럼 switch statement안에서 사용된다.

enum은 너가 변수 이름처럼 사용할 수 없는 키워드이며, JDK1.5에 소개된 이후로 너의 모든 이전 코드는

변수 이름과 같은 enum은 돌아가지 않을 것이고 리팩토링할 필요가 있을 것이다.

  • 자바의 Enum에 대한 이익

1) Enum은 타입에 대해 안전하다. 당신은 미리 정의된 enum 변수안의 상수외 다른 것을 할당할 수 없다.

2) Enum은 그 자신의 name-space를 가진다.

3) Enum의 가장 큰 특징은 int나 char같이 원시타입처럼 switch statememt 안에서 Enum을 사용할 수 있다.

4) 자바의 Enum은 새로운 상수를 추가하기가 쉽고, 기존에 존재하는 코드를 고치지 않고 새로운 상수를 추가할

수 있다.

  • 자바의 Enum에 대한 중요한 점

1) 자바안의 Enums는 타입에 대해 안전하고 자신의 name-space를 가진다. 그것은 enum이 type값을 가진다는 것이고

아래 “Currency” 예제에서 Enum 상수안에 명기된 것 외 어떤 다른 값도 할당할 수 없다

1
2
3
public enum Currency {PENNY, NICKLE, DIME, QUARTER};
Currency coin = Currency.PENNY;
coin = 1; //compilation error

2) 지바안의 Enums는 클래스 혹은 인터페이스처럼 타입을 참조한다. 생성자를 정의할 수 있고, 자바 enum안의 메서드나 변수는

자바의 enum type의 다음 예제에서 보여준 것처럼 C나 C++안의 Enum보다 더욱 파워풀하게 만든다

3) 아래 예제에서 보는 것처럼 만들때 enum 상수에 값을 명기할 수 있다.

1
public enum Currency {PENNY(1), NICKLE(5), DIME(10), QUARTER(25)};

그러나 멤버변수나 생성자를 이용할 필요가 있을 때 이것을 해야한다. 왜냐하면 PENNY(1)은 정말로 아래 예제에서 보는 것처럼

int value를 가지는 생성자를 호출하기 때문이다.

1
2
3
4
5
6
7
8
public enum Currency {
        PENNY(1), NICKLE(5), DIME(10), QUARTER(25);
        private int value;
 
        private Currency(int value) {
                this.value = value;
        }
};

자바 enum의 생성자는 private이고, 다른 접근제어자는 에러가 나올 것이다. 지금 각 코인에 연결된 값을 가져오기 위해 어느 평범한

자바 클래스처럼 자바 enum안에 public getValue()메서드를 정의할 있다. 또한 첫번째 라인의 세미콜론은 선택이다.

4) Enum 상수들은 static과 final을 내재하고 한번 만들어지면 바꿀 수 없다. 아래 코드는 에러를 뱉어낼 것이다

1
2
Currency.PENNY = Currency.DIME;
The final field EnumExamples.Currency.PENNY cannot be re assigned.

5) 자바의 Enum은 int, char처럼 switch statement의 인자로 사용할 수 있다. 이런 특징은 switch문에서

매우 유용하다. switch statement에서 java enum을 어떻게 사용하는지 아래 예를 보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Currency usCoin = Currency.DIME;
    switch (usCoin) {
            case PENNY:
                    System.out.println("Penny coin");
                    break;
            case NICKLE:
                    System.out.println("Nickle coin");
                    break;
            case DIME:
                    System.out.println("Dime coin");
                    break;
            case QUARTER:
                    System.out.println("Quarter coin");
    }

6) enum안에 정의된 상수들은 final이고, 비교할때는 “==”를 사용한다.

1
2
3
4
5
Currency usCoin = Currency.DIME;
    if(usCoin == Currency.DIME){
       System.out.println("enum in java can be"+
               "compared using ==");
    }

7) 자바 컴파일러는 자동으로 모든 enum을 위한 static values() 메서드를 제공한다.

Values() 메서드는 enum 상수의 배열을 리턴한다. values()를 사용해서 enums의 값들을

배열을 돌면서 사용할 수 있다.

1
2
3
4
5
6
7
8
for(Currency coin: Currency.values()){
        System.out.println("coin: " + coin);
    }
And it will print:
coin: PENNY
coin: NICKLE
coin: DIME
coin: QUARTER

enums안에 정의된 순서와 동일하다는 것에 주목하라

8) Enum은 메서드를 오버라이드 할 수 있다. 아래예제처럼 enum안에 toString()메서드를

오버라이드 해서 enums 상수들을 위한 의미있는 설명을 제공하고 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
  public String toString() {
       switch (this) {
         case PENNY:
              System.out.println("Penny: " + value);
              break;
         case NICKLE:
              System.out.println("Nickle: " + value);
              break;
         case DIME:
              System.out.println("Dime: " + value);
              break;
         case QUARTER:
              System.out.println("Quarter: " + value);
        }
  return super.toString();
 }
};

그리고 여기 그것이 어떻게 보여지는지 나와있다.

1
2
3
4
5
Currency usCoin = Currency.DIME;
System.out.println(usCoin);
 
output:
Dime: 10

9) 두개의 새로운 컬렉션 클래스 EnumMap과 EnumSet은 추가되었고, 자바 enum을 지원하기 위해 컬렉션

패키지에 추가되었다. 이러한 클래스들은 자바의 Map과 Set 인터페이스에서 높은 성능을 보이며 우리는

어떤 기회에서든지 이것을 사용할 수 있다

10) 자바의 new 연산자를 사용해서 객체를 생성할 수 없다. 왜냐하면 Enum의 생성자가 private이고

Enums 상수는 오직 Enums 그 자신 안에서는 만들어질 수 있다.

11) 자바 enums의 객체는 어떤 enums 상수가 코드안에서 처음 호출되거나 참조될 때 만들어진다.

12) 자바의 Enum은 인터페이스를 구현하고 평범한 클래스처럼 어느 메서드라도 오버라이드 할 수 있다.

그것은 또한 Serializable과 Comparable 인터페이스 둘다 구현을 내재하고 있다.

java enum을 사용하여 인터페이스를 어떻게 구현하는지 아래 예를 보자

1
2
3
4
5
6
7
8
9
10
11
public enum Currency implements Runnable{
  PENNY(1), NICKLE(5), DIME(10), QUARTER(25);
  private int value;
  ............
 
  @Override
  public void run() {
  System.out.println("Enum in Java implement interfaces");
 
   }
}

13) 자바의 enum안에 추상메서드도 정의할 수 있다. 또한 enum의 다른 객체를 위해 다른

구현을 제공할 수도 있다. 아래 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
public enum Currency implements Runnable{
          PENNY(1) {
                  @Override
                  public String color() {
                          return "copper";
                  }
          }, NICKLE(5) {
                  @Override
                  public String color() {
                          return "bronze";
                  }
          }, DIME(10) {
                  @Override
                  public String color() {
                          return "silver";
                  }
          }, QUARTER(25) {
                  @Override
                  public String color() {
                          return "silver";
                  }
          };
          private int value;
 
          public abstract String color();
 
          private Currency(int value) {
                  this.value = value;
          }
          ..............
  }

이번 예에서 모든 코인은 color() 추상메서드를 만들어 다른 컬러를 가지게 될 것이고, 자신의 컬러가

정의된 enum 객체를 가지게 될 것이다.

color()메서드를 호출해서 어느 코인의 컬러를 아래예처럼 가져올 수 있다.

1
System.out.println("Color: " + Currency.DIME.color());
  • Enum valueof의 예

나의 구독자 중에 한명이 enum에서 String을 바꾸는valueOf메서드에 대해 언급되지 않았다는 걸 알려주었다.

여기 그가 제안한게 있다.

“enum의 valueOf()메서드를 포함할 수 있다. valueOf()는 static 메서드이고 String 인자값을 가지고

enum안에 String을 바꿔 사용할 수 있다.

한가지 생각해야 할건 enum의 valueOf(String)메서드는

“Exception in thread “main” java.lang.IllegalArgumentException: No enum const class

예외를 던질수 있다는 것이다.

ordenal()과 name() 유틸리티 메서드에 제안한 또 다른 독자는 Ordinal은 enum 상수의 포지션(순서)

를 리턴해주고, 특별한 enum 상수를 만들때 정확한 스트링을 리턴하도록 name()이 선언되어 있다라고 한다

*자바 enum의 대해선 아래 블로그에 정리가 잘 되어 있으니 참고하세요

http://iilii.egloos.com/4343065/

 

 

=================================

=================================

=================================

 

 

 

출처: http://rangken.github.io/blog/2015/effective-java-5/

 

열거형과 어노테이션 (30~37)

  • Java 에서 열거 자료형(enum type) 은 참조 자료형이다.

ITEM30 : int 상수 대신 enum을 사용하라

  • 열거 자료형(enumerated type)은 고정 개수의 상수들로 값이 구성되는 자료형
  • int 대신 enum 을 사용하면 공간적 시간적 손해를 보긴핮만 성능면에서 비등하다
  • enum 은 가독성이 좋고 안전하며 강력함.
  • 생성자나 멤버가 필요없으나 데이터 또는 그데이터에 관계된 메서드를 추가해서 기능을 향상시킨 enum 도 많다!
  • int enum 패턴 : 사용하면안된다
1 public static final int APPLE_FUJI = 0; 2 public static final int APPLE_PIPPIN =1; 3 ... 4 int i = (APPLE_FUJI - ORANGE_TEMPLE) / APPLE_PIPPIN; // 변수가 섞임
  • 단점
    • 상수를 사용하는 클라이언트 코드와 함께 컴파일됨, 값이 변경되면 다시 컴파일 다시해야함
    • 크기를 알아낼수 없음
    • 디버깅이 불편함
      • String enum 패턴으로 해결 가능하지만 더더욱 최악의방법
1 public enum Apple { FUJI, PIPPIN, GRANNY_SMITH}
  • enum
    • C/C++/C# 언어가 제공하는 enum 이랑 다름, 다른언어에서는 단순 int 값이다
      • Java 에서는 참조 자료형
    • enum 자료형은 public static final 필드 형태로 제공하는것
    • 클라이언트가 접근할수 있는 생성자가 없다. 계승 확장 불가능
    • 컴파일 시점에 형 안정성 제공한다! 같은 enum형 에서 비교연산자 == 가 가능하다!
    • 같은 이름의 상수가 공존 가능.
    • enum 형은 toString 메서드를 호출 가능해서 문자열로 쉽게 변경 가능!
    • Object 에 정의된 메서드들이 포함되어있다.
    • Comparable, Serializable 인터페이스 구현되어있다!
 1 public enum Planet {  2      MERCURY (3.302e+23, 2.439e6),  3      VENUS .  4      EARTH  5      MARS  6      JUPITER  7      SATURE  8      URANUS  9      NEPTUNE 10      11      private final double mass; // 킬로그램 단위 12      private final double radius; 13      private final dobule surfaceGravity; 14      15      // MERCURY (3.302e+23, 2.439e6) 처럼 두개의 값을 초기화 하는 생성자 16      Planet(double mass, double radius){ 17           this.mass = mass; 18           this.radius = radius; 19           surfaceGravity = G * mass / (radius * radius); 20      } 21      22      // 접근자 23      public double mass() { return mass; } 24      ... 25      26      // 추가 메서드 27      public double surcfaceWight(double mass){ 28           return mass * surfaceGravity; // F = ma 29      } 30 } 31  32 // 사용 33 double mass = earthWeight / Planet.EARTH.surfaceGravity(); 34 // values 라는 static 함수존재한다! 이외에도 기본 제공함수들이 많음! 35 for(Planet p : Planet.values() ) 36      p.surfaceWeight(mass)
  • enum 상수에 데이터를 넣으려면 객체 필드를 선언하고 생성자를 통해 받은 데이터를 그 필드에 저장하면 된다!
  • enum 도 클라이언트에게 공개할 이유가 없다면 private 나 package-private로 선언해야 한다.
  • 공개한다면 최상위 public 클래스로 생성하거나, 최상위 클래스의 멤버 클래스로 선언하면 된다.
  • switch 문 대신 상수별 메서드를 구현해야 한다!
 1 public enum Operation {  2      // switch 로 +,-,*,/ 일때 분기해서 처리하는것보다 이런식으로 처리하는게 확장성에 좋다.  3      // 새로운 ^ 과 같은 값이 추가된다면 switch 로 하면 문제없이 컴파일 되지만 abstract 방식을 사용하면 컴파일 에러 발생!  4      PLUS("+") {  5           double apply(double x, double y) {  6                return x + y;  7           }  8      },  9      MINUS("-") { 10           double apply(double x, double y) { 11                return x - y; 12           } 13      }, 14      TIMES("*") { 15           double apply(double x, double y) { 16                return x * y; 17           } 18      }, 19      DIVIDE("/") { 20           double apply(double x, double y) { 21                return x / y; 22           } 23      }; 24      private final String symbol; 25  26      Operation(String symbol) { 27           this.symbol = symbol; 28      } 29  30      @Override 31      public String toString() { 32           return symbol; 33      } 34  35      abstract double apply(double x, double y); 36  37      private static final Map<String, Operation> stringToEnum = new HashMap<String, Operation>(); 38      static { 39           // Operation 생성자 안에서 stringToEnum 에 값을 넣으면 NullPointerException 이 난다. 40           // enum 생성자안에서는 enum 의 static 필드를 접근할수 없다. (컴파일 시점에서 상수면 접근 가능) 41           // 생성자가 실행될때 static 필드는 초기화된 상태가 아니기 떄문! 42           for (Operation op : values()) 43                stringToEnum.put(op.toString(), op); 44      } 45  46      public static Operation fromString(String symbol) { 47           return stringToEnum.get(symbol); 48      } 49  50      public static void main(String[] args) { 51           double x = Double.parseDouble(args[0]); 52           double y = Double.parseDouble(args[1]); 53           for (Operation op : Operation.values()) 54                System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y)); 55      } 56 }
  • enum 값에 따라 처리하는 방법2
 1 enum PayrollDay {  2      MONDAY(PayType.WEEKDAY), TUESDAY(PayType.WEEKDAY), WEDNESDAY(  3                PayType.WEEKDAY), THURSDAY(PayType.WEEKDAY), FRIDAY(PayType.WEEKDAY), SATURDAY(  4                PayType.WEEKEND), SUNDAY(PayType.WEEKEND);  5   6      private final PayType payType;  7   8      PayrollDay(PayType payType) {  9           this.payType = payType; 10      } 11  12      double pay(double hoursWorked, double payRate) { 13           return payType.pay(hoursWorked, payRate); 14      } 15  16      private enum PayType { 17           WEEKDAY { 18                double overtimePay(double hours, double payRate) { 19                     return hours <= HOURS_PER_SHIFT ? 0 : (hours - HOURS_PER_SHIFT) 20                               * payRate / 2; 21                } 22           }, 23           WEEKEND { 24                double overtimePay(double hours, double payRate) { 25                     return hours * payRate / 2; 26                } 27           }; 28           private static final int HOURS_PER_SHIFT = 8; 29           30           // 추상 메소드! 확장성에 좋다! 31           abstract double overtimePay(double hrs, double payRate); 32  33           double pay(double hoursWorked, double payRate) { 34                double basePay = hoursWorked * payRate; 35                return basePay + overtimePay(hoursWorked, payRate); 36           } 37      } 38 }

ITEM31 : ordinal 대신 객체 필드를 사용하라

  • 모든 enum 에는 ordinal() 이라는 메서드가 있음
1 public num Ensemble { 2     SOLO, DUET, TRIO, QUATET, QUINTET,.. 3      4     // 절대 만들어서는 안되는 메소드, 타입을 추가하거나 제외할 경우에 매우 취약하다. 5     public int numberOfMusicians() { return ordinal() + 1;}  6 }
  • ENUM 형 값에 명시적으로 값을 줄수도 있다. 이런경우에 값을 바꿀경우 numberOfMusicians() 함수가 외부에 종속적이므로 문제가 발생할수 있다.
  • ENUM 상수에 연계되는 값을 ordinal 을 사용해 표현하지 말아라!!! 그런값이 필요하다면 그대신 객체 필드에 저장해야 한다.
 1 public enum Ensemble {  2     // 명시적으로 enum 숫자 정해줌  3     SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5), SEXTET(6), SEPTET(7), OCTET(  4             8), DOUBLE_QUARTET(8), NONET(9), DECTET(10), TRIPLE_QUARTET(12);  5   6     private final int numberOfMusicians;  7       8     // 생성 할때 사이즈 받는다!  9     Ensemble(int size) { 10         this.numberOfMusicians = size; 11     } 12     // 객체 필드를 리턴함 13     public int numberOfMusicians() { 14         return numberOfMusicians; 15     } 16 }
  • ordinal() 함수 자체는 EnumSet EnumMap 처럼 일반적인 용도의 enum 기반 자료구조에서 사용할 목적으로 설계한 메서드 이다.
    • EnumSet : 비트 필드 대신에 사용해야하는 자료구조
      • EnumSet.of(Ensemble.SOLO,Ensemble,DUET) _ EnumMap : 맵에 키값으로 enum 필드를 사용할수 있는 자료구조
      • EnumMap<Ensemble, String> map = new ..

ITEM32 : 비트 필드(bit field) 대신 EnumSet을 사용하라

  • 열거 자료형 원소들을 집합에 사용할때 C 스타일로 int eunm 패턴에 2의 거듭제곱 값을 대입해서 사용했다
1 public class Text{ 2     public static final int STYLE_BOLD = 1 << 0; //1 3     public static final int STYLE_ITALIC = 1 << 1; // 2 4     ... 5     public void applyStyle(int styles){ ..} 6 } 7 text.appleStyles(STYLE_BOLD | STYLE_ITALIC); // 원소들을 집합으로 사용하기 위해서 | 비트 연산을 이용
  • 집합을 비트 필드로 나타내면 비트 단위 산술 연산을 통해 합집합 교집합 등의 집합 연산도 효울적으로 실행할수 있다. 하지만 여러 단점이 존재함!!
    • 상수를 출력해도 이해하기가 어렵다.
    • 비트 필드에 포함된 모든 요소를 순차적으로 살펴보기도 어렵다.
  • 대신에 java.util.EnumSet 을 사용하면 된다.
    • Set 인터페이스을 구현해서 Set 이 제공하는 풍부한 기능을 그대로 사용할수 있다.
    • 형 안정성, 상호운영성(다른 구조의 Set도 EnumSet으로 바꿀수 있다.) 을 제공함!
    • EnumSet 내부 구현 자체는 bit vector 로 되어있다. enum 값 갯수가 64개 이하이면 long 값 하나만 사용할수 있다.
 1 public class Text {  2     public enum Style {  3         BOLD, ITALIC, UNDERLINE, STRIKETHROUGH  4     }  5     // EnumSet 이 아니라 Set 으로 사용가능하게 했다.   6     // 인터페이스를 자료형으로 쓰므로 인해서 상호 운영성을 얻을수 있다.  7     public void applyStyles(Set<Style> styles) {  8         // Body goes here  9     } 10  11     public static void main(String[] args) { 12         Text text = new Text(); 13         // EnumSet 사용! 14         text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC)); 15     } 16 }
  • 열거 자료형을 집합에 사용해야 한다고 해서 비트 필드로 구현하지마라!!!
  • 자바1.6 기준으로 EnumSet 은 변경 불가능한 객체를 만들수 없다.
    • 이후 변경될것이다.
    • Java 1.7 에서도 불가능하다. Guava 를 사용하면 가능하다

ITEM33 : ordinal을 배열 첨자로 사용하는 대신 EnumMap을 이용하라

  • ordinal 을 사용해 여러 자바 자료구조와 Enum 형을 맵핑 시키는 일은 하면 안된다.
    • 형 안정성을 제공하지 못하고, 틀린값을 쓰게 되면 이상한 짓을 하게 되거나 해당 Index 를 넘어갈경우 ArrayIndexOutOfBoundsExeception 을 만들수도 있다.
  • java.util.EnumMap 을 사용하여 enum 상수를 키로 사용하는 맵을 만들자!
 1 public class Herb {  2     public enum Type {  3         ANNUAL, PERENNIAL, BIENNIAL  4     }  5   6     private final String name;  7     private final Type type;  8   9     Herb(String name, Type type) { 10         this.name = name; 11         this.type = type; 12     } 13  14     @Override 15     public String toString() { 16         return name; 17     } 18  19     public static void main(String[] args) { 20         Herb[] garden = { new Herb("Basil", Type.ANNUAL), 21                 new Herb("Carroway", Type.BIENNIAL), 22                 new Herb("Dill", Type.ANNUAL), 23                 new Herb("Lavendar", Type.PERENNIAL), 24                 new Herb("Parsley", Type.BIENNIAL), 25                 new Herb("Rosemary", Type.PERENNIAL) }; 26  27         // Enum 형 Herb.Type 을 키로 사용!! 28         Map<Herb.Type, Set<Herb>> herbsByType = new EnumMap<Herb.Type, Set<Herb>>( 29                 Herb.Type.class); 30         for (Herb.Type t : Herb.Type.values()) 31             herbsByType.put(t, new HashSet<Herb>()); 32         for (Herb h : garden) 33             herbsByType.get(h.type).add(h); 34         System.out.println(herbsByType); 35     } 36 }
  • 내부적으로는 ordinal 을 사용한다. 명시적으로 ordinal 을 사용하는것과 성능상에 크게 차이가 없다.
  • new EnumMap<Herb.Type, Set<Herb>>( Herb.Type.class)
    • 생성자에 키의 자료형을 나타내는 Class 객체를 인자로 받는다!.
    • Class 객체를 한정적 자료형 토큰으로 사용해서 실행 시점에서 제네릭 자료형 정보를 제공한다.
      • EnumMap 은 내부적으로 new Object[length] 형식으로 되어있다. 타입을 체크하고 리턴하기 위해서는 타입정보를 알아야한다.
      • EnumSet 은 static factory 로 생성되서 타입 정보를 넘기지 않는거 같지만 내부적으로e.getDeclaringClass() 를 가져와서 타입정보를 가져온다. 처음 생성시에 타입을 넘기므로 EnumMap 처럼 생성할때 타입정보를 넘기지 않아도 된다.
  • 액체 고체 기체 변화를 나타내는 Enum 형
    • 표현해야 하는 관계가 다차원적이라면 EnumMap<… , EnumMap<…>> 과 같이 사용하면 된다.
 1 public enum Phase {  2     SOLID, LIQUID, GAS;  3   4     public enum Transition {  5         MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID), BOIL(LIQUID, GAS), CONDENSE(  6                 GAS, LIQUID), SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID);  7   8         private final Phase src;  9         private final Phase dst; 10  11         Transition(Phase src, Phase dst) { 12             this.src = src; 13             this.dst = dst; 14         } 15  16         // 변경 테이블을 맵으로 만든다. 17         private static final Map<Phase, Map<Phase, Transition>> m = new EnumMap<Phase, Map<Phase, Transition>>( 18                 Phase.class); 19         static { 20             // 가능한 변화를 초기화 21             for (Phase p : Phase.values()) 22                 m.put(p, new EnumMap<Phase, Transition>(Phase.class)); 23             for (Transition trans : Transition.values()) 24                 m.get(trans.src).put(trans.dst, trans); 25         } 26  27         public static Transition from(Phase src, Phase dst) { 28             return m.get(src).get(dst); 29         } 30     } 31  32     public static void main(String[] args) { 33         for (Phase src : Phase.values()) 34             for (Phase dst : Phase.values()) 35                 if (src != dst) 36                     System.out.printf("%s to %s : %s %n", src, dst, 37                             Transition.from(src, dst)); 38     } 39 }

ITEM34 : 확장 가능한 enum을 만들어야 한다면 인터페이스를 이용하라

  • ENUM 형은 기본적으로 계승이 안된다!!
    • 계승이 되는거처럼 흉내 내려면 인터페이스를 만들어서 같은 인터페이스를 implements 할수 있다.
 1 public interface Operation {  2     double apply(double x, double y);  3 }  4 public enum BasicOperation implements Operation {  5     PLUS("+") {  6         public double apply(double x, double y) {  7             return x + y;  8         }  9     }, 10     MINUS("-") { 11         public double apply(double x, double y) { 12             return x - y; 13         } 14     }, 15     TIMES("*") { 16         public double apply(double x, double y) { 17             return x * y; 18         } 19     }, 20     DIVIDE("/") { 21         public double apply(double x, double y) { 22             return x / y; 23         } 24     }; 25     private final String symbol; 26  27     BasicOperation(String symbol) { 28         this.symbol = symbol; 29     } 30  31     @Override 32     public String toString() { 33         return symbol; 34     } 35 }
 1 // BasicOperation 처럼 Operation 구현  2 public enum ExtendedOperation implements Operation {  3     EXP("^") {  4         public double apply(double x, double y) {  5             return Math.pow(x, y);  6         }  7     },  8     REMAINDER("%") {  9         public double apply(double x, double y) { 10             return x % y; 11         } 12     }; 13  14     private final String symbol; 15  16     ExtendedOperation(String symbol) { 17         this.symbol = symbol; 18     } 19  20     @Override 21     public String toString() { 22         return symbol; 23     } 24  25     public static void main(String[] args) { 26         double x = Double.parseDouble(args[0]); 27         double y = Double.parseDouble(args[1]); 28         test(ExtendedOperation.class, x, y); 29  30         System.out.println();  31         test2(Arrays.asList(ExtendedOperation.values()), x, y); 32     } 33  34     // Enum 형을 테스트 하기 위해 한정적 자료형 토큰을 넘기는 방법  35     // Class 에 getEnumConstants 를 사용해 가능한 Operation 을 가져올수 있다. 36     private static <T extends Enum<T> & Operation> void test(Class<T> opSet, 37             double x, double y) { 38         for (Operation op : opSet.getEnumConstants()) 39             System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y)); 40     } 41  42     // Enum 형을 테스트 하기 위해  한정적 와일드 카드 자료형을 사용 43     private static void test2(Collection<? extends Operation> opSet, double x, 44             double y) { 45         for (Operation op : opSet) 46             System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y)); 47     } 48 }
  • _<T extends Enum & Operation> void test(Class opSet, double x, double y)_
    • 자료형의 class 리터럴을 사용, class 리터럴은 한정적 자료형 토큰 역활을 한다.
    • <T extends Enum<T> & Operation> : Class 객체가 나타내는 자료형이 enum 자료형인 동시에 Opertion 하위 자료형이 되어야한다. (Enum 자체는 상속이 안되고 같은 interface 를 상속하는 방식이므로 이런 한정적 방법이 필요함)
  • void test2(Collection<? extends Operation> opSet, double x, double y)
    • EnumSet 이나 EnumMap 을 사용할수 있는 유연성이 가능하다.
    • 하지만 한정적 와일드 카드 자료형 특성상 특정 operation을 직접 수행할수는 없다., 실제로 연산을 수행할수 있으르면 한정적 자료형 토큰으로 사용하자!

ITEM35 : 작명 패턴 대신 어노테이션을 사용하라

  • 자바 1.5 이전에는(어노테이션이 없을때) 도구나 프레임워크가 특별히 취급해야 되면 작명패턴을 사용했다
    • JUnit 은 테스트 이름을 test 로 시작하게 했다.
    • 이름을 잘못 입력하면 문제가 발생하거나 무시해서 문제가 생김..
    • 프로그램 요소에 인자를 전달할 방법이없다.
  • 주석 타입 정의
 1 import java.lang.annotation.ElementType;  2 import java.lang.annotation.Retention;  3 import java.lang.annotation.RetentionPolicy;  4 import java.lang.annotation.Target;  5   6 // 어노테이션 사용  7 // 메타 주석  8 @Retention(RetentionPolicy.RUNTIME) // Test 주석이 런타임시에 존속되어야 한다  9 @Target(ElementType.METHOD) // 메소드 선언시에만 적합하다. 10 public @interface Test { 11 }
  • 메타 주석 : 주석 타입 선언에 나온 주석
  • 특정 예외를 발생 시키는 경우에 한해서 테스트가 성공하도록 만들기
 1 @Retention(RetentionPolicy.RUNTIME)  2 @Target(ElementType.METHOD)   3 public @interface ExceptionTest {  4     Class<? extends Exception> value(); // 한개의 타입만 받는다.  5     Class<? extends Exception>[] value(); // 여러개의 타입을 받는다.  6     /*  7     Class<? extends Exception>[] excTypes = m.getAnnotation(  8                             ExceptionTest.class).value(); // 처럼 사용  9     */ 10 }
  • Class<? extends Exception>
    • Exception 예외 클래스에서 상속받은 어떤 클래스의 class 객체
    • 이주석 사용자가 어떤 예외 타입을 지정해도 된다!(바운드 타입 토큰)
 1 public class Sample2 {  2     // 매개변수로 클래스 리터럴을 받는 주석!  3     @ExceptionTest(ArithmeticException.class)  4     public static void m1() {  5         // 예외 발생하므로 성공하는 테스트  6         int i = 0;  7         i = i / i;  8     }  9  10     @ExceptionTest(ArithmeticException.class) 11     public static void m2() {  12         // 잘못된 Exception 으로 실패하는 테스트 13         int[] a = new int[0]; 14         int i = a[1]; 15     } 16  17     @ExceptionTest(ArithmeticException.class) 18     public static void m3() { 19     } // 예외가 발생 안하므로 실패하는 테스트 20  21     @ExceptionTest({ IndexOutOfBoundsException.class, 22             NullPointerException.class }) 23     public static void doublyBad() { 24         List<String> list = new ArrayList<String>(); 25  26         // IndexOutOfBoundsException or NullPointerException 에러 발생 27         list.addAll(5, null); 28     } 29 }
  • Test Annotation 처리
 1 public class RunTests {  2     public static void main(String[] args) throws Exception {  3         int tests = 0;  4         int passed = 0;  5         Class testClass = Class.forName(args[0]);  6         for (Method m : testClass.getDeclaredMethods()) {  7             // Test Annotation 이 적용된것만 테스트!  8             if (m.isAnnotationPresent(Test.class)) {  9                 tests++; 10                 try { 11                     m.invoke(null); 12                     passed++; 13                 } catch (InvocationTargetException wrappedExc) { 14                     Throwable exc = wrappedExc.getCause(); 15                     System.out.println(m + " failed: " + exc); 16                 } catch (Exception exc) { 17                     System.out.println("INVALID @Test: " + m); 18                 } 19             } 20              21             // 만약에 Exception Annotation 이라면 22             if (m.isAnnotationPresent(ExceptionTest.class)) { 23                 tests++; 24                 try { 25                     m.invoke(null); 26                     System.out.printf("Test %s failed: no exception%n", m); 27                 } catch (Throwable wrappedExc) { 28                     Throwable exc = wrappedExc.getCause(); 29                      30                     // 가능한 예외 목록을 처리 31                     Class<? extends Exception>[] excTypes = m.getAnnotation( 32                             ExceptionTest.class).value(); 33                     int oldPassed = passed; 34                     // 여러개의 타입을 받는것을 처리 35                     for (Class<? extends Exception> excType : excTypes) { 36                         if (excType.isInstance(exc)) { 37                             passed++; 38                             break; 39                         } 40                     } 41                     if (passed == oldPassed) 42                         System.out.printf("Test %s failed: %s %n", m, exc); 43                 } 44             } 45         } 46         System.out.printf("Passed: %d, Failed: %d%n", passed, tests - passed); 47     } 48 }

ITEM36 : 어노테이션은 일관되게 사용하라

 1 public class Bigram {  2     private final char first;  3     private final char second;  4   5     public Bigram(char first, char second) {  6         this.first = first;  7         this.second = second;  8     }  9  10     public boolean equals(Bigram b) { 11         return b.first == first && b.second == second; 12     } 13  14     public int hashCode() { 15         return 31 * first + second; 16     } 17  18     public static void main(String[] args) { 19         Set<Bigram> s = new HashSet<Bigram>(); 20         for (int i = 0; i < 10; i++) 21             for (char ch = 'a'; ch <= 'z'; ch++) 22                 s.add(new Bigram(ch, ch)); 23         System.out.println(s.size()); 24     } 25 }
  • 26 개의 바이그램을 10번 반복해서 Set 에 추가함! 정상적이라면 26 을 출력해야 한다! 하지만 모든 오브젝트가 다르게 인식되어서 260개가 출력됨
    • Bigram 클래스 개발자는 equals 를 오버라이딩을 하려 했다 하지만 eqauls(Object b) 가 되어야 하는데 잘못 오버라이딩 해주었다.
    • @Override 주석을 eauls 함수에 넣어주면 컴파일 에러가 발생해서 실수를 방지할수 있다.

ITEM37 : 자료형을 정의할 때 표식 인터페이스를 사용하라

  • 표시 인터페이스(marker interface)는 메소드 선언은 전혀없으면서 클래스가 그 인터페이스를 구현하는지만 나타내는(표시하는) 인터페이스이다.
    • ex) Serializable
  • 표시 인터페이스는 주석에 비해 2가지 장점이 있다.
    • 표시된 클래스의 인스턴스에 의해 구현되는 타입을 정의한다. 타입이 있기때문에 주석이 런타입에서 에러를 잡을수 있을것을 컴파일 시점에서 할수 있다.
      • Serialzable 의 ObjectOutputStream.write(Object) 함수는 Serialzable 을 메소드 인자 타입으로 이용하지 않고 Object 를 이용했다. 따라서 컴파일 시점이 아닌 런타임에서 오류가 나도록 잘못 설계가 되어있다.
    • 더 정확한 목표를 가실수 있다는 장점이있다. 주석타입이 어떤 목표로 선언된다면 그 주석은 어떤 클래스나 인터페이스에도 적용할수 있게 된다. 표시 인터페이스를 정의하면 그것을 적용 가능한 인터페이스로 확장할수 있고 모든 표시 타입들도 그적용 가능한 인터페이스의 서브 타입이 되도록 할수 있다.
  • Set 인터페이스는 제한적 표시 인터페이스 이다. ___
  • 주석이 표시 인터페이스에 비해 갖는 장점
    • 하나 이상의 주석 타입 요소들을 추가함으로써 더 많은 정보를 주석타입에 추가할수 있다!
  • 클래스나 인터페이스가 아닌 다른 포로그램 요소에 적용할 때는 주석을 사용해야 한다.
  • 클래스와 인터페이스에만 적용하되 표시자를 갖는 개체만을 인자로 받는 메소드를 하나 이상작성하지 않는다면 그표시자를 영원히 특정 인터페이스의 요소에만 제한해서 사용할 것인지 고려해 봐야한다. 그렇다면 인터페이스의 서브 인터페이스로 표시인터페이스를 정의하는 것이 좋다
  • 정의할 타입과 연관된 어떤 새로운 메소드도 갖지 않는 타입을 정의하고자 한다면 표시 인터페이스
  • 클래스와 인터페이스가 아닌 다른 프로그램 요소를 표시한다면, 향후에 더많은 정볼르 표시자에 추가할 가능성이 있다면 표시 주석!

 

=================================

=================================

=================================

 

 

반응형


관련글 더보기

댓글 영역