특정 타입만을 담는 박스가 존재한다고 가정할 때 타입이 늘어남에 따라 작성해야 하는 코드의 분량이 늘어나게 된다. 이런 문제를 해결하기 위해 제네릭을 도입하게 되었다.
public class ObjectBox {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
public class BoxMain {
public static void main(String[] args) {
ObjectBox integerBox = new ObjectBox();
integerBox.setValue(10);
Object value1 = integerBox.getValue();
Integer integer = (Integer) value1;
System.out.println("integer = " + integer);
ObjectBox stringBox = new ObjectBox();
stringBox.setValue("문자");
Object value2 = stringBox.getValue();
String string = (String) value2;
System.out.println("string = " + string);
// 문제가 발생하는 부분
integerBox.setValue("문자");
Integer value = (Integer) integerBox.getValue();
System.out.println("value = " + value);
}
}
integerBox
를 만들어서 숫자 10을 보관했다. 숫자를 입력하는 부분에는 문제가 없어 보이지만 integerBox.getxxx()
을 호출할 때 문제가 나타난다.integerBox
에는 정수형 타입의 값이 들어가는 것을 전제로 설계했으나 문자열 타입의 값이 들어가도 문제가 발생하지 않는 것처럼 보인다. 이런 잘못된 타입의 값을 전달하고 getxxx()
을 호출할 때 문제가 나타나게 된다.Object
이고 반환타입 역시 Object
이기 때문에 원하는 타입을 정확하게 받을 수 없고 항상 위험한 다운 캐스팅을 시도해야 한다. 결과적으로 이 방식은 타입 안전성이 떨어진다.public class GenericBox<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
<>
를 사용한 클래스를 제네릭 클래스라 한다. 이 기호를 다이아몬드라고 한다.Integer
, String
같은 타입을 미리 결정하지 않는다.<T>
와 같이 선언하면 제네릭 클래스가 된다. 여기서 T
를 타입 매개변수라 한다. 이 타입 매개변수는 이후에 Integer
, String
으로 변할 수 있다.T
타입이 필요한 곳에 T value
와 같이 타입 매개변수를 적어둔다.public class GenericBox<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
public class GenericBoxMain {
public static void main(String[] args) {
GenericBox<Integer> integerGenericBox = new GenericBox<>();
integerGenericBox.setValue(100);
// integerGenericBox.setValue("문자");
Integer integer = integerGenericBox.getValue();
System.out.println("integer = " + integer);
GenericBox<String> stringGenericBox = new GenericBox<>();
stringGenericBox.setValue("문자100");
// stringGenericBox.setValue(100);
String string = stringGenericBox.getValue();
System.out.println("string = " + string);
}
}
GenericBox
객체를 생성하는 시점에 원하는 타입을 마음껏 지정할 수 있다.GenericBox<Integer>
, GenericBox<String>
와 같은 코드가 실제 만들어지는 것은 아니다. 대신에 자바 컴파일러가 우리가 입력한 타입 정보를 기반으로 이런 코드가 있다고 가정하고 컴파일 과정에 타입 정보를 반영한다. 이 과정에서 타입이 맞지 않으면 컴파일 오류가 발생한다.<Integer>
가 두 번 나온다. 자바는 왼쪽에 있는 변수를 선언할 때의 <Integer>
를 보고 오른쪽에 있는 객체를 생성할 때 필요한 타입 정보를 얻을 수 있다. 따라서 오른쪽 코드에서 <Integer>
와 같은 타입 정보를 생략할 수 있다. 이렇게 자바가 스스로 타입 정보를 추론해서 개발자가 타입 정보를 생략할 수 있는 것을 타입 추론이라고 한다.T
Integer
직접 클래스를 만들고 제네릭을 도입해보자. Animal
관련 클래스들은 이후 예제에서도 사용하므로 generic.animal
이라는 별도의 패키지에서 관리해라.
public class AnimalMain1 {
public static void main(String[] args) {
Animal animal = new Animal("동물", 0);
Dog dog = new Dog("멍멍이", 100);
Cat cat = new Cat("야옹이", 50);
Box<Dog> dogBox = new Box<>();
dogBox.setValue(dog);
Dog findDog = dogBox.getValue();
System.out.println("findDog = " + findDog);
Box<Cat> catBox = new Box<>();
catBox.setValue(cat);
Cat findCat = catBox.getValue();
System.out.println("findCat = " + findCat);
}
}