지네릭스(Generics)
지네릭스를 모르고는 Java API 문서조차 제대로 보기 어려울 만큼 종요한 위치를 차지하고 있다.
지네릭스란?
지네릭스란 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입체크를 해주는 기능이다. 객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어든다.
타입 안정성을 높인다는 것은 의도하지 않은 타입의 객체가 저장되는 것을 막고, 저장된 객체를 꺼내올 때 원래의 타입과 다른 타입으로 잘못 형변환되어 발생할 수 있는 오류를 줄여준 다는 뜻이다.
지네릭스의 장점
1. 타입 안정성을 제공한다.
2. 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해 진다.
간단하게,
다룰 객체의 타입을 미리 명시해줌으로써 번거로운 형변환을 줄여준다.
지네릭 클래스의 선언
class Box {
Object item;
void setItem(Object item) { this.item = item; }
Object getItem() { return item; }
}
이 클래스를 지네릭 클래스로 변경하면 다음과 같이 클래스 옆에 '<T>'를 붙이면 된다. 그리고 'Object'를 모두 'T'로 바꾼다.
class Box<T> { // 지네릭 타입 T를 선언
T item;
void setItem(T item) { this.item = item; }
T getItem() { return item; }
}
지네릭스의 용어
class Box<T> { }
Box<T> : 지네릭 클래스. 'T의 Box' 또는 'T Box'라고 읽는다.
T : 타입 변수 또는 타입 배개변수. (T는 타입 문자)
Box : 원시 타입(raw type)
지네릭스의 제한
지네릭 클래스 Box의 객체를 생성할 때, 객체별로 다른 타입을 지정하는 것은 적절하다.
지네릭스는 이처럼 인스턴스별로 다르게 동작하도록 하려고 만든 기능이기 때문.
Box<Apple> appleBox = new Box<Apple>(); // OK. Apple 객체만 저장 가능
Box<Grape> grapeBox = new Box<Grape>(); // OK. Grape 객체만 저장 가능
그러나 모든 객체에 대해 동일하게 동작해야하는 static 멤버에 타입 변수 T를 사용할 수 없다. T는 인스턴스 변수로 간주되기 때문이다. static 멤버는 인스턴스 변수를 참조할 수 없다.
class Box<T> {
static T item; // 에러
static int compare(T t1, T t2) { ... } // 에러
...
}
static 멤버는 타입 변수에 지정된 타입, 즉 대입된 타입의 종류와 관계없이 동일한 것이어야 하기 때문이다.
증, 'Box<Apple>.item'과 'Box<Grape>.item'이 다른 것이어서는 안된다는 뜻이다. 그리고 지네릭 타입의 배열을 생성하는 것도 허용하지 않는다. 지네릭 배열 타입의 참조변수를 선언하는 것은 가능하지만, 'new T[10]'과 같이 배열을 생성하는 것은 안 된다는 뜻이다.
class Box<T> {
T[] itemArr; // OK. T타입의 배열을 위한 참조변수
...
T[] toArray() {
T[] tmpArr = new T[itemArr.length]; // 에러. 지네릭 배열 생성불가
...
return tmpArr;
}
...
}
지네릭 배열을 생성할 수 없는 것은 new 연산자 때문인데, 이 연산자는 컴파일 시점에 타입 T가 뭔지 정확히 알아야 한다. 그런데 위의 코드에 정의된 Box<T> 클래스를 컴파일 하는 시점에서는 T가 어떤 타입이 될지 전혀 알 수 없다. instanceof 연산자도 new 연산자와 같은 이유로 T를 피연산자로 사용할 수 없다.
지네릭 메서드
메서드의 선언부에 지네릭 타입이 선언된 메서드를 지네릭 메서드라 한다.
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) {
...
}
}
열거형(enums)
열거형은 서로 관련된 상수를 편리하게 선언하기 위한 것으로 여러 상수를 정의할 때 사용하면 유용하다.
class Card {
static final int CLOVER = 0;
static final int HEART = 1;
static final int DIANMOND = 2;
static final int SPADE = 3;
static final int TWO = 0;
static final int THREE = 1;
static final int FOUR = 2;
final int kind;
final int num;
}
↓
class Card {
enum Kind { CLOVER, HEART, DIAMOND, SPADE } // 열거형 Kind를 정의
enum Value { TWO, THREE, FOUR } // 열거형 Value를 정의
final Kind kind; // 타입이 int가 아닌 Kind임에 유의
final Value value;
}
열거형의 정의와 사용
enum 열거형이름 { 상수명1, 상수명2, ... }
enum Direction { EAST, SOUTH, WEST, NORTH }
class Unit {
int x, y;
Direction dir;
void init() {
dir = Direction.EAST;
}
}
애너테이션
프로그램의 소스코드 안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함신킨 것.
주석처럼 프로그래밍 언어에 영향을 미치지 않으면서도 다른 프로그램에게 유용한 정보를 제공할 수 있다는 장점이 있다.
표준 애너테이션
자바에서 기본적으로 제공하는 애너테이션
@Override
메서드 앞에만 붙일 수 있는 애너테이션으로, 조상의 메서드를 오버라이딩하는 것이라는걸 컴파일러에게 알려주는 역할을 한다.
@Deprecated
더 이상 사용되지 않는 필드나 메서드에 '@Deprecated"를 붙이는 것. 이 애너테이션이 붙은 대상은 다른 것으로 대체되었으니 더 이상 사용하지 않는 것을 권한다는 의미.
@FunctionalInterface
'함수형 인터페이스'를 선언할 때, 이 애너테이션을 붙이면 컴파일러가 '함수형 인터페이스'를 올바르게 선언했는지 확인하고, 잘못된 경우 에러를 발생시킨다. 실수를 방지하기 위해 '함수형 인터페이스'를 선언할 때 이 애너테이션을 반드시 붙이도록 하자.
@SuppressWarnings
컴파일러가 보여주는 경고메시지가 나타나지 않게 억제해준다.
@SafeVarargs
메서드에 선언된 가변인자의 타입이 non-reifiable 타입일 경우, 해당 메서드를 선언하는 부분과 호출하는 부분에서 "unchecked"경고가 발생한다. 해당 코드에 문제가 없다면 이 경고를 억제하기 위해 '@SafeVarargs"를 사용해야 한다.
이 애너테이션은 static이나 final이 붙은 메서드와 생성자에만 붙일 수 있다. 즉 오버라이드될 수 있는 메서드에는 사용할 수 없다는 뜻이다.
메타 애너테이션
애너테이션을 위한 애너테이션, 즉 애너테이션을 붙이는 애너테이션으로 애너테이션을 정의할 때 애너테이션의 적용대상이나 유지기간 등을 지정하는데 사용된다.
@Target
애너테이션이 적용가능한 대상을 지정하는데 사용된다. 아래는 '@SuppressWarninigs"를 정의한 것인데, 이 애너테이션에 적용할 수 있는 대상을 "@Target"으로 적용하였다. 여러 개의 값을 지정할 때는 배열처럼 괄호 {} 를 사용해야한다.
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
@Retention
애너테이션이 유지되는 기간을 지정하는데 사용된다.
| 유지 정책 | 의미 |
| SOURCE | 소스 파일에만 존재. 클래스 파일에는 존재하지 않음 |
| CLASS | 클래스 파일에 존재. 실행시에 사용불가. 기본값 |
| RUNTIME | 클래스 파일에 존재. 실행시에 사용가능. |
@Documented
애너테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 한다. @Override와 @SuppressWarnings를 제외하고는 모두 이 메타 애너테이션이 붙어있다.
@Inherited
애너테이션이 자손 클래스에 상속되도록 한다. '@Inherited'가 붙은 애너테이션을 조상 클래스에 붙이면, 자손 클래스도 이 애너테이션이 붙은 것과 같이 인식된다.
@Inherited // @SupperAnno가 자손까지 영향 미치게
@interface SupperAnno {}
@SuperAnno
class Parent {}
class Child extends Parent {} // Child에 애너테이션이 붙은 것으로 인식
위 코드에서 Child 클래스는 애너테이션이 붙지 않았지만, 조상인 Parent 클래스에 붙은 '@SuperAnno'가 상속되어 Child 클래스에도 '@SuperAnno'가 붙은 것처럼 인식된다.
@Repeatable
보통은 하나의 대상에 한 종류의 애너테이션을 붙이는데, '@Repeatable'이 붙은 애너테이션은 여러 번 붙일 수 있다.
@Repeatable (ToDos.calss) // ToDo 애너테이션을 여러 번 반복해서 쓸 수 있게 한다.
@interface ToDo {
String value();
}
@Native
네이티브 메서드에 의해 참조되는 '상수 필드'에 붙이는 애너테이션이다.
@Native public static final long MIN_VALUE = 0x8000000000000000L;
자바에서는 메서드의 선언부만 정의하고 구현은 하지 않는다. 그래서 추상 메서드처럼 선언부만 있고 몸통이 없다.
애너테이션 타입 정의하기
새로운 애너테이션을 정의하는 방법은 아래와 같다.
@interface 애너테이션이름 {
타입 요소이름(); // 애너테이션의 요소를 선언하다.
...
}
@Override : 애너테이션
Override : 애너테이션의 타입
애너테이션의 요소
애너테이션 내에 선언된 메서드를 '애너테이션의 요소'라고 하며, 아래에 선언된 TestInfo 애너테이션은 다섯 개의 요소를 갖는다.
@interface TestInfo {
int count();
String testedBy();
String[] testTools();
TestType testType(); // enum TestType { FIRST, FINAL }
DateTime testDate(); // 자신이 아닌 다른 애너테이션(@DateTime)을 포함할 수 있다.
}
@interface DateTime {
String yymmdd();
String hhmmss();
}
'자바 > Java의 정석' 카테고리의 다른 글
| [Chapter 14] 람다와 스트림 (1) | 2024.11.30 |
|---|---|
| [Chapter 13] 쓰레드 (2) | 2024.11.30 |
| [Chapter 11] 컬렉션 프레임웍 (1) | 2024.11.26 |
| [Chapter 10] 날짜와 시간 & 형식화 (0) | 2024.11.25 |
| [Chapter 09] java.lang 패키지와 유용한 클래스 (0) | 2024.11.24 |