[Java] 제네릭 (Generic) 2

22. 제네릭 (Generic)

☑️ 타입 매개변수 제한

제네릭 도입 실패

public class AnimalHospital<T> {

    private T animal;

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

    public void checkup() {
        animal.toString();
        animal.equals(null);

        // System.out.println("동물 이름: " + animal.getName());
        // animal.sound();
    }

    public T bigger(T target) {
        // return animal.getSize() > target.getSize() ? animal : target;
        return null;
    }
}
  • <T> 를 사용해서 제네릭 타입을 선언하였고, checkup(), bigger()과 같은 메서드를 정의하는 시점에서는 T의 타입을 알 수 없기 때문에 Object의 기능을 사용하는 것 이외에는 컴파일 오류가 발생한다.
  • AnimalHospital<T>를 사용하여 객체를 생성할 때 자바 컴파일러는 T를 어느 타입이든 받을 수 있는 Object 로 가정한다. 그래서 Integer, Object 와 같은 동물과 관련 없는 모든 타입을 타입인자로 전달할 수 있다.

      AnimalHospital<Dog> dogHospital = new AnimalHospital<>();
      AnimalHospital<Cat> catHospital = new AnimalHospital<>();
      AnimalHospital<Integer> integerHospital = new AnimalHospital<>();
      AnimalHospital<Object> objectHospital = new AnimalHospital<>();
    
  • 타입 인자로 Animal과 관련된 타입만 받고싶다면 타입 매개변수에 제한을 두어야 한다.

타입 매개변수 제한

public class AnimalHospital<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;
    }
}
  • <T extends Animal> 과 같이 타입 매개변수 TAnimal과 그 자식만 받을 수 있도록 제한을 둔다.
  • 자바 컴파일러는 T 의 타입 인자로 Animal, Dog, Cat만 인식하고, Animal이 제공하는 checkup(), bigger() 메서드를 모두 사용 가능하다.
public static void main(String[] args) {
        AnimalHospitalV3<Dog> dogHospital = new AnimalHospitalV3<>();
        AnimalHospitalV3<Cat> catHospital = new AnimalHospitalV3<>();
        // AnimalHospitalV3<Integer> integer = new AnimalHospitalV3<>();

        Dog dog = new Dog("멍멍이1", 100);
        Cat cat = new Cat("냐옹이1", 300);

        dogHospital.set(dog);
        dogHospital.checkup();

        catHospital.set(cat);
        catHospital.checkup();

        // dogHospital.set(cat); // 다른 타입 입력 : 컴파일 오류
        dogHospital.set(dog);
        Dog biggerDog = (Dog) dogHospital.bigger(new Dog("멍멍이2", 200));
    }
  • 타입 매개변수 TAnimal로 상한을 지정하였기 때문에 전혀 관계 없는 타입 인자를 컴파일 시점에서 막는다.
  • set(), checkup() 과 같이 Animal의 기능을 모두 사용할 수 있게 되며, 다른 타입을 입력 시 컴파일 오류가 발생한다.
  • dogHospital.bigger() 에서는 Animal 타입이 아닌 Dog 타입을 반환하기 때문에 다운 캐스팅을 하지 않아도 된다.
  • 즉, 제네릭에 타입 매개변수를 사용해서 타입 안전성코드 재사용을 보장할 수 있게 되었다.

☑️ 제네릭 메서드

public class GenericMethod {
    public static <T> T genericMethod(T t) {
        System.out.println("generic print: " + t);
        return t;
    }
}
Integer i = 10;

Integer result = GenericMethod.<Integer>genericMethod(i); // generic print: 10

제네릭 메서드란?

  • <T> T genericMethod(T t)로 정의하며, 메서드를 호출하는 시점에 타입 인자를 전달한다.
    • 제네릭 타입 (GenericClass<T>)은 객체를 생성하는 시점에 타입 인자를 전달한다.
  • 클래스 전체가 아닌, 특정 메서드 단위에 제네릭을 도입하기 위해 사용한다.
  • 인스턴스 메서드static 메서드에 모두 적용할 수 있다.

      class Box<T> {
          static <V> V staticMethod(V t) {}
          <Z> Z instanceMethod(Z z) {}
      }
    
    • static 메서드는 인스턴스 단위가 아닌, 클래스 단위로 작동한다.
    • 즉, 특정 메서드 단위로 제네릭을 도입해서 사용하는 제네릭 메서드static 메서드에 도입 가능하다.
    • 하지만 제네릭 타입은 객체를 생성하는 시점에 타입이 정해지기 때문에 static 메서드에 도입 불가능하다.

        static T staticMethod(T t) {} // 제네릭 타입의 T 사용 불가능
      

타입 매개변수 제한

public class GenericMethod {
    public static <T extends Number> T numberMethod(T t) {
        System.out.println("bound print: " + t);
        return t;
    }
}
Integer integerValue = GenericMethod.<Integer>numberMethod(10); // bound print:  10
Double doubleValue = GenericMethod.<Double>numberMethod(20.0); // bound print:  20.0
// GenericMethod.numberMethod("Hello"); // 컴파일 오류
  • 제네릭 메서드는 <T extends Number> T numberMethod(T t)와 같이 타입 매개변수를 제한할 수 있다.

타입 추론

Integer i = 10;

Integer result = GenericMethod.genericMethod(i);
Integer integerValue = GenericMethod.numberMethod(10);
Double doubleValue = GenericMethod.numberMethod(20.0);
  • 자바 컴파일러는 메서드에 전달되는 인자의 타입을 추론할 수 있다.
  • 그래서 타입 인자 전달을 생략해도 된다.

제네릭 타입과 제네릭 메서드의 우선순위

public class ComplexBox<T extends Animal> {

    private T animal;

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

    public <T> T printAndReturn(T t) {
        System.out.println("animal.className: " + animal.getClass().getName());
        System.out.println("t.className: " + t.getClass().getName());
        return t;
    }
}
  • 제네릭 타입인 ComplexBox<T extends Animal> 와 제네릭 메서드인 <T> T printAndReturn(T t) 의 타입 매개변수를 둘 다 T 로 설정해보자.
  • printAndReturn() 내부에서 바로 t.getName() 는 호출 불가다.
    • 해당 제네릭 메서드의 타입 매개변수는 상한이 존재하지 않아, TObject 로 취급되기 때문이다.
Dog dog = new Dog("멍멍이", 100);
Cat cat = new Cat("냐옹이", 50);

ComplexBox<Dog> hospital = new ComplexBox<>();
hospital.set(dog);

Cat returnCat = hospital.printAndReturn(cat);
System.out.println("returnCat = " + returnCat);

실행 결과

animal.className: generic.animal.Dog
t.className: generic.animal.Cat
returnCat = Animal{name='냐옹이', size=50}
  • animal.classNameComplexBox 클래스의 제네릭 타입 T를 사용하기 때문에 Dog 클래스의 타입을 출력한다.
  • t.className 은 메서드 레벨의 제네릭 타입 <T>를 따르며, 이 경우 메서드 호출 시 전달된 매개변수인 cat 객체의 타입 (Cat)을 출력한다.
  • 즉, 제네릭 타입보다 제네릭 메서드가 높은 우선순위를 가진다.
  • 타입 매개변수의 이름이 겹치는 건 피하는 게 좋다.




김영한의 실전 자바 중급 2편을 참고하였습니다

© 2021. All rights reserved.

yaejinkong의 블로그