컴파일할 때 타입 체크를 해주는 기능. 클래스명 옆에 <> 를 붙여서 특정한 타입의 객체만 담을 수 있도록 지정해주는 것.
타입 안전성을 높이고, 형변환을 생략할 수 있어 코드가 간결해진다는 장점을 갖는다.
class Example<T> {}
지네릭스가 적용되어 있는 클래스를 지네릭 클래스라 하며, Example을 원시타입이라 한다.
<> 안의 T 를 타입 변수라고 하며, 객체를 생성할 때는 타입 변수에 실제 타입을 지정해주어야 한다. 타입 변수 대신 지정된 타입을 '대입된 타입(매개변수화된 타입)' 이라 한다.
참조변수에서 지정한 지네릭 타입과 생성자에 지정한 지네릭 타입은 항상 일치해야 한다.
Example<String> e = new Example<String>();
Example<String> e2 = new Example<>(); // JDK 1.7부터 뒤에 지네릭 타입 생략 가능.
지네릭 타입이 아닌 클래스의 타입간 다형성은 적용 가능하다. 매개변수의 다형성 또한 성립한다.
List<Fruit> list = new ArrayList<Fruit>();
list.add(new Apple());
list.add(new Orange());
Iterator<E>
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
Iterator에도 지네릭스가 적용되어 next()
를 사용할 때 형변환을 생략할 수 있다.
HashMap<K, V>
HashMap의 key와 value 의 타입을 지정해줄 수 있다. K에는 key의 타입을, V에는 value의 타입을 지정해준다.
get()
, keySet()
, values()
등을 사용할 때 형변환을 생략할 수 있다.
지네릭 타입에 extends를 사용하면 특정 타입의 자손들만 대입할 수 있게 제한할 수 있다.
인터페이스를 구현해야 한다는 제약 또한 implements가 아닌 extends 를 사용한다.
class Example<T extends Fruit> {}
특정 타입의 자손이면서, 특정 인터페이스를 구현한 클래스로 제한하는 경우 '&' 를 사용하여 연결한다.
class Example2<T extends Fruit & Eatable> {}
지네릭 타입에 다형성을 적용할 수 있도록 하는 방법. ?
를 사용한다.
: T와 그 자손들만 대입 가능 : T와 그 자손들만 대입 가능 : 제한 없음. 와 동일
메서드 선언부에 지네릭 타입이 선언된 메서드.
static <T> void sort(List<T> list, Comparator<? super T> c)
아래의 예시에서와 같이 클래스의 타입 매개변수 <T>
와 메서드 타입 매개변수 <T>
는 타입문자만 같을 뿐, 다르다.
지네릭 메서드에서의 타입 매개변수는 메서드 영역에서만 유효하다. 때문에 원래 static 멤버에서는 타입 매개변수를 사용할 수 없지만, 메서드에 지네릭 타입을 선언하는 것은 가능하다.
class FruitBox<T> {
...
static <T> void sort(List<T> list, Comparator<? super T> c) {
...
}
}
메서드를 호출할 때마다 타입을 대입해야 하지만, 대부분 생략 가능하다.
만약 대입된 타입을 생략하지 않는 경우, 참조변수나 클래스 이름을 생략할 수 없다.
와일드 카드 vs 지네릭 메서드
와일드 카드 : 하나의 참조변수로 서로 다른 타입이 대입된 여러 지네릭 객체를 다루기 위한 것
지네릭 메서드 : 메서드를 호출할 때마다 다른 지네릭 타입을 대입할 수 있게 한 것
컴파일러는 지네릭 타입을 제거하고, 필요한 곳에 형변환을 넣어준다.
왜? 하위 호환성 때문에. (지네릭스가 JDK 1.5부터 도입되었기 때문)
지네릭 타입의 경계 제거
지네릭 타입 <T>
가 Object로 치환된다. 아래와 같이 <T extends Fruit>
과 같이 제한을 주면, 해당 타입의 최고 조상인 Fruit으로 치환된다.
class Box<T extends Fruit> { void add(T t) {...}}
⇩⇩
class Box { void add(Fruit t) {...}}
타입이 일치하지 않으면, 형변환을 추가한다.
T get(int i) { return list.get(i);}
⇩⇩
Fruit get(int i) { return (Fruit) list.get(i);}
와일드 카드가 포함되어 있으면, 적절한 타입으로 형변환이 추가된다.
관련된 상수를 같이 묶어놓은 것.
열거형을 정의하려면 다음과 같이 작성한다.
enum 열거형이름 {상수명1, 상수명2, ... }
열거형을 이용하면 자동적으로 정수값을 0부터 할당해주어 상수가 많은 경우, 간단하게 상수를 선언할 수 있다.
class Card {// 0 1 2 3
enum Kind { CLOVER, HEART, DIAMOND, SPADE }
enum Value { TWO, THREE, FOUR } final Kind kind; final Value value;}
열거형 상수간 비교에는 ==
과 compareTo()
를 사용 가능하다. 비교 연산자(>
, <
) 는 사용 불가.
Java에서는 열거형 상수의 값과 타입을 모두 체크하기 때문에 Kind.CLOVER
와 Value.TWO
를 비교했을 때 값이 0으로 같아도 컴파일 에러가 발생한다.
java.lang.Enum은 모든 열거형의 조상이다.
메서드 | 설명 |
---|---|
Class getDeclaringClass() | 열거형의 Class 객체 반환 |
String name() | 열거형 상수 이름을 문자열로 반환 |
int ordinal() | 열거형 상수가 정의된 순서 반환(0부터 시작) |
T valueOf(Class enumType, String name) | 열거형 enumType 에서 name과 일치하는 열거형 상수 반환 |
메서드 | 설명 |
---|---|
values() | 열거형의 모든 상수 출력에 사용 |
valueOf(String name) | 열거형 상수의 이름으로 문자열 상수 참조 가능 |
불연속적인 열거형 상수의 경우, 원하는 값을 괄호 안에 적는다. 괄호() 를 사용하려면 정수를 저장할 인스턴스 변수와 생성자를 새로 추가해줘야 한다.(열거형의 생성자는 제어자가 private)
enum Direction { EAST(1), SOUTH(5), WEST(-1), NORTH(10) }
주석처럼 프로그래밍 언어에 영향을 미치지 않지만, 유용한 정보를 제공하는 것.
애너테이션 | 설명 |
---|---|
@Override | 오버라이딩한 메서드인지 컴파일러가 체크하게 함 |
@Deprecated | 사용을 권장하지 않는 필드나 메서드에 붙임 |
@SuppressWarnings | 특정 경고메시지가 나오지 않도록 함 |
@SafeVarargs | 지네릭스 타입의 가변인자에 사용 |
@FunctionalInterface | 함수형 인터페이스임을 알림 |
@Native | native 메서드에 참조되는 상수 앞에 붙임 |
↓ 아래는 표준 애너테이션 | 애너테이션 만들 때 사용하는 애너테이션 |
@Target | 애너테이션이 어디에 적용되는지 지정 |
@Documented | 애너테이션 정보를 javadoc으로 작성된 문서에 포함시킴 |
@Inherited | 애너테이션을 상속하게 함 |
@Retention | 애너테이션 유지 범위 설정 |
@Repeatable | 같은 애너테이션을 여러번 사용할 수 있게 함. 이 애너테이션들을 하나로 묶을 컨테이너 애너테이션(value라는 이름의 배열 타입 요소 선언) 추가 정의해야 함. |
@interface 애너테이션이름 { 타입 요소이름(); ...}
애너테이션 안에 선언된 메서드
애너테이션 요소는 추상 메서드로, 구현할 필요 없이 애너테이션을 적용할 때 지정해준다.
애너테이션 요소는 기본값을 가질 수 있다. 애너테이션 적용시에 값을 지정해주지 않으면 기본값이 사용된다.
ex)
@interface DateTime {
String yymmdd() default "991231"
String hhmmss();
}
@DateTime(yymmdd = "211012", hhmmss = "093021")public class Example {}
애너테이션 요소가 단 하나뿐이고, 요소 이름이 value인 경우, 요소를 지정할 때 이름을 생략할 수 있다.
@interface ExampleAnno { String value();}
@ExampleAnno("hello")class Example {}
요소 타입이 배열일 경우, 괄호{ } 를 사용하여 여러 값을 지정할 수 있다. 값이 하나인 경우는 괄호{ }를 생략 가능하다.
요소가 하나도 정의되지 않은 애너테이션. @Override가 대표적이다.
모든 애너테이션의 조상은 Annotation 이라는 인터페이스이다. 상속은 허용되지 않지만, Annotation 인터페이스에 정의된 메서드들을 호출 가능하다.
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
애너테이션 요소를 선언할 때, 반드시 지켜야 하는 규칙
- 요소의 타입은 기본형, String, enum, 애너테이션, Class 만 허용
- ( ) 안에 매개변수 선언 불가
- 예외 선언 불가
- 타입 매개변수로 정의 불가
다음과 같은 경우 에러가 발생한다.
@Interface WrongAnno {
String test1(int i);
String test2() throws Exception;
ArrayList<T> list();
}