메서드나 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능이다.
쉽게 말하면
지네릭스는 타입 파라미터다.
원래는 변수를 파라미터로 사용했는데
타입(자료형)도 파라미터로 사용할 수 있다는 말이다.
가장 흔히 쓰는 자료구조인 ArrayList가 대표적인 예다.
class myGenericClass<T>{
T item;
myGenericClass(T item){
this.item = item;
}
public T getItem(){
return item;
}
}
T를 타입파라미터로 받고
T타입의 변수가 선언되어 있는 클래스다.
만약 T(타입 파라미터) 에 String을 줬다면
MyGenericClass<String> myGenericsClass = new MyGenericClass<>("STRING");
System.out.println(myGenericsClass.getItem()); //결과: STRING
컴파일러가 이 코드를 아래와 같이 컴파일 해준다.
class myGenericClass<String>{
String item;
myGenericClass(String item){
this.item = item;
}
public String getItem(){
return item;
}
}
주의할 것은 static 메서드나 필드에서 타입파라미터를 사용할 수 없다는 것이다. static 은 해당 클래스의 모든 인스턴스에서 공유되는 것이기 때문이다.
지네릭스에서도 다형성을 적용할 수 있을까?
가능하다.
Fruit라는 클래스를 상속받는 Apple, Grape 클래스가 있다고 해보자.
타입 파라미터를 Fruit의 자손들만 받도록 제한할 수 있다.
MyGenericClass<Fruit> myGenericClass = new MyGenericClass<Apple>();
MyGenericClass<Fruit> myGenericClass2 = new MyGenericClass<Grape>();
타입파라미터에 extends 를 추가하면 된다.
class MyGenericClass<T extends Fruit>{
T item;
MyGenericClass(){}
MyGenericClass(T item){
this.item = item;
}
public T getItem(){
return item;
}
}
인터페이스의 경우에도 implements 가 아닌 extends를
사용하니 주의하자.
List 의 자손클래스의 인스턴스를 받아서
forEach로 요소를 하나씩 출력하는 메서드가 있다고 해보자.
public void printList(List<String> list){
list.forEach(i-> System.out.println(i));
}
이 메서드는 String 타입파라미터를 받는 List 만 파라미터로 받을 수 있다.
Integer나 다른 타입으로 받고 싶으면 어떻게 할까?
public void printList(List<String> list)
public void printList(List<Integer> list)
public void printList(List<Boolean> list)
이런 식으로 오버로딩해주면 될까?
하지만 컴파일 에러가 난다.
지네릭스는 컴파일러가 컴파일 할때만 사용하고 제거해버리기 때문이다.
때문에 위의 메서드들이 모두 같은 파라미터를 받는다고 보는 것이다.
String 과 Integer, Boolean 은 모두 Object의 자손이다.
때문에 아래의 코드처럼 바꿔주면 된다.
public void printList(List<?> list){
list.forEach(i-> System.out.println(i));
}
이게 바로 와일드 카드다.
만약 와일드카드의 자손들만 받도록 제한하고 싶으면
? 뒤에 extends를 붙혀주면 된다.
이것을 상한 제한이라고 한다.
public void printList(List<? extends Fruit> list){
list.forEach(i-> System.out.println(i));
}
반대로 와일드카드의 자손이 아닌 모든 부모들만 받도록 제한 할 수도 있다. 이것은 하한 제한이라고 한다.
public void printList(List<? super Apple> list){
list.forEach(i-> System.out.println(i));
}
위에서 static메서드는 타입파라미터를 사용하지 못한다고 했다.
하지만 지네릭 메서드를 이용하면 가능하다.
클래스에 타입파라미터를 정의한 것처럼
메서드에도 똑같이 가능하다는 것이다.
static <T> T myStaticMethod(T item){
return item;
}
정말 쓸모없는 메서드이지만 설명을 위해 작성했다.
만약 클래스의 타입파라미터 이름과 지네릭 메서드의 타입 파라미터 이름이 같아도 둘은 구분된다.
class MyGenericClass<T>{
T item;
//에러! this의 T와 메서드의 T는 다르다.
public <T> T myStaticMethod(T item){
this.item= item;
}
}
자바에 내장돼있는 클래스들과 인터페이스들은 대부분
지네릭을 사용하기 때문에 알아두면 라이브러리를 해석하는데
많은 도움이 될 듯 하다.