Chapter 12. 제네릭스, 열거형, 애너테이션
1. 제네릭스(generics)
(1) 제네릭스, 제네릭 클래스의 선언
- 제네릭스의 도입
- JDK1.5에서 처음 도입되었으며 JDK1.8에 도입된 람다식만큼 큰 변화
- 제네릭스 : 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능
- 제네릭스의 장점
- 타입 안정성을 제공
- 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해짐
(2) 제네릭 클래스의 객체 생성과 활용
- 제네릭 클래스의 선언
- T : 타입 변수, 임의의 참조형 타입이며 꼭 T가 아니어도 되며 각 타입 변수의 첫글자를 사용해도 된다
(Key, Value일 경우 K, V, Element일 경우 E 등)
class Box {
Object item;
void setItem(Object item) {
this.item = item;
}
Object getItem() {
return item;
}
}
class Box<T>
T item;
void setItem(T item) {
this.item = item;
}
Object getItem() {
return item;
}
class Box<T> {}
- 제네릭스의 제한
- 모든 객체에 대해 동일하게 동작해야 하는 static 멤버에 대해서는 타입 변수 T를 사용할 수 없음. T는 인스턴스 변수로 간주되기 때문
class Box<T> {
static T item;
static int compare(T t1, T t2...) {}
}
- 제네릭 클래스의 객체 생성과 사용
- 제네릭 클래스의 객체를 생성할 때엔 참조변수와 생성자에 대입된 타입이 일치해야 함
- 상속 관계에 있다고 해도 에러가 발생함
- 다만 두 제네릭 클래스의 타입이 상속 관계에 있고 대입된 타입이 같은 것은 괜찮음
Box<Apple> appleBox = new Box<Apple>();
Box<Fruit> appleBox = new Box<Apple>();
Box<Apple> appleBox = new FruitBox<Apple>();
- 추정 가능한 경우 타입을 생략할 수 있음 (JDK1.7~)
Box<Apple> appleBox = new Box<Apple>();
Box<Apple> appleBox = new Box<>();
(3) 제한된 제네릭 클래스
- 제네릭 타입에 extends를 사용할 경우 특정 타입의 자손들만 대입할 수 있음
class FruitBox<T extends Fruit> {
}
(4) 와일드 카드
- 와일드 카드의 필요성
- 아래와 같은 이슈가 있어 와일드 카드가 필요하게 됨
class Juicer
static Juice makeJuice(FruitBox<Fruit> box) {
.....
}
static Juice makeJuce(FruitBox<Apple> box) {}
- 와일드 카드
<? extends T>
: 와일드 카드의 상한 제한. T와 그 자손들만 사용 가능
<? super T>
: 와일드 카드의 하한 제한. T와 그 조상들만 사용 가능
<?>
: 제한 없음. 모든 타입이 가능. <? extends Object>
와 동일
class Juicer
static Juice makeJuice(FruitBox<? extends Fruit> box) {
.....
}
(5) 제네릭 메서드
- 제네릭 메서드 : 메서드의 선언부에 제네릭 타입이 선언된 메서드
- static 멤버에 타입 변수를 사용할 수는 없으나 메서드에 제네릭 타입을 선언하고 사용하는 것은 가능
- 메서드에 선언된 제네릭 타입은 지역 변수를 선언한 것과 같다고 생각하면 됨
static <T> void sort(List<T> list, Comparator<? super T> c)
class Juicer
static <T extends Fruit> Juice makeJuice(FruitBox<T> box) {
.....
}
(6) 제네릭 타입의 형변환, 제거
- 제네릭 타입의 형변환
- 제네릭 타입과 원시 타입 간의 형 변환은 항상 가능. 다만 경고만 발생할 뿐
- 단, 대입된 타입이 다른 제네릭 타입 간에는 형변환 불가
Box box = null;
Box<Object> objBox = null;
Box<String> strBox = null;
box = (Box) objBox;
objBox = (Box<Object>) box;
objBOx = (Box<Object>) strBox;
- 제네릭 타입의 제거
- 컴파일러는 제네릭 타입을 이용해 소스파일을 체크하고 필요한 곳에 형변환을 넣어준 후, 제네릭 타입을 제거
- 따라서 컴파일 된 파일(.class) 에는 제네릭 타입에 대한 정보가 없음
2. 열거형(enums)
(1) 열거형의 정의와 사용
- 열거형 : 서로 관련된 상수를 편리하게 선언하기 위한 것으로 여러 상수를 정의할 때 사용
- 열거형의 정의
enum 열거형 이름 {상수명1, 상수명2}
public enum PowerRangers {RED, BLUE, GREEN, YELLOW, PINK}
public enum PowerRangers {
RED(1), BLUE(2), GREEN(3), YELLOW(4), PINK(5);
private final int id;
PowerRangers(int id) {
this.id = id;
}
int getId() {
return id;
}
abstract void signal();
}
(2) 열거형의 이해
- 열거형 상수 하나하나가 열거형 객체이며, static이다 (위에서는 PowerRangers 객체)
- 일반 클래스로 바꾸면 다음과 같다
class PowerRangers {
static final PowerRangers RED = new PowerRangers("RED");
...
private String name;
private PowerRangers(String name) {
this.name = name;
}
}
3. 애너테이션(annotation)
(1) 애너테이션의 정의
- 애너테이션의 기원
- 소스 코드와 문서를 하나의 파일로 관리하는 것이 낫다고 생각해서 만들어진 주석
/** ~ */
-> 해당 주석에 소스코드에 대한 정보를 저장하고 주석으로부터 HTML 문서를 생성해내는 javadoc.exe를 만들어서 사용
- 해당 기능을 응용해서 프로그램의 소스코드 안에 다른 프로글매을 위한 정보를 미리 약속된 형식으로 포함시킨 것이 바로 애너테이션
- 애너테이션의 특징
- 주석처럼 프로그래밍 언어에 영향을 미치지 않으면서도 다른 프로그램에게 유용한 정보를 제공할 수 있다는 장점이 있음
- JDK에서 기본적으로 제공하는 애너테이션과 다른 프로그램에서 제공하는 것이 있음
- JDK에서 제공하는 표준 애너테이션은 주로 컴파일러를 위한 것
- 추가로 새로운 애너테이션을 정의할 때 사용하는 메타 애너테이션이 존재
(2) 표준 애너테이션
- @Override : 컴파일러에게 오버라이딩하는 메서드라는 것을 알림
- @Deprecated : 앞으로 사용하지 않을 것을 권장하는 대상에 붙임
- @SupperessWarnings : 컴파일러의 특정 경고 메시지가 나타나지 않게 해줌
- @SafeVarargs : 제네릭스 타입의 가변인자에 사용 (JDK1.7)
- @FunctionalInterface : 함수형 인터페이스라는 것을 알림 (JDK1.8)
- @Native : native 메서드에 참조되는 상수 앞에 붙임 (JDK1.8)
(3) 메타 애너테이션
- @Target : 애너테이션이 적용가능한 대상을 지정하는데 사용
- @Documented : 애너테이션 정보가 javadoc으로 작성된 문서에 포함되게 함
- @Inherited : 애너테이션이 자손 클래스에 상속되도록 함
- @Retention : 애너테이션이 유지되는 범위를 지정하는데 사용
- SOURCE : 소스 파일에만 존재, 클래스 파일에는 존재하지 않음
- CLASS : 클래스 파일에 존재, 실행 시에 사용 불가, 기본값
- RUNTIME : 클래스 파일에 존재, 실행시에 사용 가능
- @Repeatable : 애너테이션을 반복해서 적용할 수 있게 함 (JDK1.8)
(4) 애니테이션 타입 정의하기
@interface 애너테이션 이름 {
타입 요소 이름();
}
- 애너테이션의 요소
- 애너테이션의 요소는 추상 메서드의 형태를 가지며, 상속을 통해 구현하지 않아도 됨. 다만 애너테이션 적용 시 이 요소들의 값을 빠짐없이 지정해줘야 함. 순서는 상관 없음.
- 요소는 null을 제외한 기본값을 가질 수 있음
@interface TestInfo {
int count() default 1;
String testedBy();
String[] testTools();
}
- 마커 애너테이션
- 값을 지정할 필요가 없는 경우 요소를 하나도 정의하지 않을 수 있음
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}
- 애노테이션 요소의 규칙
- 요소의 타입은 기본형, StRING, enum, 애너테이션, Class만 허용
- () 안에 매개변수를 선언할 수 없음
- 예외를 선언할 수 없음
- 요소를 타입 매개변수로 정의할 수 없음