본문 바로가기

복습

[Java 복습] 제네릭 메서드

목차

  • 제네릭 메서드
  • 제네릭 메서드 활용
  • 제네릭 타입과 제네릭 메서드 우선순위

 

이번에는 특정 메서드에 제네릭을 적용하는 제네릭 메서드에 대해 알아보자!

제네릭 타입과 지금부터 살펴볼 제네릭 메서드는 둘다 제네릭을 사용하긴 하지만 서로 다른 기능을 제공한다.

public class GenericMethod {

    public static Object objMethod(Object obj) {
        System.out.println("Object Print = " + obj);
        return obj;
    }
    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;
    }
}
public class MethodMain1 {

    public static void main(String[] args) {
        Integer i = 10;
        Object object = GenericMethod.objMethod(i);
        
        // 타입 인자(Type Argument) 명시적 전달
        System.out.println("명시적 타입 인자 전달");
        Integer result = GenericMethod.<Integer>genericMethod(i);
        Integer integerValue = GenericMethod.<Integer>numberMethod(10);
        Double doubleValue = GenericMethod.<Double>numberMethod(20.0);
    }
}
Object Print = 10
명시적 타입 인자 전달
generic Print = 10
bound Print = 10
bound Print = 20.0
  • 제네릭 타입
    • 정의: GenericClass<T>
    • 타입 인자 전달: 객체를 생성하는 시점
    • 예) new GenericClass<String>
  • 제네릭 메서드
    • 정의: <T> T genericMethod(T t)
    • 타입 인자 전달: 메서드를 호출하는 시점
    • 예) GenericMethod.<Integer>genericMethod(i)

 

  • 제네릭 메서드는 클래스 전체가 아니라 특정 메서드 단위로 제네릭을 도입할 때 사용한다.
  • 제네릭 메서드를 정의할 때는 메서드의 반환 타입 왼쪽에 다이아몬드를 사용해서 <T> 와 같이 타입 매개변수를 적어준다.
  • 제네릭 메서드는 메서드를 실제 호출하는 시점에 다이아몬드를 사용해서 <Integer> 와 같이 타입을 정하고 호출한다.
  • 제네릭 메서드의 핵심은 메서드를 호출하는 시점에 타입 인자를 전달해서 타입을 지정하는 것이다.
  • 따라서 타입을 정하면서 메서드를 호출한다.

 

 

인스턴스 메서드, static 메서드


제네릭 메서드는 인스턴스 메서드와 static 메서드에 모두 적용할 수 있다.

class Box<T> { //제네릭 타입
    static <V> V staticMethod2(V t) {} //static 메서드에 제네릭 메서드 도입
    <Z> Z instanceMethod2(Z z) {} //인스턴스 메서드에 제네릭 메서드 도입 가능
}

 

참고
제네릭 타입은 static 메서드에 타입 매개변수를 사용할 수 없다. 

제네릭 타입은 객체를 생성하는 시점에 타입이 정해진다. 

그런데 static 메서드는 인스턴스 단위가 아니라 클래스 단위로 작동하기 때문에 제네릭 타입과는 무관하다.
따라서 static 메서드에 제네릭을 도입하려면 제네릭 메서드를 사용해야 한다.

class Box<T> {
    T instanceMethod(T t) {} //가능
    static T staticMethod1(T t) {} //제네릭 타입의 T 사용 불가능
}

 

 

 

타입 매개변수 제한


제네릭 메서드도 제네릭 타입과 마찬가지로 타입 매개변수를 제한할 수 있다.
다음 코드는 타입 매개변수를 Number 로 제한했다. 따라서 Number 와 그 자식만 받을 수 있다.
참고로 Integer , Double , Long 과 같은 숫자 타입이 Number 의 자식이다.

public static <T extends Number> T numberMethod(T t) {}
//GenericMethod.numberMethod("Hello"); // 컴파일 오류 Number의 자식만 입력 가능

 

 

제네릭 메서드 타입 추론

 

제네릭 메서드를 호출할 때 <Integer> 와 같이 타입 인자를 계속 전달하는 것은 매우 불편하다. 

Integer i = 10;
Integer result = GenericMethod.<Integer>genericMethod(i);


