학습 주제
- 제네릭 사용법
- 제네릭 주요 개념 (바운디드 타입, 와일드 카드)
- 제네릭 메소드 만들기
- Erasure
제너릭
Java 1.5 부터 추가
클래스, 메서드 내에서 사용할 자료형을 클래스를 생성하면서 지정하여 사용
제너릭은 다이아몬드 연산자(Diamon operator)라고 읽음.
<>으로 표기
보편적인 제너릭 타입 매개변수 명명
- E - Element
- K - Key
- N - Number
- T - Type
- V - Value
제너릭을 쓰지않고 모든 타입이든지 받아 전달하는 객체를 만들어야한다고 가정하자.
쉽게 Object를 필드로 생각할 수 있다.
public class DataTransfer {
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
public class Main {
public static void main(String[] args) {
DataTransfer data = new DataTransfer();
DataTransfer data2 = new DataTransfer();
data.setData(100);
data2.setData("A");
Integer num = (Integer) data.getData();
String text = (String) data2.getData();
System.out.printf("%d, text" ,num, text);
}
}
하지만 이 경우, 항상 자료를 꺼내 쓸 때 명시적으로 형변환이 필요하고 오류가 발생할 수 있다.
이러한 문제 상황에서 제네릭을 고려해볼 수 있다.
제너릭 선언
(접근 제어자) class 클래스명<참조 자료형> {
}
제너릭을 이용하면 다음과 같이 변경할 수 있다.
public class DataTransfer<T> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
public class Main {
public static void main(String[] args) {
DataTransfer<Integer> data = new DataTransfer<Integer>();
DataTransfer<String> data2 = new DataTransfer<String>();
data.setData(100);
data2.setData("A");
Integer num = data.getData();
String text = data2.getData();
System.out.printf("%d, text" ,num, text);
}
}
Java 7 부터는 생성자를 추론해 타입을 추론할 수 있다.
List<String> group = new ArrayList<>();
제너릭은 한 개 이상도 사용가능하다.
public class DataTransfer<T, F> {
T t;
F f;
public DataTransfer(T t, F f) {
this.t = t;
this.f = f;
}
public T getT() {
return t;
}
public F getF() {
return f;
}
public void printAll() {
System.out.printf(t + " : " + f);
}
}
public class Main {
public static void main(String[] args) {
DataTransfer<String, String> data = new DataTransfer<>("AA", "BB");
data.printAll();
}
}
Bounded Type
메서드가 받을 수 있는 타입을 제한하여 특정 타입만 받게 설정할 수도 있다.
public class DataTransfer<T extends String, F extends String> {
T t;
F f;
public DataTransfer(T t, F f) {
this.t = t;
this.f = f;
}
public T getT() {
return t;
}
public F getF() {
return f;
}
public void printAll() {
System.out.printf(t + " : " + f);
}
}
와일드카드 제네릭 타입
메서드 인자 중에 제네릭 타입으로 넘어올 때 어떤 제네릭 타입이 들어올지 알 수 없을 때 사용하는 타입을 '?'으로 작성
public class Basket<T extends Number> {
int sum;
public void add(T num) {
this.sum += num.intValue();
}
public int getSum() {
return sum;
}
}
public class Pay {
int result = 0;
// Pay에서는 자료형이 Integer인지 Float인지 모름
public void calculate(Basket<?> basket) {
result += basket.getSum();
}
}
와일드 카드를 활용한 제한된 자료형
- <? extneds 상위 클래스> : 상위 클래스를 상속 받은 자료형으로 제한
- <? super 하위 클래스> : 하위 클래스가 상속 받고 있는 상위 클래스 자료형으로 제한
제너릭 메서드
재너릭 메서드 메서드의 반환될 자료형 앞에 다이아몬드 연산자를 넣어서 제너릭 타입 선언
[접근 제어자](static) <T> [반환된 자료형] [메서드명 (T t) {
return 반환 자료형 리터럴;
}
extends 키워드를 사용하여 자료형을 제한할 수도 있다.
public class DataTransfer {
String value;
public <T> void setValue(T t) {
this.value = t.toString();
}
public String getValue() {
return value;
}
}
Erasure
컴파일 타임에만 원소 타입을 검사하고 런타임에는 타입 정보를 소거한다.
즉, 실체화 되지 않는다.
참고 : www.baeldung.com/java-type-erasure
public static <E> boolean containsElement(E [] elements, E element){
for (E e : elements){
if(e.equals(element)){
return true;
}
}
return false;
}
byte코드를 살펴보면 타입정보는 소거되어 Object로 바뀌었다.
/ access flags 0x9
// signature <E:Ljava/lang/Object;>([TE;TE;)Z
// declaration: boolean containsElement<E>(E[], E)
public static containsElement([Ljava/lang/Object;Ljava/lang/Object;)Z
L0
LINENUMBER 11 L0
ALOAD 0
ASTORE 2
ALOAD 2
ARRAYLENGTH
ISTORE 3
ICONST_0
ISTORE 4
L1
FRAME APPEND [[Ljava/lang/Object; I I]
ILOAD 4
ILOAD 3
IF_ICMPGE L2
ALOAD 2
ILOAD 4
AALOAD
ASTORE 5
L3
LINENUMBER 12 L3
ALOAD 5
ALOAD 1
INVOKEVIRTUAL java/lang/Object.equals (Ljava/lang/Object;)Z
IFEQ L4
L5
LINENUMBER 13 L5
ICONST_1
IRETURN
L4
LINENUMBER 11 L4
FRAME SAME
IINC 4 1
GOTO L1
L2
LINENUMBER 16 L2
FRAME CHOP 3
ICONST_0
IRETURN
L6
LOCALVARIABLE e Ljava/lang/Object; L3 L4 5
// signature TE;
// declaration: e extends E
LOCALVARIABLE elements [Ljava/lang/Object; L0 L6 0
// signature [TE;
// declaration: elements extends E[]
LOCALVARIABLE element Ljava/lang/Object; L0 L6 1
// signature TE;
// declaration: element extends E
MAXSTACK = 2
MAXLOCALS = 6
}