12. 지네릭스, 열거형, 애너테이션
1. 지네릭스(Generics)
1-1. 지네릭스
컴파일할 때 타입 체크를 해주는 기능. 클래스명 옆에 <> 를 붙여서 특정한 타입의 객체만 담을 수 있도록 지정해주는 것.
타입 안전성을 높이고, 형변환을 생략할 수 있어 코드가 간결해진다는 장점을 갖는다.
1-2. 용어 정리
class Example<T> {}
지네릭스가 적용되어 있는 클래스를 지네릭 클래스라 하며, Example을 원시타입이라 한다.
<> 안의 T 를 타입 변수라고 하며, 객체를 생성할 때는 타입 변수에 실제 타입을 지정해주어야 한다. 타입 변수 대신 지정된 타입을 '대입된 타입(매개변수화된 타입)' 이라 한다.
1-3. 다형성
-
참조변수에서 지정한 지네릭 타입과 생성자에 지정한 지네릭 타입은 항상 일치해야 한다.
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());
1-4. 컬렉션
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()
등을 사용할 때 형변환을 생략할 수 있다.
1-5. 제한된 지네릭 클래스
지네릭 타입에 extends를 사용하면 특정 타입의 자손들만 대입할 수 있게 제한할 수 있다.
인터페이스를 구현해야 한다는 제약 또한 implements가 아닌 extends 를 사용한다.
class Example<T extends Fruit> {}
특정 타입의 자손이면서, 특정 인터페이스를 구현한 클래스로 제한하는 경우 '&' 를 사용하여 연결한다.
class Example2<T extends Fruit & Eatable> {}
1-6. 제약
- 타입 변수에 대입은 인스턴스마다 다르게 할 수 있다.
- static 멤버에는 모든 인스턴스에 공통이므로, 타입 변수를 사용할 수 없다.
- 지네릭 타입의 배열을 생성할 수 없다. 참조변수 선언만 가능.(new 연산자는 컴파일 시에 타입이 무엇인지 정확히 알아야 하기 때문)
1-7. 와일드 카드
지네릭 타입에 다형성을 적용할 수 있도록 하는 방법. ?
를 사용한다.
: T와 그 자손들만 대입 가능 : T와 그 자손들만 대입 가능 : 제한 없음. 와 동일
1-8. 지네릭 메서드
메서드 선언부에 지네릭 타입이 선언된 메서드.
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 지네릭 메서드
와일드 카드 : 하나의 참조변수로 서로 다른 타입이 대입된 여러 지네릭 객체를 다루기 위한 것
지네릭 메서드 : 메서드를 호출할 때마다 다른 지네릭 타입을 대입할 수 있게 한 것
1-9. 지네릭 타입 형변환
- 지네릭 타입과 원시타입간의 형변환은 가능하지만, 바람직하지 않다.
- 서로 다른 타입이 대입된 지네릭 타입간의 형변환은 불가능하다.
- 와일드 카드가 사용된 지네릭 타입으로는 형변환이 가능하다.
1-10. 지네릭 타입 제거
컴파일러는 지네릭 타입을 제거하고, 필요한 곳에 형변환을 넣어준다.
왜? 하위 호환성 때문에. (지네릭스가 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);}
와일드 카드가 포함되어 있으면, 적절한 타입으로 형변환이 추가된다.
2. 열거형(enum)
2-1. 열거형
관련된 상수를 같이 묶어놓은 것.
열거형을 정의하려면 다음과 같이 작성한다.
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으로 같아도 컴파일 에러가 발생한다.
2-2. 열거형의 조상 - java.lang.Enum
java.lang.Enum은 모든 열거형의 조상이다.
메서드
메서드 | 설명 |
---|---|
Class getDeclaringClass() | 열거형의 Class 객체 반환 |
String name() | 열거형 상수 이름을 문자열로 반환 |
int ordinal() | 열거형 상수가 정의된 순서 반환(0부터 시작) |
T valueOf(Class enumType, String name) | 열거형 enumType 에서 name과 일치하는 열거형 상수 반환 |
컴파일러가 자동으로 추가해주는 메서드
메서드 | 설명 |
---|---|
values() | 열거형의 모든 상수 출력에 사용 |
valueOf(String name) | 열거형 상수의 이름으로 문자열 상수 참조 가능 |
2-3. 열거형에 멤버 추가
불연속적인 열거형 상수의 경우, 원하는 값을 괄호 안에 적는다. 괄호() 를 사용하려면 정수를 저장할 인스턴스 변수와 생성자를 새로 추가해줘야 한다.(열거형의 생성자는 제어자가 private)
enum Direction { EAST(1), SOUTH(5), WEST(-1), NORTH(10) }
3. 애너테이션(Annotation)
3-1. 애너테이션
주석처럼 프로그래밍 언어에 영향을 미치지 않지만, 유용한 정보를 제공하는 것.
애너테이션 | 설명 |
---|---|
@Override | 오버라이딩한 메서드인지 컴파일러가 체크하게 함 |
@Deprecated | 사용을 권장하지 않는 필드나 메서드에 붙임 |
@SuppressWarnings | 특정 경고메시지가 나오지 않도록 함 |
@SafeVarargs | 지네릭스 타입의 가변인자에 사용 |
@FunctionalInterface | 함수형 인터페이스임을 알림 |
@Native | native 메서드에 참조되는 상수 앞에 붙임 |
↓ 아래는 표준 애너테이션 | 애너테이션 만들 때 사용하는 애너테이션 |
@Target | 애너테이션이 어디에 적용되는지 지정 |
@Documented | 애너테이션 정보를 javadoc으로 작성된 문서에 포함시킴 |
@Inherited | 애너테이션을 상속하게 함 |
@Retention | 애너테이션 유지 범위 설정 |
@Repeatable | 같은 애너테이션을 여러번 사용할 수 있게 함. 이 애너테이션들을 하나로 묶을 컨테이너 애너테이션(value라는 이름의 배열 타입 요소 선언) 추가 정의해야 함. |
3-2. 애너테이션 타입 정의
@interface 애너테이션이름 { 타입 요소이름(); ...}
3-3. 애너테이션의 요소
애너테이션 안에 선언된 메서드
애너테이션 요소는 추상 메서드로, 구현할 필요 없이 애너테이션을 적용할 때 지정해준다.
애너테이션 요소는 기본값을 가질 수 있다. 애너테이션 적용시에 값을 지정해주지 않으면 기본값이 사용된다.
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가 대표적이다.
3-4. 모든 애너테이션의 조상
모든 애너테이션의 조상은 Annotation 이라는 인터페이스이다. 상속은 허용되지 않지만, Annotation 인터페이스에 정의된 메서드들을 호출 가능하다.
package java.lang.annotation;
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
3-5. 애너테이션 요소의 규칙
애너테이션 요소를 선언할 때, 반드시 지켜야 하는 규칙
- 요소의 타입은 기본형, String, enum, 애너테이션, Class 만 허용
- ( ) 안에 매개변수 선언 불가
- 예외 선언 불가
- 타입 매개변수로 정의 불가
다음과 같은 경우 에러가 발생한다.
@Interface WrongAnno {
String test1(int i);
String test2() throws Exception;
ArrayList<T> list();
}
Author And Source
이 문제에 관하여(12. 지네릭스, 열거형, 애너테이션), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@nrudev/12.-지네릭스-열거형-애너테이션저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)