지네릭스, 열거형, 애너테이션

1. 지네릭스(Generics)

1.1 지네릭스란?

  • 지네릭스는 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크(compile-time type check)를 해주는 기능이다. 객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어든다.
  • 타입 안정성을 높인다는 것은 의도하지 않은 타입의 객체가 저장되는 것을 막고, 저장된 객체를 꺼내올 때 원래의 타입과 다른 타입으로 잘못 형변환되어 발생할 수 있는 오류를 줄여준다는 뜻이다.

    지네릭스의 장점

    1. 타입 안정성을 제공한다.
    2. 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해 진다.

1.2 지네릭 클래스의 선언

class Box<T>{ // 지네릭 타입 T를 선언
    T item;
    
    void setItem(T item) { this.item = item; }
    T getItem() { return item; }
}
  • 박스< T >에서 T는 '타입 변수(type variable)'라고 하며, 'Type'의 첫 글자에서 따온 것이다. 타입 변수는 T가 아닌 다른 것을 사용해도 된다. ArrayList< E >의 경우, 타입 변수 E는 'Element(요소)'의 첫 글자를 따서 사용했다.
  • 타입 변수가 여러 개인 경우에는 Map<K,V>와 같이 콤마','를 나열하면 된다. K는 Key(키)를 의미하고, V는 Value(값)을 의미한다.
  • 무조건 'T'를 사용하기보다 가능하면, 이처럼 상황에 맞게 의미있는 문자를 선택해서 사용하는 것이 좋다.
  • 이들은 기호의 종류만 다를 뿐 '임의의 참조형 타입'을 의미한다는 것은 모두 같다.
  • 이제 지네릭 클래스가 된 Box클래스의 객체를 생성할 때에는 다음과 같이 참조변수와 생성자에 타입 T대신에 사용될 실제 타입을 지정해주어야 한다.
Box<String> b = new Box<String>(); // 타입 T대신, 실제 타입을 지정
b.setItem(new Object); // 에러. 실제 String이외의 타입은 지정불가
b.setItem("ABC")
String item = b.getItem(); // 형변환이 필요없음

지네릭스의 용어

class Box< T > {}

  • Box< T > : 지네릭 클래스. 'T의 Box' 또는 'T Box'라고 읽는다.
  • T : 타입 변수 또는 타입 매개변수.(T는 타입문자)
  • Box : 원시 타입(raw type)
  • 아래와 같이 매개변수에 타입을 지정하는 것을 '지네릭 타입 호출'이라고 하고, 지정된 타입 'String'을 '매개변수화된 타입(parameterized type)'이라고 한다.
Box<String> b = new Box<String> ();

지네릭스의 제한

  • 지네릭 클래스의 Box의 객체를 생성할 때, 객체별로 다른 타입을 지정하는 것은 적절하다.
Box<Apple> appleBox = new Box<Apple>();
Box<Grape> grapeBox = new Box<Grape>();
  • 그러나 모든 객체에 대해 동일하게 동작해야 하는 static멤버에 타입 변수 T를 사용할 수 없다. T는 인스턴스변수로 간주되기 때문이다.
  • 또한, 지네릭 타입은 배열을 생성하는 것도 허용하지 않는다.

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

class Box<T>{
    ArrayList<T> list = new ArrayList<T> ();
    
    void add(T item) {list.add(item);}
    T get(int i) {return list.get(i);}
    ArrayList<T> getList() {return list;}
    int size() {return list.size();}
    public String toString() {return list.toString();}
}

Box<Apple> appleBox = new Box<Apple>(); // ok
Box<Fruit> appleBox = new Box<apple>(); // 상속관계도 안됨 XXXX
Box<Apple> appleBox = new Box<>(); // ok. 생략가능
appleBox.add(new Apple()); // ok
appleBox.add(new Grape()); // Apple객체만 추가가능 XXXX

1.4 제한된 지네릭 클래스

  • 타입 문자로 사용할 타입을 명시하면 한 종류의 타입만 저장할 수 있도록 제한할 수 있지만, 그래도 여전히 모든 종류의 타입을 지정할 수 있다는 것에는 변함이 없다.
  • 다음과 같이 지네릭 타입에 'extends'를 사용하면, 특정 타입의 자손들만 대입할 수 있게 제한할 수 있다.
class FruitBox<T extends Fruit>{ // Fruit의 자손만 타입으로 지정가능
    ArrayList<T> list = new ArrayList<T>();
    ...
}
  • 만일 클래스가 아니라 인터페이스를 구현해야 한다는 제약이 필요하다면, 이때도 'extends'를 사용한다.
