12장
1. 제네릭
1.1. 제네릭의 소개
제네릭(Generic)은 메서드나 클래스의 데이터 유형을 제한하는 방법이다. 제네릭은 java 1.5에서 도입한 다양한 확장 기능 중 하나다. java 1.2에서 등장한 컬렉션 프레임워크는 다양한 타입의 객체들을 다룰 수 있다는 장점이 있었지만, 반면에 데이터를 다룰 때 런타임 오류가 발생하거나 형 변환을 해야하는 등의 단점도 있었다.
프로그래머가 실제로 자신의 의도를 표현하고 특정 데이터 유형을 포함하도록 제한하게끔 표시하면 어떨까?
이것이 제네릭의 핵심 아이디어이다. 아래 예시와 함께 생각해보자.
/*
==========================================================================
1. 제네릭 미사용
==========================================================================
*/
List myIntList = new LinkedList(); // 1
myIntList.add(new Integer(0)); // 2
Integer x = (Integer) myIntList.iterator().next(); // 3
/*
==========================================================================
2. 제네릭 사용
==========================================================================
*/
List <Integer> myIntList = new LinkedList<Integer>(); // 4
myIntList.add(new Integer(0)); // 5
Integer x = myIntList.iterator().next(); // 6
1번 예시를 보자. 전통적인 방법대로 프로그래밍하기 위해선 필수적으로 형변환을 거쳐야 한다. 컴파일러는 iterator가 객체를 반환하는 것만 보장한다. 정수형 변수에 대한 할당이 안전한지 확인하기 위해 형변환은 필요하다. 형변환은 프로그래머가 코드의 한 지점에서 참이라고 생각하는 것을 알려주므로 다른 지점에서 형변환을 하지 않으면 에러를 일으킬 수도 있다. 2번 예시를 보면 myIntList가 <Integer>, 즉 정수형 매개변수를 받는 LinkedList임을 선언하고 있다. 이제 형변환이 사라졌으며 컴파일러는 컴파일 타임에 프로그램의 유형 정확성을 확인할 수 있다. 이제 myIntList는 언제 어디서 사용되더라도 정수형 매개변수를 받는 List이며 이를 컴파일러가 보장한다. 제네릭을 사용하면 대규모 프로그램에서 가독성과 견고성이 향상된다.
1.2. 제네릭의 정의
/*
==========================================================================
1. java.util에 정의된 List와 Iterator의 인터페이스
==========================================================================
*/
public interface List<E> {
void add(E x);
Iterator<E> iterator();
}
public interface Iterator<E> {
E next();
boolean hasNext();
}
/*
==========================================================================
2. List<Integer>에 대한 상상
==========================================================================
*/
public interface IntegerList {
void add(Integer x);
Iterator<Integer> iterator();
}
위 코드 블럭에 작성된 코드처럼, List는 java.util에 정의된 List의 인터페이스에서 E를 Integer로 대체한 걸까? 안타깝게도 그렇지 않다. 매개변수화된 유형 List<Integer>에는 실제로 이러한 확장과 유사한 메서드가 있다. 그러나 제네릭의 선언은 실제로 이런 식으로 확장되지 않는다. 소스, 바이너리, 디스크, 메모리 어느 곳에도 코드의 복사본이 여러 개 존재하지 않는다. 이는 C++의 템플릿과 매우 다르다는 걸 의미한다. 제네릭 타입 선언은 일반 클래스 또는 인터페이스 선언과 마찬가지로 한 번만 컴파일되고 단일 클래스 파일로 변환된다. 또한 타입 매개변수는 메서드나 생성자에서 사용되는 일반 매개변수와 유사하다. 메서드가 작동하는 값의 종류를 설명하는 형식 값 매개변수가 있는 것처럼, 일반 선언에는 형식 유형 매개변수가 있다. 메서드가 호출되면 실제 인수가 형식 매개변수로 대체되고 메서드 본문이 평가된다. 동일하게 제네릭 선언이 호출되면 실제 타입 인수가 형식적 타입 인수로 대체된다.
1.3. 제네릭 및 하위 유형
/*
==========================================================================
1. 제네릭 및 하위 유형
==========================================================================
*/
List<String> ls = new ArrayList<String>(); // 1
List<Object> lo = ls; // 2
lo.add(new Object()); // 3
String s = ls.get(0); // 4: Attempts to assign an Object to a String!
자바 컴파일러는 제네릭으로 제한된 데이터 타입과 일치하지 않는 데이터의 저장을 허용하지 않는다. 따라서 2행은 컴파일 시 에러를 일으킨다. B는 A의 서브타입(서브 클래스 또는 서브 인터에피스)이고 G가 제네릭 타입 선언이라고 가정하자. G<B>는 G<A>의 서브타입이 될 수 없다. 우리의 본능은 컬렉션을 불변한다고 생각할 수 있다. 그러나 컬렉션이 불변이라고 가정해서는 안 된다. 다양한 요구사항에 맞추기 위해 컬렉션에 들어갈 값은 변경될 수 있다. 그렇다면 제네릭으로 타입을 제한하되 다양한 유형을 저장하는 방법은 무엇일까?
1.4. 와일드카드
컬렉션의 모든 요소를 출력한다고 가정해보자.
/*
==========================================================================
1. 제네릭 미사용
==========================================================================
*/
void printCollection(Collection c) {
Iterator i = c.iterator();
for (k = 0; k < c.size(); k++) {
System.out.println(i.next());
}
}
/*
==========================================================================
2. 제네릭 사용
==========================================================================
*/
void printCollection(Collection<Object> c) {
for (Object e : c) {
System.out.println(e);
}
}
위 예시처럼 제네릭을 사용하는 버전은 제네릭을 사용하지 않는 버전보다 호환성이 떨어진다. 다형성을 유지하고 제네릭을 사용할 수 있는 모든 컬렉션의 상위 유형은 무엇일까? 그것은 Collection<?>로 정의되고(collection of unknown) 와일드카드 유형이라고 부른다.
/*
==========================================================================
3. 제네릭 사용(와일드카드 유형)
==========================================================================
*/
void printCollection(Collection<?> c) {
for (Object e : c) {
System.out.println(e);
}
}
이제 모든 유형의 컬렉션을 호출할 수 있다. printCollection 내부에서는 여전히 c의 요소를 읽고 객체 유형을 지정할 수 있다. 컬렉션의 실제 유형이 무엇이든 컬렉션에는 객체가 포함되므로 항상 안전하다. 모든 컬렉션의 상위 유형을 Collection<?>로 정의한다면 제네릭도 Object를 상속하는 걸까? 사실 Collection<?>는 Collection<? extends Object>와 동일하다. <? extends T>는 와일드카드의 상한을 제한하여 T와 그 자손들의 타입을 사용할 수 있다. 반대로 <? super T>는 와일드카드의 하한을 제한하여 T와 그 조상들의 타입만 사용 가능하다.
1.5. 제네릭 메서드
타입 선언과 마찬가지로 메서드 선언도 하나 이상의 타입 매개변수로 제네릭 선언이 가능하다. 객체 배열과 컬렉션을 받아 모든 객체를 컬렉션에 넣는 메서드를 작성한다고 가정해보자.
/*
==========================================================================
1. 메서드(에러)
==========================================================================
*/
static void fromArrayToCollection(Object[] a, Collection<?> c) {
for (Object o : a) {
c.add(o); // compile-time error
}
}
/*
==========================================================================
2. 제네릭 메서드(올바른 사용)
==========================================================================
*/
static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
for (T o : a) {
c.add(o); // Correct
}
}
1번 예시를 보면 컬렉션에 무엇이 들어올 지 몰라 Collection<?> c를 선언했다. 이는 알 수 없는 유형의 컬렉션에 객체를 밀어넣기 때문에 컴파일 에러가 발생한다. 2번 예시를 보면 제네릭 메서드를 사용해서 객체를 밀어넣고 있다. 제네릭 타입의 선언 위치는 반환 타입 바로 앞이다. 제네릭 클래스에 정의된 타입 매개변수 T와 제네릭 메서드에 정의된 타입 매개변수 T는 별개의 것임에 유의하자.
2. 열거형
열거형(enum): 여러 상수를 선언해야 할 때 편리하게 선언할 수 있는 방법
/*
==========================================================================
1. 열거형(enum) 정의
==========================================================================
*/
enum 열거형이름 { 상수명1, 상수명2, ... }
/*
==========================================================================
2. 열거형(enum) 예제
==========================================================================
*/
class Card {
static final int CLOVER = 0;
static final int HEART = 1;
static final int DIAMOND = 2;
static final int SPADE = 3;
static final int ONE = 0;
static final int TWO = 1;
static final int THREE = 2;
static final int FOUR = 3;
final int kind;
final int value;
}
// 값 비교 시(문제점)
if (Card.CLOVER == Card.ONE) {
// Card의 kind와 num은 비교 대상이 아니므로 false가 되어야 함
}
// 열거형으로 상수 선언
class Card {
enum Kind { CLOVER, HEART, DIAMIND, SPADE }
enum Value { TWO, THREE, FOUR }
final Kind kind;
final Value value;
}
// 값 비교 시(올바른 사용|에러)
if(Card.Kind.CLOVER= == Card.Value.ONE) {
// 타입이 달라 컴파일 에러 발생
}
/*
==========================================================================
3. 열거형(enum)에 멤버 추가하기
==========================================================================
*/
// 열거형 상수의 값이 불규칙한 경우
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) { // 묵시적으로 private
this.value = value;
}
public int getValue() {
return value;
}
}
// 열거형 생성자 외부 호출 시(에러)
Direction d = new Direction(1); // 열거형 생성자 외부 호출 불가
3. 어노테이션
어노테이션(annotation): 소스코드와 결합하여 다른 프로그램을 위한 정보를 (미리 약속한 형식으로) 제공하는 주석
메타 어노테이션(annotaion): 어노테이션에 붙이는 어노테이션으로 어노테이션의 적용 대상이나 유지기간 등을 지정
3.1. 메타 어노테이션
3.1.1. @Documented
Description
Type Signature
@Documented @Retention (RUNTIME) @Target (ANNOTATION_TYPE) public @interface Documented
Block
어노테이션 정보가 java doc으로 작성된 문서에 포함되게 하는 어노테이션 인터페이스
Note
Since
1.5
3.1.2. @Inherited
Description
Type Signature
@Documented @Retention (RUNTIME) @Target (ANNOTATION_TYPE) public @interface Inherited
Block
주석이 자동으로 상속됨을 나타내는 어노테이션 인터페이스
Note
Since
1.5
3.1.3. @Retention
Description
Type Signature
@Documented @Retention (RUNTIME) @Target (ANNOTATION_TYPE) public @interface Retention
Block
주석이 유지되는 기간을 나타내는 에너테이션 인터페이스
Note
Since
1.5
3.1.4. @Target
Description
Type Signature
@Documented @Retention (RUNTIME) @Target (ANNOTATION_TYPE) public @interface Target
Block
어노테이션 인터페이스가 적용될 수 있는 컨텍스트를 나타내는 어노테이션
Note
Since
1.5
See Java Language Specification
3.1.5. @Repeatable
Description
Type Signature
@Documented @Retention (RUNTIME) @Target (ANNOTATION_TYPE) public @interface Repeatable
Block
(메타) 주석인 주석 인터페이스가 반복 가능함을 나타내는 어노테이션 인터페이스
Note
Since
1.8
See Java Language Specification
3.2. 메타 에너테이션을 제외한 어노테이션
3.2.1. @Deprecated
Description
Type Signature
@Documented @Retention (RUNTIME) @Target ({CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE}) public @interface Deprecated
Block
- 사용하지 않는 걸 권장하는 프로그램 요소를 나타내는 에너테이션 인터페이스 - 컴파일러는 deprecated 프로그램 요소가 사용되거나 override될 때 경고
Note
Since
1.5
3.2.2. @Override
Description
Type Signature
@Documented @Retention (RUNTIME) @Target (TYPE) public @interface FunctionalInterface
Block
메서드 선언이 상위 유형의 메서드 선언을 재정의한다는 걸 나타내는 에너테이션 인터페이스 컴파일러는 다음 조건 중 하나 이상이 충족되지 않으면 오류 메세지 생성 - 메서드가 상위 유형에 선언된 메서드를 대체하거나 구현 - 메서드가 Object에 선언된 공통 메서드를 재정의
Note
Since
1.5
See Java Language Specification
3.2.3. @SuppressWarnings
Description
Type Signature
@Target ({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE}) @Retention (SOURCE) public @interface SuppressWarnings
Block
주석이 달린 요소(및 주석이 달린 요소에 포함된 모든 프로그램 요소)에서 명명된 컴파일러 경고가 억제되어야 함을 나타내는 에너테이션 인터페이스
Note
Since
1.5
See Java Language Specification
3.2.4. @SafeVarargs
Description
Type Signature
@Documented @Retention (RUNTIME) @Target ({CONSTRUCTOR, METHOD}) public @interface SafeVarargs
Block
- 에너테이션이 달린 메서드 또는 생성자의 본문이 해당 varages 매개 변수에 대해 잠재적으로 안전하지 않은 작업을 수행하지 않는다는 인터페이스 에너테이션 - 메서드 또는 생성자에 이 주석을 적용하면 수정 불가능한 변수 인수(vararg) 유형에 경고 억제
Note
Since
1.7
3.2.5. @FunctionalInterface
Description
Type Signature
@Documented @Retention (RUNTIME) @Target (TYPE) public @interface FunctionalInterface
Block
인터페이스 유형이 함수형 인터페이스임을 나타내는 에너테이션 인터페이스 - 개념적으로 함수형 인터페이스에는 정확히 하나의 추상 메서드가 있음 - 함수형 인터페이스의 인스턴스는 람다 표현식, 메서드(생성자) 참조를 사용하여 생성 가능함을 유의
Note
Since
1.8
See Java Language Specification
3.2.6. @Native
Block
- native code에서 상수를 참조할 수 있음을 나타내는 에너테이션 인터페이스 - 네이티브 헤더 파일을 생성하는 도구에서 헤더 파일이 필요한지, 필요한 경우 어떤 선언을 포함해야 하는지 결정하기 위한 힌트
Note
Since
1.8
3.3. 사용자 정의 어노테이션
/*
==========================================================================
1. 사용자 정의 어노테이션
- 요소의 타입은 기본형, String, enum, Annotation, Class만 허용
- ()안에 매개변수, 예외 선언 불가
- 요소를 타입 매개변수로 정의 불가
==========================================================================
*/
@interface 어노테이션이름{
타입 요소이름(); // 어노테이션 요소 선언
}
참고 자료
자바의 정석 - 도우출판
Last updated