자바의 제네릭에 대해 학습하세요.
제네릭
제네릭 타입
public class ArrayList<E> extends AbstractList<E> {
private transient E[] elementData;
public boolean add(E o){...}
public E get(int index){...}
...
}
이외 관련 용어
한글 용어 | 영문 용어 | 예 |
---|---|---|
매개변수화 타입 | parameterized type | List<String> |
실제 타입 매개변수 | actual type parameter | String |
제네릭 타입 | generic type | List<E> |
정규 타입 매개변수 | formal type parameter | E |
비한정적 와일드카드 타입 | unbounded wildcard type | List<?> |
로 타입 | raw type | List |
한정적 타입 매개변수 | bounded type parameter | <E extends Number> |
재귀적 타입 한정 | recursive type bound | <T extends Comparable<T>> |
한정적 와일드카드 타입 | bounded wildcard type | List<? extends Number> |
제네릭 메서드 | generic method | static <E> List<E> asList(E[] a) |
타입 토큰 | type token | String.class |
출저: 이펙티프자바 3/E
제네릭 사용 전 코드
public class ArrayList extends AbstractList {
private transient Object[] elementData;
public boolean add(Object o){...}
public Object get(int index){...}
...
}
ArrayList squareList = new ArrayList();
squareList.add(new Square());
squareList.add(new Triangle()); //Object로 리스트에 들어간다
Square square = (Square) squareList.get(0); //꺼낼때 형변환 필요
Square square1 = (Square) squareList.get(1); //ClassCastException
제네릭 사용 후 코드
public class ArrayList<E> extends AbstractList<E> {
private transient E[] elementData;
public boolean add(E o){...}
public E get(int index){...}
...
}
ArrayList<Square> squareList = new ArrayList<>();
squareList.add(new Square());
//squareList.add(new Triangle()); //컴파일 에러
Square square = squareList.get(0); // 꺼낼때 형변환 불필요
ArrayList<Fruit> fruitList = new ArrayList<Fruit>();
ArrayList<Fruit> fruitList1 = new ArrayList<>(); // 생략 간능 부분
//ArrayList<Fruit> fruitList2 = new ArrayList<Apple>(); //컴파일 에러
타입 매개변수에 외부에서 지정할 타입인 매개변수화 타입(parameterized type)을 지정
생성자의 제네릭 타입은 컴파일러가 추정 가능할 경우 생략 가능
참조변수와 생성자에 지정하는 제네릭 타입은 일치해야 한다(어길시 컴파일 에러)
로 타입과 매개변수화 타입<Object> 지정의 차이
class MyGenericClass <T1, T2, ..., Tn> {
... //T1, T2 ... Tn 사용 가능
}
class MyGenericInterface <T1, T2, ..., Tn> {
... //T1, T2 ... Tn 사용 가능
}
,
로 구분타입 소거 관련 내용이 많으므로 타입 소거 모른다면 이후 장에서 읽고 보길 추천
Pair<int, char> p = new Pair<>(8, 'a'); // compile-time error
Pair<Integer, Character> p = new Pair<>(8, 'a');
Object
로 변경되는데, 프리미티브 타입은 Object
타입으로 형변환할 수 없기 때문에 Wrapper 클래스를 사용해야 한다public static <E> void append(List<E> list) {
E elem = new E(); // compile-time error
list.add(elem);
}
new
연산자는 힙 영역에 생성한 객체를 할당하는 데, 컴파일 시점에 타입 T가 뭔지 정확히 알아야 한다public class MobileDevice<T> {
private static T os; // compile-time error
}
static
필드에 코드 작성시 정적이지 않은 제네릭 사용시 혼란instanceof
연산자 사용 불가//타입 소거로 매개변수 타입 정보 알 수 없기 때문에 불가
public static <E> void rtti(List<E> list) {
if (list instanceof ArrayList<Integer>) { // compile-time error
// ...
}
}
//비한정적 와일드 카드 타입 사용시 가능(매개변수 타입은 상관 없으므로)
public static void rtti(List<?> list) {
if (list instanceof ArrayList<?>) { // OK; instanceof requires a reifiable type
// ...
}
}
List<Integer>[] arrayOfLists = new List<Integer>[2]; // compile-time error
배열은 제네릭 타입, 매개변수화 타입, 타입 매개변수로 사용할 수 없다. 즉, 코드를 new List<E>[], new List<String>[], new E[] 식으로 작성하면 컴파일할 때 제네릭 배열 생성 오류를 일으킨다
제네릭 배열을 만들지 못하게 막은 이유는 무엇일까? 타입 안전하지 않기 때문이다. 이를 허용한다면 컴파일러가 자동으로 생성한 형변환 코드에서 런타임에 ClassCastException이 발생할 수 있다. 런타임에 ClassCastException이 발생하는 일을 막아주겠다는 제네릭 타입 시스템의 취지에 어긋나는 것이다.
List<String>[] stringList = new List<String>[1]; //허용한다고 가정
List<Integer> intList = List.of(42);
Object[] objects = stringLists;
objects[0] = intList; //타입 소거로 인해 가능
String s = stringList[0].get(0); //타입 안정성 깨짐
출저: 이펙티브 자바 3/E
//A generic class cannot extend the Throwable class directly or indirectly.
//For example, the following classes will not compile:
// Extends Throwable indirectly
class MathException<T> extends Exception { /* ... */ } // compile-time error
// Extends Throwable directly
class QueueFullException<T> extends Throwable { /* ... */ // compile-time error
//A method cannot catch an instance of a type parameter:
public static <T extends Exception, J> void execute(List<J> jobs) {
try {
for (J job : jobs)
// ...
} catch (T e) { // compile-time error
// ...
}
}
//You can, however, use a type parameter in a throws clause:
class Parser<T extends Exception> {
public void parse(File file) throws T { // OK
// ...
}
}
public class Example {
public void print(Set<String> strSet) { }
public void print(Set<Integer> intSet) { }
}
class MyGenericClass <T1 extends X> {
...
}
extends
키워드 사용해 상위 바운드 설정extends X
로 상위 바운드 제한할 경우, 해당 제네릭 객체 생성시 매개변수화 타입으로 X 클래스나 인터페이스의 자손만 가능extends
조건 부여시 &
로 구분<T>
일 경우 해당 T
의 역할(메서드)에 대해 알 수 없다<T extends Comparable>
같은 경우 해당 T
가 compareTo()
를 구혔했음을 알기 때문에 compareTo()
같은 메서드 사용 가능List<Integer> integerList = new ArrayList<>();
List<Number> numberList = integerList; //매개변수화 타입에 따른 상속관계 성립한다면?
numberList.add(12.34);
Double d = intgerList.get(0); //???
명칭 | 형식 | 매개변수화 타입 범위 |
---|---|---|
Unbounded Wildcards | <?> | 제한 없음 |
Upper Bounded Wildcards | <? extends UpperBound> | UpperBound와 그 서브클래스만 가능 |
Lower Bounded Wildcards | <? super LowerBound> | LowerBound와 그 슈퍼클래스만 가능 |
//와일드 카드 사용 전
static void printListWithObject(List<Object> list) {
for (Number e : list) {
System.out.println(e);
}
}
//와일드 카드 사용 후
static void printListWithWildcard(List<? extends Number> list) {
for (Number e : list) {
System.out.println(e);
}
}
//
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
//printListWithObject(list); //컴파일 에러
printListWithWildcard(list);
}
printListWithObject(List<Object> list)
메서드의 파라미터로 List<Integer>
를 넣을 수 없다List<? extends Number>
로 Number
와 그 서브클래스로 매개변수화 타입을 허용했으므로 List<Integer>
을 파라미터로 해당 메서드 사용 가능List<? extends Integer> intList = new ArrayList<>(); //아래와 같이 자동 형변환
List<? extends Integer> intList = (List<? extends Integer>) new ArrayList<>();
class GenericClass<T> {
...
public <T> boolean genericMethod(T param){
...
}
}
class MyClass {
...
public <T> boolean genericMethod(T param){
...
}
static <T> void genericMethod(List<T> list){
...
}
}
static
메서드도 제네릭 메서드로 만들 수 있다boolean result = MyClass.<String>genericMethod("string");
boolean result = MyClass.genericMethod("string");
제네릭을 구현하기 위해 자바 컴파일러는 다음 유형 삭제를 적용
Object
로 변경Object
로 변경//Consider the following generic class that represents a node in a singly linked list:
public class Node<T> {
private T data;
private Node<T> next;
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
public T getData() { return data; }
// ...
}
//Because the type parameter T is unbounded, the Java compiler replaces it with Object:
public class Node {
private Object data;
private Node next;
public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
public Object getData() { return data; }
// ...
}
//In the following example, the generic Node class uses a bounded type parameter:
public class Node<T extends Comparable<T>> {
private T data;
private Node<T> next;
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
public T getData() { return data; }
// ...
}
//The Java compiler replaces the bounded type parameter T with the first bound class, Comparable:
public class Node {
private Comparable data;
private Node next;
public Node(Comparable data, Node next) {
this.data = data;
this.next = next;
}
public Comparable getData() { return data; }
// ...
}
관련 추가 링크
📑📌📜✏️