interface Eatable {}
class FtuitBox<T extends Eatable> { ... }
  • 클래스 Fruir의 자손이면서 Eatable인터페이스도 구현해야 한다면 아래와 같이 '&'기호로 연결한다.
class FruitBox<T extends Fruit & Eatable>

1.5 와일드 카드

  • < ? extends T > : 와일드 카드의 상한 제한. T와 그 자손들만 가능
  • < ? super T> : 와일드 카드의 하한 제한. T와 그 조상들만 가능
  • < ? > : 제한 없음. 모든 타입이 가능<? extends Object>와 동일
  • 매개변수 타입이 Comparator<? super Apple>이라는 의미는 Comparator의 타입 매개변수로 Apple과 그 조상이 가능하다는 뜻이다.
  • Comparator<? super Apple> : Comparator< Apple >, Comparator< Fruit >, Comparator< Object >
  • Comparator<? super Grape> : Comparator< Grape >, Comparator< Fruit >, Comparator< Object >
  • 이러한 장점 때문에 Comparator에는 항상 <? super T>가 습관적으로 따라 붙는다. 와일드 카드 때문에 Comparator를 어려워하는 경우가 많은데, 그럴 때는 그냥 와일드 카드를 무시하고 Comparator라고 생각하는게 낫다.

1.6 지네릭 메서드

  • 메서드의 선언부에 지네릭 타입이 선언된 메서드를 지네릭 메서드라 한다. 앞서 살펴본 것처럼, Collections.sort()가 바로 지네릭 메서드이며, 지네릭 타입의 선언 위치는 반환타입 바로 앞이다.
static <T> void sort(List<T> list, Comparator<? super T> c)
  • 지네릭 클래스에 정의된 타입 매개변수와 지네릭 메서드에 정의된 타입 매개변수는 전혀 별개의 것이다. 같은 타입 문자 T를 사용해도 같은 것이 아니라.

    지네릭 메서드는 지네릭 클래스가 아닌 클래스에도 정의할 수 있다.

class FruitBox<T>{
    ...
    static<T> void sort(List<T> list, Comparator<? super T> c){
        ....
    }
}
  • 위의 코드에서 지네릭 클래스 FruitBox에 선언된 타입 매개변수 T와 지네릭 메서드 sort()에 선언된 타입 매개변수 T는 타입 문자만 같을 뿐 서로 다른 것이다.
// 변경 전
static Juice makeJuice(FruitBox<? extends Fruit> box){
    String tmp = "";
    for(Fruit f : box.getList()) tmp += f + " ";
    return new Juice(tmp);
}
// 변경 후
static <T extends Fruit> Juice makeJuice(FruitBox<T> box){
    String tmp = "";
    for (Fruit f : box.getList()) tmp += f + " ";
    return new Juice(tmp);
}

// 호출시
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
    ....
System.out.println(Juicer.<Fruit>makeJuice(fruitBox));
System.out.println(Juicer.<Apple>makeJuice(appleBox));

1.8 지네릭 타입의 제거

  • 컴파일러는 지네릭 타입을 이용해서 소스파일을 체크하고, 필요한 곳에 형변환을 넣어준다. 그리고 지네릭 타입을 제거한다. 즉, 컴파일된 파일(*.class)에는 지네릭 타입에 대한 정보가 없는 것이다.
  • 기본적인 제거 과정
    1. 지네릭 타입의 경계(bound)를 제거한다.
    2. 지네릭 타입을 제거한 후에 타입이 일치하지 않으면, 형변환을 추가한다.

2. 열거형(enums)

2.1 열거형이란?

  • 열거형은 서로 관련된 상수를 편리하게 선언하기 위한 것으로 여러 상수를 정의할 때 사용하면 유용하다.
class Card{
    enum Kind {CLOVER, HEART, DIAMOND, SPADE} // 열거형 Kind를 정의
    enum Value {TWO, THREE, FOUR} // 열거형 Value를 정의

    final Kind kind; // 타입이 int가 아닌 Kind임
    final Value value;
}

2.2 열거형의 정의와 사용

  • 열거형을 정의하는 방법은 다음과 같이 괄호{}안에 상수의 이름을 나열하기만 하면 된다.

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

  • 열거형 상수간의 비교에는 '=='를 사용할 수 있다. 또한 비교연산자는 사용할 수 없고 compareTo()는 사용가능하다.

