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

|

1. 지네릭스(Generics)

  • JDK1.5에서 처음 도입

1.1 지네릭스란?

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

지네릭스의 장점

  1. 타입 안정성을 제공
  2. 타입체크와 형변환을 생략할 수 있어 코드가 간결해짐
  • 다룰 객체의 타입을 미리 명시해줌으로써 번거로운 형변환을 줄여줌

1.2 지네릭 클래스의 선언

클래스에 선언하는 지네릭 타입

class Box {
    Object item;
    
    void setItem(Object item) {this.item = item;}
    Object getItem() {return item;}
}
  • Box 클래스를 지네릭 클래스로 변경하면 클래스 옆에 <T>를 붙인 후, Object를 모두 T로 변경
class Box<T> {
    T item;
    
    void setItem(T item) {this.item = item;}
    T getItem()	{return item;}
}
  • Box<T>에서 T를 ‘타입 변수(type variable)‘라고 함

    • 타입 변수의 경우 T가 아닌 다른 것을 사용해도 됨. 상황에 맞게 의미있는 문자를 선택해서 사용하는 것이 좋음
    • 타입 변수가 여러 개인 경우 콤마를 구분자로 나열

    • 기호의 종류만 다를 뿐 임의의 참조형 타입을 의미한다는 것은 같음
  • 지네릭 클래스된 Box 클래스의 객체를 생성할 때는 참조변수와 성성자에 타입 T대신 사용될 실제 타입을 지정해주어야 함

    Box<String> b = new Box<String>();
    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)

지네릭스의 제한

  • 지네릭 클래스 Box의 객체를 생성할 때, 객체별로 다른 타입을 저정하는 것은 적절하지만, 모든 객체에 대해 동일하게 동작해야하는 static 멤버에 타입 변수 T를 사용할 수 없음
    • T는 인스터스변수로 간주되기 때문. static 멤버는 인스턴스변수 참조 불가
    • static 멤버는 타입 변수에 지정된 타입, 즉 대입된 타입의 종류에 관계없이 동일한 것이어야 하기 때문
  • 지네릭 타입의 배열을 생성하는 것은 허용되지 않음
    • 지네릭 배열 타입의 참조변수를 선언하는 것은 가능하지만, 배열을 생성하는 것은 안됨
    • new 연산자는 컴파일 시점에 타입 T가 뭔지 정확하게 알아야 하지만 컴파일하는 시점에서 T가 어떤 타입이 될지 전혀 알 수 없음
    • 지네릭 배열을 생성해야할 필요가 있는 경우, new 연산자대신 ‘Reflection API’의 newInstance()와 같이 동적으로 객체를 생성하는 메서드로 배열을 생성하거나, Object 배열을 생성해 복사한 다음 T[]로 형변환하는 방법 등을 사용

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

  • Box<>의 객체에는 한 가지 종류 (T타입)의 객체만 저장 가능
class Box<T> {
    ArrayList<T> list = new ArrayList<T>();
    
