[JAVA] 제네릭
제네릭
- Java 5부터 도입된 기능으로, 타입 파라미터를 전달받아 객체나 메소드에서 제네릭 타입을 사용하는 기법
- 컴파일 시점에 타입을 체크하여 타입 안전성 보장
- 다양한 타입에서 작동하는 객체나 메소드 정의 가능
- 캐스팅(형변환) 사용이 불필요함
- 타입 파라미터에는 참조형만 전달 가능
- Java에서 제공하는 각 기본형에 대응하는 래퍼 클래스(Wrapper Class) 사용
- 제네릭 타입은 배열을 선언할 수 없음
Data<Integer>[] arrayOfData; //오류
기본형 | 래퍼 클래스 |
---|---|
boolean | Boolean |
char | Character |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
제네릭 클래스
1
2
3
4
5
6
7
8
9
10
11
class Data<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
- 클래스를 정의할 때 클래스명 뒤에 다이아몬드 연산자(
<>
)를 사용하여 타입 파라미터 선언 - 여러 개의 타입 파라미터 지정 가능
- 클래스 내부의 데이터 필드나 메소드에 타입 파라미터 사용 가능
- static 데이터 필드에는 사용할 수 없음
- 생성자 호출 시, 변수 선언부에 제네릭 타입이 명시되어 있다면 생성자의 타입 파라미터 생략 가능 (Java 7부터 컴파일러가 타입 추론 가능)
- ex)
List<Integer> list = new ArrayList<>();
- ex)
제네릭 메소드
1
2
3
4
5
public <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
- 타입 파라미터를 메소드의 반환형, 매개변수의 자료형, 지역 변수의 자료형으로 사용 가능
- 메소드를 정의할 때 접근 제어자와 반환형 사이에 타입 파라미터 선언 (반환형을 지정하지 않으면 타입 파라미터를 반환형으로 사용)
- 하나의 메소드로 여러 타입의 데이터를 처리할 때 유용함
- static, non-static 모두 제네릭 메소드로 정의 가능
- 메소드에 전달하는 매개변수로 메소드의 타입 추론이 가능한 경우, 타입 파라미터 생략 가능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
class Util {
// 제네릭 메소드
public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
return p1.getKey().equals(p2.getKey()) && p1.getValue().equals(p2.getValue());
}
}
public class Main {
public static void main(String args[]) {
Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2); //<Integer, String> 생략 가능
System.out.println(same);
}
}
제네릭 인터페이스
제네릭 클래스에서 제네릭 인터페이스 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface Pair<K, V> {
public K getKey();
public V getValue();
}
// 변수 선언부에서 지정된 타입 파라미터가 클래스와 인터페이스에 모두 적용
class OrderedPair<K, V> implements Pair<K, V> {
private K key;
private V value;
public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
public class Main {
public static void main(String args[]) {
Pair<String, Integer> p1 = new OrderedPair<> ("Even", 8);
Pair<String, String> p2 = new OrderedPair<> ("hello", "java");
}
}
일반 클래스에서 제네릭 인터페이스 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface Pair<K, V> {
public K getKey();
public V getValue();
}
// 클래스를 정의할 때 제네릭 인터페이스에 타입 파라미터 선언
class MyPair implements Pair<String, Integer> {
private String key;
private Integer value;
public MyPair(String key, Integer value) {
this.key = key;
this.value = value;
}
public String getKey() { return key; }
public Integer getValue() { return value; }
}
public class Main {
public static void main(String args[]) {
MyPair mp = new MyPair("test", 1);
}
}
Raw 타입
- 타입 파라미터 없이 사용되는 제네릭 타입
- 타입 파라미터를 Object 타입으로 처리함
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ArrayList<E> implements List<E> {
public boolean add(E, e) {...}
public E get(int index) {...}
public E remove(int index) {...}
}
public class Main {
public static void main(String args[]) {
List list1 = new ArrayList(); // Raw 타입: 타입 파라미터를 Object 타입으로 취급
list1.add("hello"); // Object 타입으로 저장됨
String s1 = (String) list1.get(0); // Object->String 형변환 필요
List<String> list2 = new ArrayList<String>(); // 제네릭 타입 사용
list2.add("hello"); // String 타입으로 저장됨
String s2 = list2.get(0); // 형변환 필요 없음
}
}
제네릭 타입 제한
- 타입 파라미터에 적용 가능한 자료형의 상한을 제한하는 것
<T extends 제한타입>
- 타입 파라미터는 제한타입이거나 제한타입의 하위 클래스여야 함
1
2
3
4
5
6
7
8
class Data<T extends Number> {...}
public class Main {
public static void main(String args[]) {
Data<Integer> data1 = new Data<>(20);
Data<String> data2 = new Data<>("Hello"); //오류: The type String is not a valid substitute for the bounded parameter <T extends Number> of the type Data<T>
}
}
인터페이스 타입 제한
- 인터페이스를 제네릭 제한 타입으로 선언하여, 해당 인터페이스를 구현하는 클래스만 타입 파라미터로 제한할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
interface Readable {...}
// Readable 인터페이스를 구현한 클래스
class Student implements Readable {...}
// 타입 파라미터에 Readable 인터페이스를 구현한 클래스만이 올 수 있게 됨
class School <T extends Readable> {...}
public class Main {
public static void main(String args[]) {
School<Student> a = new School<Student>();
}
}
다중 인터페이스 타입 제한
&
연산자를 이용하면 해당 인터페이스들을 모두 구현한 클래스만 타입 파라미터로 제한할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Readable {}
interface Closeable {}
// Readable 인터페이스만 구현한 클래스
class Student implements Readable {}
// 인터페이스 Readable과 Closeable를 구현한 클래스만 제네릭 타입에 올 수 있음
class School <T extends Readable & Closeable> {}
public class Main {
public static void main(String args[]) {
School<Student> a = new School<Student>(); //오류: Readable과 Closeable를 모두 구현해야 함
}
}
제네릭 클래스의 형변환
- 일반 클래스는 상속 관계에 있는 클래스 간에 자동 형변환이 가능하지만, 제네릭 클래스는 상속 관계여도 형변환이 되지 않는다.
- 만약 형변환이 필요하다면, 와일드카드를 사용하여 우회할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
// 일반적인 상속 관계에서의 형변환
Object parent = new Object();
Integer child = new Integer(1);
parent = child; //업캐스팅 가능
Object parent2 = new Integer(1);
Integer child2;
child2 = (Integer) parent2; //다운캐스팅 가능
// 제네릭에서는 불가능
ArrayList<Object> parent = new ArrayList<>();
ArrayList<Integer> child = new ArrayList<>();
parent = child; //오류: 업캐스팅 불가능
child = parent; //오류: 다운캐스팅 불가능
상한 경계 와일드카드(upper bounded)
<? extends T>
- T 또는 T의 하위 클래스만 타입 파라미터로 사용 가능
1
2
3
4
5
6
7
8
9
ArrayList<? extends Object> parent = new ArrayList<>();
ArrayList<? extends Number> child = new ArrayList<>();
ArrayList<? extends Integer> child2 = new ArrayList<>();
ArrayList<Integer> child3 = new ArrayList<>();
//제네릭 타입 업캐스팅
parent = child;
parent = child2;
parent = child3;
하한 경계 와일드카드(lower bounded)
<? super T>
- T 또는 T의 상위 클래스만 타입 파라미터로 사용 가능
1
2
3
4
5
6
7
8
9
ArrayList<Object> parent = new ArrayList<>();
ArrayList<? super Object> parent2 = new ArrayList<>();
ArrayList<? super Number> parent3 = new ArrayList<>();
ArrayList<? super Integer> child = new ArrayList<>();
//제네릭 타입 다운캐스트
child = parent;
child = parent2;
child = parent3;
비한정적 와일드카드(unbounded)
<?>
- 모든 타입 허용
참고사이트
This post is licensed under CC BY 4.0 by the author.