포스트

제네릭

제네릭

제네릭이 필요한 이유

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); // 다른 타입 입력: 컴파일 오류
  }
}

위 코드에서 보면 강아지와 고양이 병원의 기능은 동일합니다. 그러나 타입이 다르기 때문에 코드 중복이 많이 발생합니다.

  • 코드 재사용 X
  • 타입 안전성 O

다형성을 이용하기

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);
  }
}

이번엔 반대로 코드 재사용은 줄였지만, 타입 안전성이 없어졌습니다.

  • 코드 재사용 O
  • 타입 안전성 X

제네릭 도입

문제 발생

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 - Element
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S,U,V etc. - 2nd, 3rd, 4th types

참고

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.