[Java] Generic (제네릭) 문법 / 필요한 이유
제네릭이 필요한 이유
- 첫 번째로 제네릭은 타입 언어에서 중복되거나 필요 없는 코드를 줄여준다.
- 두 번째로 그러면서도 제네릭은 타입 안정성을 해치지 않는다.
제네릭 제한점
1. static 사용불가
제네릭은 인스턴스 변수 취급되는데 static은 컴파일 된 시점부터 바로 사용가능하기에 인스턴스화가 필요 없다.
static T get() { ... } // 에러
static void set(T t) { ... } // 에러
2. 제네릭 배열은 생성 할 수 없다.
제네릭 클래스 / 메소드 사용 예시
package week04.gen;
// 제네릭은 클래스 또는 메서드에 사용 가능 !
// <>안에 들어가야 할 타입을 명시해주면 된다.
public class Generic<T> {
// 2. 내부 필드에 String
private T t;
// 3. method의 return type도 String
public T get() {
return this.t;
}
// 4. method의 매개변수 type도 String
public void set(T t) {
this.t = t;
}
public static void main(String[] args) {
// 1. Generic 클래스 사용 T를 -> String으로 만들음.
Generic<String> stringGeneric = new Generic<>();
// 5. method의 매개변수 type이 String으로 사용되는 것을 볼 수 있다.
stringGeneric.set("Hello World");
String tValueTurnOutWithString = stringGeneric.get();
System.out.println(tValueTurnOutWithString);
}
}
제네릭 문법
1. 다수의 타입 변수를 사용할 수 있다.
public class Generic<T, U, E> {
public E multiTypeMethod(T t, U u) { ... }
}
Generic<Long, Integer, String> instance = new Generic();
instance.multiTypeMethod(longVal, intVal);
2. 다형성 즉 상속과 타입의 관계는 그대로 적용된다.
3. 제네릭 타입으로는 기본형(int, char, double, long 등)이 아닌 래퍼 클래스 및 참조형 변수(String, Integer, Double, Long 등) 만 들어갈 수 있다.
4. 와일드카드
와일드카드를 통해 제네릭의 제한을 구체적으로 정할 수 있다.
- <? extends T> : T와 그 자손들만 사용 가능
- <? super T> : T와 그 조상들만 가능
- <?> : 제한 없음
public class ParkingLot<T extends Car> { ... }
ParkingLot<BMW> bmwParkingLot = new ParkingLot();
ParkingLot<Iphone> iphoneParkingLot = new ParkingLog(); // error!
이렇게 제한을 하는 이유는 다형성 때문이다. 위의 코드에서, T는 Car의 자손 클래스들이라고 정의했기 때문에, 해당 클래스 내부에서 최소 Car 객체에 멤버를 접근하는 코드를 적을 수 있다. 반대로 그러한 코드들이 있을 여지가 있기 때문에, Car 객체의 자손이 아닌 클래스는 제한해주어야 한다.
제네릭 메서드 별도 선언
메서드를 스코프로 제네릭을 별도로 선언할 수 있다.
static <T> void sort(List<T> list, Comparator<? super T> c) { ... }
제너릭 클래스가 없이 제네릭 메소드만 있어도 사용이 가능하다. 이렇게 반환 타입 앞에 <> 제네릭을 사용한 경우, 해당 메서드에만 적용되는 제네릭 타입 변수를 선언할 수 있다.
참고로 메서드 제어자로 Static 사용 가능하다. 제네릭 메소드의 제네릭 타입변수는 해당 메소드에만 적용되기 때문에 메소드 하나를 기준으로 선언하고 사용할 수 있기때문이다.
주의할 점
public class Generic<T, U, E> {
// Generic<T,U,E> 의 T와 아래의 T는 이름만 같을뿐 다른 변수
static <T> void sort(List<T> list, Comparator<? super T> c) { ... }
}
같은 이름의 변수를 사용했다고 해도 제네릭 메소드의 타입변수는 제네릭 클래스의 타입변수와 다르다. 위 코드의 두 T는 같은 것이 아니다. 따로 따로 선언된 것이다.
자바에서 실제 제네릭이 사용된 예시 : List
자바에서 많이 사용하는 컬렉션인 List 인터페이스도 제네릭으로 구현되어있다.
// 실제 java.util의 List 코드
public interface List<E> extends Collection<E> {
int size();
boolean isEmpty();
...
boolean addAll(Collection<? extends E> c);
boolean add(E e);
...
}