Java Intermediate_2 (Generic) | JeongKeepsCalm

Java Intermediate_2 (Generic)

제네릭(Generic)

  • 제네릭 타입
    제네릭 클래스, 제네릭 인터페이스를 모두 합쳐 제네릭 타입으로 부른다.
    예: class GenericBox {private T t;} GenericBox 을 제네릭 타입이라 부른다.
  • 타입 매개변수(Type Parameter)
    제네릭 타입이나 메소드에서 사용되는 변수로, 실제 타입으로 대체된다.
    예: GenericBox T를 타입 매개변수라 한다.
  • 타입 인자(Type Argument)
    제네릭 타입을 사용할 때 제공되는 실제 타입
    예: GenericBox Integer를 타입 인자라 한다. 기본형이 아닌 참조형만 사용 가능하다.
Generic Class
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
31
32
33
34
35
36
37
public class GenericBox<T> {

  private T value;

  public T get() {
    return value;
  }

  public void set(T value) {
    this.value = value;
  }

}

package generic.ex1;

public class BoxMain3 {

  public static void main(String[] args) {

    /**
     * 생성 시점에 T의 타입이 결정된다.
     */
    GenericBox<Integer> integerBox = new GenericBox<Integer>();
    integerBox.set(10);
    // integerBox.set("hello"); // Integer 타입만 허용, 컴파일 오류
    Integer integer = integerBox.get();
    System.out.println("integer = " + integer);

    // 타입 추론: 생성하는 제네릭 타입 생략 가능
    GenericBox<String> stringBox = new GenericBox<>();
    stringBox.set("genericString");
    String str = stringBox.get();
    System.out.println("str = " + str);
  }

}

제네릭 클래스: <>를 사용한 클래스
<T>: 타입 매개변수
클래스 내부에 T 타입이 필요한 곳에 T value 와 같이 타입 매개변수를 적어준다.
타입추론: 자바가 스스로 타입 정보를 추론해서 개발자가 타입 정보를 생략할 수 있는 것


  • Raw Type
    제네릭 타입 인스턴스 생성 시, 타입 인자 없이 생성하는 것
    권장하지 않으므로, Object 타입 인자로 명시하는 것을 권장한다.
메타 데이터 조회
1
2
3
4
5
6
7
8
9
public class RawTypeMain {
  public static void main(String[] args) {
    GenericBox integerBox = new GenericBox();
    //GenericBox<Object> integerBox = new GenericBox<>(); // 권장
    integerBox.set(10);
    Integer result = (Integer) integerBox.get();
    System.out.println("result = " + result);
  }
}


타입 매개변수 상한
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 bigger(T target) {
     return animal.getSize() > target.getSize() ? animal : target;
  }
}
{}: Animal 과 하위 타입만 받는다. 타임 매개변수에 입력될 수 있는 값의 범위 예측이 가능하다.


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
31
32
33
34
35
36
public class AnimalHospitalMainV3 {

  public static void main(String[] args) {

    AnimalHospitalV3<Dog> dogHospital = new AnimalHospitalV3<>();
    AnimalHospitalV3<Cat> catHospital = new AnimalHospitalV3<>();

    Dog dog1 = new Dog("dog1", 100);
    Cat cat1 = new Cat("cat1", 300);

    // 개 병원
    dogHospital.set(dog1);
    dogHospital.checkup();

    // 고양이 병원
    catHospital.set(cat1);
    catHospital.checkup();

    // 문제1: 개 병원에 고양이 전달
     // dogHospital.set(cat1); // 타입 제한

    // 문제2: 개 타입 반환
    dogHospital.set(dog1);
    Dog biggerDog = dogHospital.bigger(new Dog("dog2", 500));
    System.out.println("biggerDog = " + biggerDog);

    /**
     * 코드 재사용성 o
     *    다형성을 통해 AnimalHospitalV3 하나로 개와 고양이를 모두 처리
     * 타입 안정성 o
     *    개 병원에 고양이를 전달하는 문제 해결
     */

  }

}


  • 제네릭 메소드
    정의: T genericMethod(T t) 메소드를 호출하는 시점에 타입인자를 전달한다.
Generic Method
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
31
32
33
34
35
36
37
38
39
40
41
public class AnimalMethod {

  /**
   * Created Static Generic Method
   */
  public static <T extends Animal> void checkup(T t) {
    System.out.println("동물 이름: " + t.getName());
    System.out.println("동물 크기: " + t.getSize());
    t.sound();
  }

  public static <T extends Animal> T bigger(T t1, T t2) {
    return t1.getSize() > t2.getSize() ? t1 : t2;
  }

}

public class MethodMain2 {

  public static void main(String[] args) {

    Dog dog = new Dog("dog1", 100);
    Cat cat = new Cat("cat1", 200);

    /**
     * 타입 매개변수 추론으로 생략 가능
     */
    AnimalMethod.checkup(dog);
    AnimalMethod.checkup(cat);
    // AnimalMethod.<Dog>checkup(dog);
    // AnimalMethod.<Cat>checkup(cat);

    Dog targetDog = new Dog("targetDog", 150);
    Dog biggerOne = AnimalMethod.bigger(targetDog, dog);
    // Dog biggerOne = AnimalMethod.<Dog>bigger(targetDog, dog);
    System.out.println("biggerOne = " + biggerOne);

  }

}


  • 와일드 카드 와일드카드는 제네릭타입/제네릭메소드가 아니라 이미 만들어진 제네릭타입/제네릭메소드를 활용할 때 사용된다.
    제네릭타입/제네릭메소드를 쉽게 사용할 수 있게해주는 도구