2.3 열거형에 멤버 추가하기

  • Enum클래스에 정의된 ordinal()이 열거형 상수가 정의된 순서를 반환하지만, 이 값을 열거형 상수의 값으로 사용하지 않는 것이 좋다. 이 값은 내부적인 용도로만 사용되기 위한 것이기 때문이다.
  • 열거형 상수의 값이 불연속적인 경우에는 이때는 다음과 같이 열거형 상수의 이름 옆에 원하는 값을 괄호()와 함께 적어주면 된다.
// enum Direction {EAST(1), SOUTH(5), WEST(-1), NORTH(10)}

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

    private final int value; // 정수를 저장할 필드를 추가
    Direction(int value) {this.value = value;} // 생성자 추가, 제어가자 묵시적으로 private
    
    public int getValue() {return value;}
}
  • 열거형의 인스턴스 변수는 반드시 final이어야 한다는 제약은 없지만, value는 열거형 상수의 값을 저장하기 위한 것이므로 final을 붙였다.
  • 필요하다면, 다음과 같이 하나의 열거형 상수에 여러 값을 지정할 수도 있다.

enum Direction {
    EAST(1,">"), SOUTH(5, "V"), WEST(-1, "<"), NORTH(10, "^");

    private final int value; 
    private final String symbol;

    Direction(int value, String symbol) {
        this.value = value;
        this.symbol = symbol;
    }
    
    public int getValue() {return value;}
    public String getSymbol() {return symbol;}
}