    void add(T time)		{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<Apple> appleBox = new Box<Graph>();	// 에러
    
  • 두 타입이 상속 관계에 있더라도 에러 발생

    // Apple이 Fruit의 자손이라 가정
    Box<Fruit> appleBox = new Box<Apple>();	// 에러. 대입된 타입이 다름
    
  • 두 지네릭 클래스의 타입이 상속관계에 있고, 대입된 타입이 같은 것은 괜찮음

    // FruitBox가 Box의 자손이라고 가정
    Box<Apple> appleBox = new FruitBox<Apple>();	// OK. 다형성
    
  • JDK1.7부터 추정이 가능한 경우 타입을 생략할 수 있게 됨

    • 참조변수의 타입으로부터 어떤 타입의 객체만 저장하는지를 알 수 있기 때문에, 생성자에 반복해서 타입을 지정해주지 않아도 됨
  • extends를 사용하면, 특정 타입의 자손들만 대입할 수 있게 제한할 수 있음

1.5 와일드 카드

  • 와일드 카드는 기호 ’?’로 표현하는데, 와일드 카드는 어떠한 타입도 될 수 있음

    • ’?’만으로는 Object 타입과 다를게 없으므로, extendssuper로 상한(upper bound)과 하한(lower bound)을 제한할 수 있다

      <? extends T> 와일드 카드의 상한 제한. T와 그 자손들만 가능

      <? super T> 와일드 카드의 하한 제한. T와 그 조상들만 가능

      <?> 제한 없음. 모든 타입이 가능. <? extends Object>와 동일

1.6 지네릭 메서드

// Collections.sort()
static <T> void sort(List<T> list, Comparator<? super T> c)
  • 메서드의 선언부에 지네릭 타입이 선언된 메서드
    • 지네릭 타입의 선언 위치는 반환 타입 바로 앞
  • 지네릭 클래스에 정의된 타입 매개변수와 지네릭 메서드에 정의된 타입 매개변수는 전혀 별개의 것
  • static 멤버에는 타입 매개변수를 사용할 수 없지만, 메서드에 지네릭 타입을 선언하고 사용하는 것은 가능
    • 메서드에 선언된 지네릭 타입은 지역 변수를 선언한 것과 같다고 생각하면 됨
    • 이 타입 매개변수는 메서드 내에서만 지역적으로 사용될 것이므로 메서드가 static이건 아니건 상관없음
  • 지네릭 메서드를 호출할 때, 대입된 타입을 생략할 수 없는 경우에는 참조변수나 클래스 이름을 생략할 수 없음

1.7 지네릭 타입의 형변환

  • 경고가 발생하지만, 지네릭 타입과 넌지네릭(non-generic)타입 간의 형변환은 항상 가능
  • 대입된 타입이 다른 지네릭 타입 간에는 형변환이 불가능
  • 와일드 카드가 사용된 지네릭 타입끼리도 형변환 가능하지만, 와일드 카드는 타입이 확정된 타입이 아니므로 컴파일러는 미확정 타입으로 형변환하는 것이라고 경고

1.8 지네릭 타입의 제거

  • 컴파일러는 지네릭 타입을 이용해 소스파일을 체크하고, 필요한 곳에 형변환을 넣어준 후 지네릭 타입을 제거하므로 컴파일된 파일(*.class)에는 지네릭 타입에 대한 정보가 없음

지네릭 타입 제거 과정

  1. 지네릭 타입의 경계(bound)를 제거
    • 지네릭 타입이 <T extends Fruit>라면 T는 Fruit로 치환. <T>인 경우 T는 Object로 치환됨
    • 치환된 후 클래스 옆의 선언은 제거됨
  2. 지네릭 타입을 제거한 후에 타입이 일치하ㅈ 않으면, 형변환을 추가함
    • 와일드 카드가 포함되어 있는 경우에는 적절한 타입으로의 형변환이 추가됨

2. 열거형(enums)

2.1 열거형이란?

  • 서로 관련된 상수를 편리하게 선언하기 위한 것으로 여러 상수를 정의할 때 사용하면 유용
  • 열거형이 갖는 값뿐만 아니라 타입도 관리하기 때문에 논리적인 오류를 줄일 수 있음
  • 타입에 안전한 열거형(typesafe enum)이기 때문에 실제 값이 같아도 타입이 다르면 컴파일 에러가 발생
  • 상수의 값이 바뀌면, 해당 상수를 참조하는 모든 소스를 다시 컴파일해야 했지만, 열거형 상수를 사용하면 기존의 소스를 다시 컴파일하지 않아도 됨

2.2 열거형의 정의와 사용

enum 열거형이름	{ 상수면1, 상수명2, ... }
  • {} 안에 상수의 이름을 나열해 정의
  • 열거형에 정의된 상수를 사용하는 방법: 열거형이름.상수명
  • 열거형 상수간의 비교에는 ‘==’ 사용이 가능. >, <와 같은 비교 연산자는 사용할 수 없고 compareTo()는 사용 가능
    • compareTo()는 두 비교대상이 같으면 0, 왼쪽이 크면 양수, 오른쪽이 크면 음수를 반환
  • switch문의 조건식에도 열거형 사용 가능
    • case문에는 열거형의 이름은 적지 않고 상수의 이름만 적어야 한다는 제약이 있음

모든 열거형의 조상 - java.lang.Enum

  • Enum 클래스에 정의된 메서드
  • values(): 열거형의 모든 상수를 배열에 담아 반환. 모든 열거형이 가지고 있는 것으로 컴파일러가 자동으로 추가해줌
  • ordinal(): 모든 열거형의 조상인 java.lang.Enum 클래스에 정의된 것으로, 열거형 상수가 정의된 순서(0부터 시작)를 정수로 반환

2.3 열거형에 멤버 추가하기

  • ordinal()이 열거형 상수가 정의된 순서를 반환하지만, 이 값을 열거형 상수 값으로 사용하는 것은 좋지 않음

    • 이유: 이 값은 내부적인 용도로만 상용되기 위한 것이기 때문
  • 열거형 상수의 값이 불연속적인 경우에는 열거형 상수의 이름 옆에 원하는 값을 괄호()와 함께 적어주면 됨

    ```java enum Diraction {EAST(1), SOUTH(5), WEST(-1), NORTH(10)}

  • 지정된 값을 저장할 수 있는 인스턴스 변수와 생성자를 새로 추가해 주어야 함

    • 먼저 열거형 상수를 모두 정의한 다음 다른 멤버들을 추가해야 함. 열거형 상수의 마지막에 ‘;’도 붙여야 함

2.4 열거형의 이해

  • 열거형 상수 하나하나가 객체
  • 모든 열거형은 추상 클래스 Enum의 자손
  • 추상 메서드를 새로 추가하면, 클래스 앞에도 abstract를 붙여줘야 하고, 각 static 상수들도 추상 메서드를 구현해주어야 함

3. 애너테이션(annotation)

3.1 애너테이션이란?

  • 소스코드의 주석(/** ~ */)에 소스코드에 대한 정보를 저장하고, 소스코드의 주석으로부터 HTML 문서를 생성해내는 프로그램(javadoc.exe)을 만들어 사용함
    • /**로 시작하는 주석 안에 소스코드에 대한 설명들이 있고, 그 안에 @이 붙은 태그들이 있음
    • 미리 정의된 태그들을 이용해 주석 안에 정보를 저장하고, javadoc.exe라는 프로그램이 이 정보를 읽어 문서를 작성하는데 사용
  • 애너테이션: 프로그램의 소스코드 안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨 것
    • 애너테이션은 프로그래밍 언어에 영향을 미치지 않으면서도 다른 프로그램에게 유용한 정보를 제공할 수 있음
    • JDK에서 제공하는 표준 애너테이션은 주로 컴파일러를 위한 것으로 컴파일러에게 유요안 정보를 제공
    • 새로운 애너테이션을 정의할 때 사용하는 메타 애너테이션을 제공
  • JDK에서 제공하는 애너테이션

3.2 표준 애너테이션

@Override

  • 메서드 앞에만 붙일 수 있는 애너테이션
  • 조상의 메서드를 오버라이딩하는 것이라는걸 컴파일러에게 알려주는 역할을 함
  • 컴파일러가 같은 이름의 메서드가 조상에 있는지 확인하고 없으면, 에러메시지를 출력

@Deprecated

  • 더 이상 사용되지 않는 필드나 메서드에 붙이는 애너테이션
  • 이 애너테이션이 붙은 대상은 다른 것으로 대체되었으니 더 이상 사용하지 않을 것을 권한다는 의미

@FunctionalInterface

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

@SuppressWarnings

  • 컴파일러가 보여주는 경고메시지가 나타나지 않게 억제해줌
  • 애너테이션이 억제할 수 있는 경고 메시지 종류
    • deprecation: @Deprecated가 붙은 대상을 사용해서 발생하는 경고
    • unchecked: 지네릭스로 타입을 지정하지 않았을 때 발생하는 경고
    • rawtypes: 지네릭스를 사용하지 않아 발생하는 경고
    • varargs: 가변인자의 타입이 지네릭 타입일 때 발생하는 경고

@SafeVarargs

  • 메서드에 선언된 가변인자의 타입이 non-reifiable 타입일 경우, 해당 메서드를 선언하는 부분과 호출하는 부분에서 unchecked 경고가 발생
    • 컴파일 후에도 제거되지 않는 지네릭 타입을 reifiable 타입이라 하고, 제거되는 타입을 non-reifiable 타입이라 함
  • 해당 코드에 문제가 없다면 이 경고를 억제하기 위해 애너테이션을 사용
  • 이 애너테이션은 static이나 final이 붙은 메서드와 생성자에만 붙일 수 있음
    • 오버라이드될 수 있는 메서드에는 사용 불가

3.3 메타 애너테이션

  • 애너테이션에 붙이는 애너테이션으로 애너테이션을 정의할 때 애너텡이션의 적용대상(target)이나 유지기간(retention) 등을 지정하는데 사용

  • @Target: 애너테이션이 적용가능한 대상을 저장하는데 사용
  • @Retention: 애너테이션이 유지되는 기간을 지정하는데 사용
    • SOURCE: 소스 파일에만 존재. 클래스파일에는 존재하지 않음
    • CLASS: 클래스 파일에 존재. 실행시에 사용불가. 기본값
    • RUNTIME: 클래스 파일에 존재. 실행시에 사용가능
  • @Documented: 애너테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 함
    • 자바에서 제공하는 기본 애너테이션 중에 @Overide@SuppressWarnings를 제외하고는 모두 이 메타 애너테이션이 붙어 있음
  • @Inherited: 애너테이션이 자손 클래스에 상속되도록 함
    • 이 애너테이션이 붙은 애너테이션을 조상 클래스에 붙이면, 자손 클래스도 이 애너테이션이 붙은 것과 같이 인식됨
  • @Repeatable: 하나의 대상에 한 종류의 애너테이션을 붙이지만, 이 애너테이션이 붙은 애너테이션은 여러 번 붙일 수 있음
    • 같은 이름의 애너테이션이 여러 개가 하나의 대상에 적용될 수 있기 때문에, 이 애너테이션들을 하나로 묶어서 다룰 수 있는 애너테이션도 추가로 정의해야 함
  • @Native: native method에 의해 참조되는 상수 필드(constant field)에 붙이는 애너테이션
    • 네이티브 메서드는 JVM에 설치된 OS 메서드를 의미. Object 클래스의 메서드들은 대부분 네이티브 메서드

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

@interface 애너테이션이름	{
    타입 요소이름();	// 애너테이션의 요소를 선언
    ...
}

애너테이션의 요소

  • 애너테이션 내에 선언된 메서드를 의미
  • 반환값이 있고 매개변수는 없는 추상 메서드의 형태를 가지며, 상속을 통해 구현하지 않아도 됨
    • 단, 애너테이션을 적용할 때 이 요소들의 값을 빠짐없이 지정해주어야 함. 요소의 이름도 같이 적어주므로 순서는 상관없음
  • 애너테이션의 각 요소는 기본값을 가질 수 있으며, 기본값이 있는 요소는 애너테이션을 적용할 때 값을 지정하지 않으면 기본값이 사용됨
  • 애너테이션 요소가 하나뿐이고 이름이 value인 경우, 애너테이션을 적용할 때 요소의 이름을 생략하고 값만 적어도 됨
  • 요소의 타입이 배열인 경우, {}를 사영해서 여러 개의 값을 지정할 수 있음

마커 애너테이션 Marker Annotation

  • 값을 지정할 필요가 없는 경우, 애너테이션의 요소를 하나도 정의하지 않을 수 있음
  • Serializable이나 Cloneable 인터페이스처럼, 요소가 하나도 정의되지 않은 애너테이션을 마커 애너테이션이라 함

애너테이션 요소의 규칙

  • 요소의 타입은 기본형, String, enum, 애너테이션, Class만 혀용됨
  • ()안에 매개변수를 선언할 수 없음
  • 예외를 선언할 수 없음
  • 요소를 타입 매개변수로 정의할 수 없음

Chapter 12 끝!!!