Wildcard Example
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class Box<T> {
  private T value;
  public void set(T value) {
    this.value = value;
  }
  public T get() {
    return value;
  }
}

public class WildcardEx {
  static <T> void printGenericV1(Box<T> box) { // Box 라는 제네릭 타입을 받는다.
    System.out.println("T = "+ box.get());
  }
  static void printWildcardV1(Box<?> box) { // wildcard 변환
    System.out.println("? = "+ box.get());
  }

  static <T extends Animal> void printGenericV2(Box<T> box) { // Box 라는 제네릭 타입을 받지만 Animal 이 들어있는 박스
    T t = box.get();
    System.out.println("동물 이름: " + t.getName());
  }
  static void printWildcardV2(Box<? extends Animal> box) { // wildcard 변환
    Animal animal = box.get();
    System.out.println("동물 이름: " + animal.getName());
  }

  static <T extends Animal> T printAndReturnGeneric(Box<T> box) { // 동물이름 출력 후 반환
    T t = box.get();
    System.out.println("동물 이름: " + t.getName());
    return t;
  }
  static Animal printAndReturnWildcard(Box<? extends Animal> box) { // wildcard 변환
    Animal animal = box.get();
    System.out.println("동물 이름: " + animal.getName());
    return animal;
  }

}

public class WildcardMain1 {
  public static void main(String[] args) {
    Box<Object> objectBox = new Box<>();
    Box<Dog> dogBox = new Box<>();
    Box<Cat> catBox = new Box<>();

    dogBox.set(new Dog("멍멍이", 100));
    WildcardEx.printGenericV1(dogBox);
    WildcardEx.printWildcardV1(dogBox);

    WildcardEx.printGenericV2(dogBox);
    WildcardEx.printWildcardV2(dogBox);

    Dog dog = WildcardEx.printAndReturnGeneric(dogBox);
    Animal animal = WildcardEx.printAndReturnWildcard(dogBox);
  }

}


제네릭 메소드 실행 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//1. 전달
printGenericV1(dogBox)
//2. 제네릭 타입 결정 dogBox는 Box<Dog> 타입, 타입 추론 -> T의 타입은 Dog
static <T> void printGenericV1(Box<T> box) {
  System.out.println("T = " + box.get());
}
//3. 타입 인자 결정
static <Dog> void printGenericV1(Box<Dog> box) {
  System.out.println("T = " + box.get());
}
//4. 최종 실행 메서드
static void printGenericV1(Box<Dog> box) {
  System.out.println("T = " + box.get());
}

와일드 카드 실행 예시

1
2
3
4
5
6
// 1. 전달(제네릭 메서드가 아닌 일반적인 메서드)
printWildcardV1(dogBox)
//2. 최종 실행 메서드, 와일드카드 ?는 모든 타입을 받을 수 있다.
static void printWildcardV1(Box<?> box) {
  System.out.println("? = " + box.get());
}

특정 시점에 타입 매개변수에 타입 인자를 전달해서 타입을 결정해야 하는 것은 번거롭다.
제네릭 타입이나 제네릭 메서드를 정의하는게 꼭 필요한 상황이 아니라면, 더 단순한 와일드카드 사용하자

  • 제네릭 메소드를 사용해야 하는 경우
    상한 와일드카드 메소드 사용 시 리턴 타입을 최상위 부모클래스로 정해져있다.
    즉, 리턴 타입을 하위 타입으로 지정하고 싶을 경우에 제네릭 메소드를 사용해야 한다.
하한 와일드 카드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class WildcardMain2 {
  public static void main(String[] args) {
    Box<Object> objectBox = new Box<>();
    Box<Animal> animalBox = new Box<>();
    Box<Dog> dogBox = new Box<>();
    Box<Cat> catBox = new Box<>();

    // Animal 포함 상위 타입 전달 가능
    writeBox(objectBox);
    writeBox(animalBox);
    // writeBox(dogBox);
    // writeBox(catBox);

  }

  // 하한 와일드카드: Animal 포함 상위 클래스만 받는다.
  static void writeBox(Box<? super Animal> box) {
    box.set(new Dog("dog", 100));
  }

}


  • 타입 이레이저(Eraser)
    자바의 제네릭 타입은 컴파일 시점에만 존재하고, 런타임 시에는 제네릭 정보가 지워진다.
    제네릭은 타입매개변수가 지정되고 컴파일되고 난 후에 사라진다.
    (클래스 파일에 지정된 타입매개변수가 Object로 변한 것을 확인 할 수 있다.)
    하지만 내부적으로 다음 컴파일 시 지정된 타입매개변수로 캐스팅하여 실행하므로 문제가 되지 않는다.