#### 열거형에 추상 메서드 추가하기
```java

enum Transfortation{
    BUS(100) {
        int fare(int distance) { return distance*BASIC_FARE; }
    },
    TRAIN(150) {
        int fare(int distance) { return distance*BASIC_FARE; }
    },
    SHIP(100) {
        int fare(int distance) { return distance*BASIC_FARE; }
    },
    AIRPLANE(300) {
        int fare(int distance) { return distance*BASIC_FARE; }
    };
    
    abstract int fare (int distance);

    protected final int BASIC_FARE;

    Transportation(int basicFaer){
        BASIC_FARE = basicFare;
    }
    piblic int getBasicFare(); {return BASIC_FARE;}
}

3. 애너테이션(annotation)

3.1 애너테이션이란?

  • 프로그램의 소스코드 안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨 것이 바로 애너테이션이다.
  • 애너테이션은 주석(comment)처럼 프로그래밍 언어에 영향을 미치지 앟으면서도 다른 프로그램에게 유용한 정보를 제공할 수 있다는 장점이 있다.

애너테이션(annotation)의 뜻은 주석, 주해, 메모이다.

  • JDK에서 제공하는 표준 애너테이션은 주로 컴파일러를 위한 것으로 컴파일러에게 유용한 정보를 제공한다. 그리고 새로운 애너테이션을 정의할 때 사용하는 메타 애너테이션을 제공한다.

3.2 표준 애너테이션

  • 자바에서 기본적으로 제공하는 애너테이션들은 몇 개 없다. 그나마 이들의 일부는 '메타 애너테이션(meta annotation)'으로 애너테이션을 정의하는데 사용되는 애너테이션의 애너테이션이다.

@Override

  • 메서드 앞에만 붙일 수 있는 애너테이션으로, 조상의 메서드를 오버라이딩하는 것이라는걸 컴파일러에게 알려주는 역할을 한다.
  • 오버라이딩할 때 메서드 앞에 '@Override'를 붙이는 것은 필수는 아니지만, 알아내기 어려운 실수를 미연에 방지해주므로 반드시 붙이는 것이 좋다.

@Deprecated

  • 더 이상 사용되지 않는 필드나 메서드에 붙인다.
  • compile은 에러없이 되나 메세지가 나온다.

@FunctionalInterface

  • 컴파일러가 '함수형 인터페이스'를 올바르게 선언했는지 확인하고, 잘못된 경우 에러를 발생시킨다.
  • 필수는 아니지만, 붙이면 실수를 방지할 수 있으므로 '함수형 인터페이스'를 선언할 때는 이 애너테이션을 붙이는 것이 좋다.

@SuppressWarnings

  • 컴파일러가 보여주는 경고메세지가 나타나지 않게 해준다.
    1. deprecation : @Deprecated가 붙은 대상을 사용해서 발생하는 경고를 억제할 때 사용
    2. unchecked : 지네릭스로 타입을 지정하지 않았을 때 발생하는 경고를 억제할 때 사용
    3. rawtypes : 지네릭스를 사용하지 않아서 발생하는 경고를 억제할 때 사용
    4. varargs : 가변인자의 타입이 지네릭 타입일 때 발생하는 경고를 억제할 때 사용
// 둘이상을 사용할 경우
@SuppressWarnings({"deprecation", "unchecked", "varargs"})

@SafeVarargs

  • 메서드에 선언된 가변인자의 타입이 non-reifiable타입일 경우, 해당 메서드를 선언하는 부분과 호출하는 부분에서 "unchecked"경고가 발생한다. 해당 코드에 문제가 없다면 이 경고를 억제하기 위해 '@SafeVarargs'를 사용해야 한다.
  • 지네릭스에서 살펴본 것과 같이 어떤 타입들은 컴파일 이후에 제거된다. 컴파일 후에도 제거되지 않은 타입을 reifiable타입이라 하고, 제거되는 타입을 non-reifiable타입이라고 한다. 지네릭 타입들은 대부분 컴파일 시에 제거되므로 non-reifiable타입니다.

3.3 메타 애너테이션

  • 앞서 설명한 것과 같이 매타 애너테이션은 '애너테이션을 위한 애너테이션', 즉 애너테이션을 붙이는 애너테이션으로 애너테이션을 정의할 때 애너테이션의 적용대상(target)이나 유지기간(retention)등을 지정하는데 사용된다.

@Target

  • 애너테이션을 적용가능한 대상을 지정하는데 사용된다.
  • 아래는 '@SuppressWarnings'를 정의한 것인데, 이 애너테이션을 적용할 수 있는 대상을 '@Target'으로 지정하였다. 앞서 언급한 것과 같이 여러 개의 값을 지정할 때는 배열에서처럼 괄호{}를 사용해야 한다.
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings{
    String[] value();
}

@Retention

  • 애너테이션이 유지되는 기간을 지정하는데 사용된다.

@Documented

  • 애너테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 한다.

@Inherited

  • 애너테이션이 자손 클래스에 상속되도록 한다.

@Repeatable

  • 보통은 하나의 대상에 한 종류의 애너테이션을 붙이는데, '@Repeatable'이 붙은 애너테이션은 여러번 붙일 수 있다.

@Native

  • 네이티브 메서드(native method)에 참조되는 '상수 필드(constant field)'에 붙이는 ㅐ너테이션이다.

3.4 애너테이션 타입 정의하기

  • '@'기호를 붙이는 것을 제외하면 인터페이스를 정의하는 것과 동일하다.
@interface 애너테이션이름 {
    타입 요소이름(); // 애너테이션의 요소를 선언한다.
    ...
}

애너테이션의 요소

  • 애너테이션 내에 선언된 메서드를 '애너테이션의 요소(element)'라고 하며, 아래에 선언된 TestInfo애너테이션은 다섯개으 ㅣ요소를 갖는다.

    애너테이션에도 인터페이스처럼 상수를 정의할 수 있지만, 디폴트 메서드는 정의할 수 없다.

@interface TestInfo(){
    int count();
    String testedBy();
    String[] testTools();
    TestType testType(); // enum Test{FIRST, FINAL}
    DateTime testDate(); // 자신이 아닌 애너테이션(@Datetime)을 포함할 수 있다.
}
@interface DateTime(){
    String yymmdd();
    String hhmmss();
}
  • 애너테이션의 요소는 반환값이 있고 매개변수는 없는 추상메서드의 형태를 가지며, 상속을 통해 구현하지 않아도 된다. 다만, 애너티에션을 적용할 때 이 요소들의 값을 빠짐없이 지정해주어야 한다. 요소의 이름도 같이 적어주므로 순서는 상관없다.
@TestInfo(
    count = 3, testedBy="Kim",
    testTools = {"JUnit", "AutoTester"},
    testType = TestType.FIRST,
    testDate = @DateTime(yymmdd="160101", hhmmss="235959")
)
public class NewClass { ... }
  • 애너테이션의 각 요소는 기본값을 가질 수 있으며, 기본값이 있는 요소는 애너테이션을 적용할 때 값을 지정하지 않으면 기본값이 사용된다.
  • 애너테이션 요소가 오직 하나뿐이고 이름이 value인 경우, 애너테이션을 적용할 떄 요소의 이름을 생략하고 값만 적어도 된다.

애너테이션 요소의 규칙

  • 애너테이션의 요소를 선언할 때 반드시 지켜야 하는 규칙은 다음과 같다.
    • 요소의 타입은 기본형, String, enum, 애너테이션, Class만 허용된다.
    • ()안에 매개변수를 선언할 수 없다.
    • 예외를 선언할 수 없다.
    • 요소를 타입 매개변수로 저으이할 수 없다.

  • 참고 : 자바의 정석 3판

좋은 웹페이지 즐겨찾기