Chapter 12. 지네릭스, 열거형, 애너테이션
28 Jan 2022 | 자바의 정석 JAVA1. 지네릭스(Generics)
- JDK1.5에서 처음 도입
1.1 지네릭스란?
- 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크(compile-time type check)를 해주는 기능
- 객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 버거로움이 줄어듦
- 타입 안정성을 높인다는 것은 의도하지 않은 타입의 객체가 저장되는 것을 막고, 저장된 객체를 꺼내올 때 원래의 타입과 다른 타입으로 잘못 형변환되어 발생할 수 있는 오류를 줄여준다는 의미
지네릭스의 장점
- 타입 안정성을 제공
- 타입체크와 형변환을 생략할 수 있어 코드가 간결해짐
- 다룰 객체의 타입을 미리 명시해줌으로써 번거로운 형변환을 줄여줌
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 타입과 다를게 없으므로,
extends
와super
로 상한(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)에는 지네릭 타입에 대한 정보가 없음
지네릭 타입 제거 과정
- 지네릭 타입의 경계(bound)를 제거
- 지네릭 타입이
<T extends Fruit>
라면 T는 Fruit로 치환.<T>
인 경우 T는 Object로 치환됨 - 치환된 후 클래스 옆의 선언은 제거됨
- 지네릭 타입이
- 지네릭 타입을 제거한 후에 타입이 일치하ㅈ 않으면, 형변환을 추가함
- 와일드 카드가 포함되어 있는 경우에는 적절한 타입으로의 형변환이 추가됨
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: 가변인자의 타입이 지네릭 타입일 때 발생하는 경고
- deprecation:
@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 끝!!!