제너릭(Generic)
자바 1.5부터 제너릭이 추가 되었다. 이 제너릭이란 기능은 현재 안쓰는 곳이 없고, 제너릭을 모르면 자바 공식 문서조차 읽을 수 없을 정도로 자바의 핵심적인 기능이다.
우선 제너릭이 사용되는 기본적인 상황을 먼저 보자
일단 제너릭을 바로 사용하기전에 제너릭을 사용하지 않을때 생기는 문제점을 보자.
public interface Manager {
String toString();
}
public class Book implements Manager {
public String toString(){
return "책";
}
}
public class Apple implements Manager {
public String toString(){
return "사과";
}
}
public class BookMgr {
private ArrayList<Manager> list;
public BookMgr(){
list = new ArrayList<>();
}
public void input(Manager manager){
list.add(manager);
}
public void printList(){
list.forEach(System.out::println);
}
}
public class Main {
static int result=0;
public static void main(String[] args) {
BookMgr bookMgr = new BookMgr();
bookMgr.input(new Book());
bookMgr.input(new Apple());
bookMgr.printList();
}
}
우선 위의 예를 보자 우선 manager인터페이스를 book,apple이 상속 받아서 사용하고 있다.
그리고 bookmgr클래스는 manager인터페이스를 상속받은 객체를 사용하는데 이름에서 보다 싶이 bookmgr은 책만을 취급하고 싶어한다.
하지만 main클래스에서 보면 apple도 들어간다. 인터페이스만으로는 타입 안전성을 보장하지 못한다.
이럴때 제너릭을 사용하면 타입세이프를 보장할 수 있다.
public class BookMgr<T> {
private ArrayList<T> list;
public BookMgr(){
list = new ArrayList<>();
}
public void input(T manager){
list.add(manager);
}
public void printList(){
list.forEach(System.out::println);
}
}
우선 위처럼 클래스옆에 <>를 추가하고 안에 적당한 문자(대체로 T)를 넣어주고 기존의 Manager의 위치를 모두 T로 바꿔준다. 이러면 BookMgr클래스를 생성할때 T에 해당하는 클래스를 지정하주면 아래의 T는 전부 그 클래스로 바뀐다.
public class Main {
static int result=0;
public static void main(String[] args) {
BookMgr<Book> bookMgr = new BookMgr<>();
bookMgr.input(new Book());
//bookMgr.input(new Apple()); 에러
bookMgr.printList();
}
}
위의 코드를 보면 알 수 있듯이 우리가 많이본 ArrayList와 같은 것이다.
이렇게 제너릭을 사용하면 타입안전성을 얻을 수 있다.
제한된 제너릭
다음으로 제너릭에 대해 알아볼 것은 제너릭의 타입 제한이다.
단순히 제너릭 타입을 T로만 해놓으면 모든 클래스를 사용 할 수 있다. 이런 문제를 해결하는 방법으로는 특정 클래스를 상속받는 클래스나 조상의 클래스를 사용하게끔하는 것이다.
사용방밥은 간단하다.
public class Person {
}
public class Male extends Person {
}
public class Student<T extends Person> {
}
public class Female {
}
위에서 부터 보면 Male은 person을 상속받고 있다. 아래의 Student의 제너릭 타입이 Person을 상속받는 것만 허용한다고 했다. 그러니 Male은 가능하지만 아래의 Female은 Student의 타입으로 사용할 수 없다.
이렇게 제너릭의 타입을 제한하는 방법도 알았다.
와일드카드
그럼 다음으로 제너릭의 주의점 한가지를 보고 문제를 해결할 수 있는 방법을 보자
제너릭을 다르게한다고해서 컴파일러는 오버로딩으로 인정하지 않는다.
한번 예를 보자
public void foo(ArrayList<Number> list){
}
public void foo(ArrayList<Integer> list){
}
위의 코드가 한 클래스에 있을때 매개변수의 제너릭 타입이 다르다고 해서 오버로딩이 된것이 아니다. 위의 코드는 에러를 일으킨다.
이럴때를 대비해서 와일드 카드가 있다.
와일드 카드는 메소드를 위한 제너릭이다.(제한된 제너릭과 매우 유사해서 필자도 처음 볼때 이해가 안되었지만 제한된 제너릭->클래스, 와일드카드-> 메소드라고 생각하니 좀 알기 쉬워졌다.)
사용방법은 아래와 같다.
public void foo(ArrayList<? extends Number> list){
}
위와 같이 T대신 ?를 사용하면된다. 이렇게 하면 Number클래스를 상속받은 클래스는 모두 사용가능해진다.
물론 상속말고 조상, 모든 클래스도 아래와 같이 할 수 있다.
public void foo2(ArrayList<? super Number> list){
}
public void foo3(ArrayList<?> list){
}