Book/자바의 정석

[자바의 정석] Chapter 12. 제네릭, 열거형, 어노테이션

yo0oni 2023. 7. 14. 12:10

제네릭

제네릭은 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능이다.

  • 타입 안정성을 제공
  • 타입체크와 형변환을 생략할 수 있어 코드가 간결

간단히 정리하자면, 다룰 객체의 타입을 미리 명시해 줌으로써 번거로운 형변환을 줄일 수 있다.

class Box<T> {
    T item;

    void setItem(T item) { this.item = item; |
    T getItem() { return iterm; }
}

Box <T>에서 T를 '타입 변수'라고 한다. 물론 T가 아니라 상황에 맞게 다른 변수로 작성해도 된다.
f(x, y) = x + y와 f(k, v) = k + v 가 다르지 않은 것처럼 타입 변수명도 상관없다.

 

제네릭을 실제로 사용하기 위해선 아래처럼 코드를 작성해 주면 된다.

Box<String> b = new Box<String>();    // 타입 T 대신, 실제 타입을 지정
b.setItem(new Object());            // Error. String 이외의 타입은 지정 불가
b.setItem("ABC");                    // OK. String 타입이므로 가능
String item = b.getItem();            // 형변환이 필요없음

저렇게 작성하면 제네릭 클래스 Box <T>는

class Box<String> {
    String item;

    void setItem(String item) { this.item = item; |
    String getItem() { return iterm; }
}

위와 같이 정의된 것이랑 같다.

 

모든 객체에 대해 동일하게 동작해야 하는 static 멤버에는 타입 변수 T를 사용할 수 없다. T는 인스턴스 변수로 간주되기 때문이다.

그리고 제네릭은 new 연산자 때문에 배열을 생성할 수 없다.
왜냐하면 new 연산자는 컴파일 시점에 타입 T가 뭔지 정확히 알아야 한다. 그런데 Box T 클래스는 컴파일하는 시점에서 T가 어떤 타입이 될지 전혀 알 수 없다.
이러한 이유로 instansof 연산자도 T를 피연산자로 사용할 수 없다.

 

제네릭 클래스의 객체 생성과 사용

  1. 참조변수와 생성자에 대입된 타입이 일치해야 한다.
  2. Box <Apple> appleBox = new Box<Apple>(); // OK Box<Apple> appleBox = new Box <Grape>(); // 에러에러
  3. 두 타입이 상속관계에 있어도 대입된 타입이 일치해야 한다.
    Apple이 Fruit의 자손이라고 가정하자
  4. Box <Fruit> appleBox = new Box <Apple>(); // 에러에러

//선언할 때만 에러가 뜨는 것이고, 객체를 추가할 때는 괜찮다.
Box fruitBox = new Box();
fruitBox.add(new Fruit());
fruitBox.add(new Apple()); // 이건 OKOK

3. 두 제네릭 클래스의 타입이 상속관계에 있고 대입된 타입이 같으면 OK
FruitBox가 Box의 자손이라고 가정하자
```java
Box<Apple> appleBox = new FruitBox<Apple>();    // OK

 

열거형

요소, 멤버라 불리는 명명된 값의 집합을 이루는 자료형이다. 열거자 이름들은 일반적으로 해당 언어의 상수 역할을 하는 식별자이다.

class Card {
    enum Kind { CLOVER, HEART, DIAMOND, SPADE }
    enum Value { TWO, THREE, FOUR }

    final Kind kind;
    final Value value;
}
if(Card.CLOVER == Card.TWO) // true지만 false이어야 의미상 맞음
if(Card.Kind.CLOVER==Card.Value.TWO) // false

 

열거형의 정의와 사용

열거형을 정의하는 방법은 간단하다.

enum 열거형 이름 { 상수명1, 상수명2 }

// 동서남북
enum Direction { EAST, SOUTH, WEST, NORTH }

열거형에 정의된 상수를 사용하는 방법은 열거형이름. 상수명

Direction.EAST

열거형들의 값의 일치 여부를 비교할 때는 == 를 이용하지만 대소비교는 compareTo()를 이용한다.

 

열거형에 멤버 추가하기

Enum 클래스에 정의된 ordinal()이 열거형 상수가 정의된 순서를 반환하지만, 이 값을 열거형 상수의 값으로 사용하지 않는 것이 좋다.

열거형 상수의 값이 불규칙적인 경우에는

enum Direction { EAST(1), SOUTH(5), WEST(-1), NORTH(10) }

이와 같이 열거형 상수의 이름 옆에 원하는 값을 괄호와 함께 적어주면 된다.

 

열거형의 이해부터

// 열거형
enum Direction { EAST, SOUTH, WEST, NORTH }

// 클래스
class Direction {
    static final Direction EAST = new Direction("EAST");
    static final Direction EAST = new Direction("SOUTH");
    static final Direction EAST = new Direction("WEST");
    static final Direction EAST = new Direction("NORTH");

    private String name;

    private Direction(String name) {
        this.name = name;
    }
}

 

어노테이션 (annotation)

어노테이션은 메타 데이터로서, 프로그램 그 자체의 일부분은 아니지만 프로그램에 대한 데이터를 제공한다. 그래서 어노테이션 자체는 어노테이션을 붙은 코드 동작에 영향을 미치지는 않는다. 참고로 어노테이션은 다음과 같은 상황에 사용된다.

  • 컴파일러에게 필요한 정보를 제공
    컴파일러가 에러를 감지하거나, 경고를 띄우지 않게 하기 위함.
  • 컴파일/배포 시에 필요한 처리 기능
    SW 개발 툴에서 어노테이션의 정보를 통해 특정 코드를 추가할 수 있음.
  • 런타임 처리 제공
    런타임에도 어노테이션의 정보를 통해 필요한 처리를 할 수 있음. (Java Reflection)