자바 컴파일러는 genericMethod() 에 전달되는 인자 i 의 타입이 Integer 라는 것을 알 수 있다.
또한 반환 타입이 Integer result 라는 것도 알 수 있다. 이런 정보를 통해 자바 컴파일러는 타입 인자를 추론할 수있다.

// 타입 추론, 타입 인자 생략
System.out.println("타입 추론");
Integer result2 = GenericMethod.genericMethod(i); // 넘어오는 타입으로 추론
Integer integerValue2 = GenericMethod.numberMethod(10);
Double doubleValue2 = GenericMethod.numberMethod(20.0);

 

타입 추론 덕분에 타입 인자를 직접 전달하는 불편함이 줄어든다. 

이 경우 타입을 추론해서 컴파일러가 대신 처리하기 때문에 타입을 전달하지 않는 것 처럼 보인다. 

하지만 실제로는 타입 인자가 전달된다는 것을 기억하자.

 

 


제네릭 메서드 활용

앞서 제네릭 타입으로 만들었던 AnimalHospitalV3 의 주요 기능을 제네릭 메서드로 다시 만들어보자!

public class AnimalMethod {
    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 getBigger (T t1, T t2) {
        return t1.getSize() > t2.getSize() ? t1 : t2;
    }
}
public class MethodMain2 {

    public static void main(String[] args) {
        Dog dog = new Dog("흰둥이", 100);
        Cat cat = new Cat("도라이몽", 1000);

        AnimalMethod.<Dog>checkUp(dog);
        AnimalMethod.<Cat>checkUp(cat);

        Animal animal = AnimalMethod.<Animal>getBigger(dog, cat);
        System.out.println("bigger = " + animal);
        Dog bigger = AnimalMethod.<Dog>getBigger(dog, new Dog("검둥이", 150));
        System.out.println("bigger = " + bigger);
    }
}
동물 이름: 흰둥이
동물 크기: 100
멍멍!
동물 이름: 도라이몽
동물 크기: 1000
냐용먀용!
bigger = Animal{name='도라이몽', size=1000}
bigger = Animal{name='검둥이', size=150}

 

 

 

 

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

정적 메서드는 제네릭 메서드만 적용할 수 있지만 인스턴스 메서드는 제네릭 타입도 제네릭 메서드도 둘다 적용할 수 있다.

여기에 제네릭 타입과 제네릭 메서드의 타입 매개변수를 같은 이름으로 사용하면 어떻게 될까?

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.class = " + t.getClass().getName());
        // t.getName(); // 호출 불가 메서드는 <T> 타입이다. <T extends Animal> 타입이 아니다.
        return t;
    }
}
public class MethodMain3 {

    public static void main(String[] args) {

        Dog dog = new Dog("몽몽이", 100);
        Cat cat = new Cat("냥냥이", 50);

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

        Cat returnCat = hospital.<Cat>printAndReturn(cat);
        System.out.println("returnCat = " + returnCat);
    }
}
animal.className = genegic.animal.Dog
t.className = genegic.animal.Cat
returnCat = Animal{name='냥냥이', size=50}

 

 

제네릭 타입 설정

class ComplexBox<T extends Animal>

 

 

제네릭 메서드 설정

<T> T printAndReturn(T t)

 


제네릭 타입보다 제네릭 메서드가 높은 우선순위를 가진다.
따라서 printAndReturn() 은 제네릭 타입과는 무관하고 제네릭 메서드가 적용된다.
여기서 적용된 제네릭 메서드의 타입 매개변수 T 는 상한이 없다. 따라서 Object 로 취급된다.
Object 로 취급되기 때문에 t.getName() 과 같은 Animal 에 존재하는 메서드는 호출할 수 없다.

 

참고로 프로그래밍에서 이렇게 모호한 것은 좋지 않다.
둘의 이름이 겹치면 다음과 같이 둘 중 하나를 다른 이름으로 변경하는 것이 좋다.

public class ComplexBox <T extends Animal> {

    private T animal;

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

    public <Z> Z printAndReturn(Z z) {
        System.out.println("animal.className = " + animal.getClass().getName());
        System.out.println("t.className = " + z.getClass().getName());
        // t.getName(); // 호출 불가 메서드는 <T> 타입이다. <T extends Animal> 타입이 아니다.
        return z;
    }
}