
타입 안정성을 유지하면서 더 깔끔한 코드를 위해
자바 프로그래밍을 하다 보면 다음과 같은 코드를 많이 작성했을 것이다.
public String a(int a, int b) { ... }
public String a(int a, long b) { ... }
public String a(int a, String b) { ... }
반환타입, 이름, 로직 모든게 동일하지만 매개변수 타입의 차이 때문에 여러번 선언한 적이 무조건 있다.
너무 귀찮은 나머지 자바 타입의 root인 Object를 사용해서 메소드를 만들고 싶던적도 있다.
public Object a(Object a, Object b) { ... }
근데 이렇게 되면 타입 안정성이 침해받게 된다.
1. a+b가 가능한가?
2. 연산을 위해 타입 통일이 필요한데 어떻게?등등..
위에서 동일 메소드 선언의 귀찮음 + Object를 사용해서 발생하는 많은 문제를 해결하는게 제네릭 문법이다.
public class 클래스명<변수명>{
~~
}
//////
public class Generic<T>{
private T t;
public T get(){return this.t;}
public T set(T t){this.t=t;}
}
////
psvm(String[] args){
Generic<String> sG = new Generic<>();
}
여기서 T는 컨벤션이라서 사실 아무거나 쓸 수 있는데 그냥 컨벤션이니까 T,U,V,E등을 사용하자.
Generic 클래스의 타입 변수를 T로 해서 필드, 메소드를 구현했고. 실제 인스턴스화는 아래 psvm에서 <String>으로 한게 보인다.
이렇게 인스턴스 생성시 실제 타입 변수를 넣어주면 위에 T가 String으로 행동하게 된다.
우리가 사용하는 ArrayList, Dequeue... 인스턴스 생성할때도 다 저렇게 하고 있었다. 우리는 사실 제네릭을 사용하고 있던것.
public class Generic<T,U,E>{
private T t;
public U a(T t, E e){..}
}
...
Generic<Integer,Integer, String> d = new Generic();
다형성은 유지된다.
부모가 제네릭이고 그 안에 자식클래스를 넘길 수 있다.
와일드카드를 통해 제네릭의 제한을 정할 수 있다.
<? extends T> : T와 그 자손들만 사용 가능<? super T> : T와 그 조상들만 가능<?> : 제한 없음public class ParkingLot<T extends Car> { ... }
ParkingLot<BMW> bmwParkingLot = new ParkingLot();
ParkingLot<Iphone> iphoneParkingLot = new ParkingLog(); // error!
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) { ... }
}
반환 타입앞 제네릭은 이 메소드에서만 사용하기 때문에 class의 T와는 별개의 타입이다.