데이터를 넣고 뺄 수 있으며 데이터는 하나의 타입(String이라면 String, Integer이라면 Integer만)만 쓸 수 있는 박스를 만들고싶다. 어떻게 만들 수 있을지 고민해보자.
첫 번째 방법은 필요한 데이터 타입마다 박스 클래스를 만드는 것이다. 장점은 IntegerBox
에는 Integer
외에 다른 클래스의 데이터가 들어가지 못하는 것이다. 반대로 단점은 데이터 타입외 외에 모든 코드와 로직이 같은데 일일이 클래스를 만들어야 하기 때문에 코드 중복의 문제가 있다.
public class IntegerBox {
private Integer value;
public void set(Integer value) {
this.value = value;
}
public Integer get() {
return value;
}
}
public class StringBox {
private String value;
public void set(String value) {
this.value = value;
}
public String get() {
return value;
}
}
모든 클래스의 조상인 Object
클래스를 활용하면 하나의 Box 클래스만 만들어 재사용할 수 있다. 하지만, 이 경우에는 타입 안정성을 확보하지 못한다. 아래 코드에서 integerBox
객체로는 Integer 데이터만 관리하고 싶다.
하지만 다른 개발자가 String 데이터를 실수로 넣었다. 그리고 난 Object → Integer
로 다운캐스팅을 하여 사용하려고 하지만 캐스팅 에러가 발생한다.
public class ObjectBox {
private Object value;
public void set(Object value) {
this.value = value;
}
public Object get() {
return value;
}
}
...
public static void main(String[] args) {
ObjectBox integerBox = new ObjectBox();
integerBox.set("문자100");
Integer result = (Integer) integerBox.get(); // String -> Integer 캐스팅 예외
System.out.println("result = " + result);
}
위 두 케이스의 단점을 보완하고 장점만 가져오기 위해 제네릭(Generic)
이라는 개념이 등장했다. 아래 GenericBox 클래스에서 T는 클래스를 정의하는 시점에서 타입이 정해지지 않고, GenericBox<Integer> integerBox = new GenericBox<>();
로 객체를 생성하는 시점에서 정해진다.
public class GenericBox<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
...
public static void main(String[] args) {
GenericBox<Integer> integerBox = new GenericBox<>();
integerBox.set(10);
// integer : 10
System.out.println("integer : " + integerBox.get());
}
integerBox를 생성할 때 T는 Integer로 바뀌며 String과 Double 같이 다른 데이터 타입이 들어가면 컴파일러가 잡아준다. 코드로 표시하면 다음과 같이 바뀐단 말이다.
박스 클래스를 내가 관리하고 싶은 데이터 타입마다 생성 시점에 정의해서 사용할 수 있다. 박스 클래스 하나로 재사용하며 생성된 후에는 다른 타입의 데이터가 들어갈 수 없기 때문에 타입의 안정성까지 보장된다.
// Integer 타입만 허용, 컴파일 오류
GenericBox<Integer> integerBox = new GenericBox<>();
integerBox.set("asda");
// T -> Integer
public class GenericBox {
private Integer value;
public void set(Integer value) {
this.value = value;
}
public Integer get() {
return value;
}
}
한 번 더 나아가면 Animal → Dog/Cat
로 부모-자식 관계가 되어있을 때, 제네릭으로 Animal
을 설정하면 그 안에 Dog와 Cat 모두 넣을 수 있다.