제네릭이 필요한 이유
1
2
3
4
5
6
7
8
9
10
| public class Cat {
public Cat(String name, int size) {
super(name, size);
}
public void sound() {
System.out.println("냐옹");
}
}
|
1
2
3
4
5
6
7
8
9
10
| public class Dog {
public Dog(String name, int size) {
super(name, size);
}
public void sound() {
System.out.println("멍멍");
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| public class CatHospital {
private Cat animal;
public void set(Cat animal) {
this.animal = animal;
}
public void checkup() {
System.out.println("동물 이름: " + animal.getName());
System.out.println("동물 크기: " + animal.getSize());
animal.sound();
}
public Cat getBigger(Cat target) {
return animal.getSize() > target.getSize() ? animal : target;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| public class DogHospital {
private Dog animal;
public void set(Dog animal) {
this.animal = animal;
}
public void checkup() {
System.out.println("동물 이름: " + animal.getName());
System.out.println("동물 크기: " + animal.getSize());
animal.sound();
}
public Dog bigger(Dog target) {
return animal.getSize() > target.getSize() ? animal : target;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| public class AnimalHospitalMainV0 {
public static void main(String[] args) {
DogHospital dogHospital = new DogHospital();
CatHospital catHospital = new CatHospital();
Dog dog = new Dog("멍멍이1", 100);
Cat cat = new Cat("냐옹이1", 300);
// 개 병원
dogHospital.set(dog);
dogHospital.checkup();
// 고양이 병원
catHospital.set(cat);
catHospital.checkup();
// 문제1: 개 병원에 고양이 전달
// dogHospital.checkup(cat); // 다른 타입 입력: 컴파일 오류
}
}
|
위 코드에서 보면 강아지와 고양이 병원의 기능은 동일합니다. 그러나 타입이 다르기 때문에 코드 중복이 많이 발생합니다.
다형성을 이용하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| public class Animal {
private String name;
private int size;
public Animal(String name, int size) {
this.name = name;
this.size = size;
}
public String getName() {
return name;
}
public int getSize() {
return size;
}
public void sound() {
System.out.println("동물 울음 소리");
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
", size=" + size +
'}';
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| public class AnimalHospitalV1 {
private Animal animal;
public void set(Animal animal) {
this.animal = animal;
}
public void checkup() {
System.out.println("동물 이름: " + animal.getName());
System.out.println("동물 크기: " + animal.getSize());
animal.sound();
}
public Animal getBigger(Animal target) {
return animal.getSize() > target.getSize() ? animal : target;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| public class AnimalHospitalMainV1 {
public static void main(String[] args) {
AnimalHospitalV1 dogHospital = new AnimalHospitalV1();
AnimalHospitalV1 catHospital = new AnimalHospitalV1();
Dog dog = new Dog("멍멍이1", 100);
Cat cat = new Cat("냐옹이1", 300);
// 개 병원
dogHospital.set(dog);
dogHospital.checkup();
// 고양이 병원
catHospital.set(cat);
catHospital.checkup();
// 문제1: 개 병원에 고양이 전달
dogHospital.set(cat); // 매개변수 체크 실패: 컴파일 오류가 발생하지 않음
// 문제2: 개 타입 반환, 캐스팅 필요
dogHospital.set(dog);
Dog biggerDog = (Dog) dogHospital.getBigger(new Dog("멍멍이2", 200));
System.out.println("biggerDog = " + biggerDog);
}
}
|
이번엔 반대로 코드 재사용은 줄였지만, 타입 안전성이 없어졌습니다.
제네릭 도입
문제 발생
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| public class AnimalHospitalV2<T> {
private T animal;
public void set(T animal) {
this.animal = animal;
}
public void checkup() {
// T의 타입을 메서드를 정의하는 시점에는 알 수 없다. Object의 기능만 사용 가능
animal.toString();
animal.equals(null);
// 컴파일 오류
//System.out.println("동물 이름: " + animal.getName());
//animal.sound();
}
public T getBigger(T target) {
// 컴파일 오류
//return animal.getSize() > target.getSize() ? animal : target;
return null;
}
}
|
제네릭은 도입하였지만, 타입을 알 수 없기 때문에 Object 타입의 메서드 밖에 쓸 수 없는 문제가 있습니다.
매개변수를 제한하여 해결 - extends
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| public class AnimalHospitalV3<T extends Animal> {
private T animal;
public void set(T animal) {
this.animal = animal;
}
public void checkup() {
System.out.println("동물 이름: " + animal.getName());
System.out.println("동물 크기: " + animal.getSize());
animal.sound();
}
public T getBigger(T target) {
return animal.getSize() > target.getSize() ? animal : target;
}
}
|
extends 키워드를 사용하면 해당 클래스를 상속 받은 클래스라는 것을 특정 할 수 있어 위 문제를 해결 할 수 있습니다.
제네릭 메서드
1
2
3
4
5
6
7
8
9
10
11
12
| public class GenericMethod {
public static <T> T genericMethod(T t) {
System.out.println("generic print: " + t);
return t;
}
public static <T extends Number> T numberMethod(T t) {
System.out.println("bound print: " + t);
return t;
}
}
|
와일드카드
1
2
3
4
5
6
7
8
9
10
11
12
| public class Box<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| public class WildcardEx {
// ?를 사용하여 간단하게 쓸 수 있음
static void printWildcardV1(Box<?> box) {
System.out.println("? = " + box.get());
}
// 와일드카드로 선언 시, 리턴 값 선언은 제네릭으로 할 수 없음
static Animal printAndReturnWildcard(Box<? extends Animal> box) {
Animal animal = box.get();
System.out.println("이름 = " + animal.getName());
return animal;
}
}
|
상한 와일드카드
extends 키워드를 통해 상한을 제한할 수 있습니다.Animal 아래 타입만 받음
1
2
3
4
5
| static Animal printAndReturnWildcard(Box<? extends Animal> box) {
Animal animal = box.get();
System.out.println("이름 = " + animal.getName());
return animal;
}
|
하한 와일드카드
super 키워드를 통해 하한을 제한할 수 있습니다.Animal 위의 타입만 받음
1
2
3
4
| static Animal printAndReturnWildcard(Box<? super Animal> box) {
Object object = box.get();
return null;
}
|
타입 이레이저
1
2
3
4
5
6
7
8
9
10
11
| public class GenericBox<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
|
1
2
3
4
5
| void main() {
GenericBox<Integer> box = new GenericBox<Integer>();
box.set(10);
Integer result = box.get();
}
|
컴파일 후
1
2
3
4
5
6
7
8
9
10
11
| public class GenericBox {
private Object value;
public void set(Object value) {
this.value = value;
}
public Object get() {
return value;
}
}
|
1
2
3
4
5
| void main() {
GenericBox box = new GenericBox();
box.set(10);
Integer result = (Integer) box.get(); //컴파일러가 캐스팅 추가
}
|
- 상한 제한
(extends)의 경우 제한한 타입으로 코드를 변경합니다. - 하한 제한
(super) 의 경우 Object로 고정입니다. - 즉, 받을 수 있는 최대 타입으로 변환합니다.
한계
컴파일 이후에는 타입정보가 모두 제거 되기 때문에 아래와 같은 소스는 에러가 발생합니다.
소스 코드
1
2
3
4
5
6
7
8
9
10
| class EraserBox<T> {
public boolean instanceCheck(Object param) {
return param instanceof T; // 오류
}
public T create() {
return new T(); // 오류
}
}
|
런타임
1
2
3
4
5
6
7
8
9
10
| class EraserBox {
public boolean instanceCheck(Object param) {
return param instanceof Object; // 오류
}
public T create() {
return new Object(); // 오류
}
}
|
명명 관례
E - ElementK - KeyN - NumberT - TypeV - ValueS,U,V etc. - 2nd, 3rd, 4th types
참고