Java Generic - 메소드

제네릭에서 제공하는 제네릭 메소드가 있다.

제네릭 타입과 제네릭 메소드는 서로 다른 기능을 제고앟ㄴ다.

  • 제네릭 타입
  • 제네릭 메소드

제네릭 메소드 사용

GMethod.class

public class GMethod {

    public static Object objMethod(Object obj) {
        System.out.println("Object print = " + obj);
        return obj;
    }

    public static <T> T callMethod(T t) {
        System.out.println("Generic print = " + t);
        return obj;
    }

    public static <T extends Number> T callNumberMethod(T t) {
        System.out.println("Bound print = " + t);
        return obj;
    }
}
  • <T> T 반환을 가진 callMethod(T obj) 메소드가 제네릭 메소드이다.
  • 제네릭 메소드에도 <T extends Number> 파라미터 제한(상한)을 사용할 수 있다.

제네릭 타입은 public class GMethod<T> {...} 클래스 선언 부분에 있는 것이 제네릭 타입이다.

이제 각자의 메소드 호출 방법을 살펴보자.

Object 반환 메소드 호출

Integer i = 10;
// 오브젝트를 받을 수 있다.
GMethod.objMethod(i);

// Object 반환은 변화에 문제가 생긴다. 다운 캐스팅이 필수이다.
Object obj = GMethod.objMethod(i);
Integer intObj = (Integer) GMethod.objMethod(i);

Object print = 10

  • Object 인자로 전달할 수 있다.
  • 반환에는 변화에 문제가 생긴다. 반환는 Object 이므로 다운 캐스팅이 필요하다.
  • Integer 변수에 담으려면 (Integer) 키워드로 명시해주어야 한다.

<> 제네릭 메소드 호출

Integer i = 10;

// 타입 인자(Type Argument)를 명시적으로 전달한다.
System.out.println("명시적인 타입으로 인자를 전달하기");
Integer result1 = GMethod.<Integer>callMethod(i);
Integer result2 = GMethod.<Integer>callNumberMethod(i);

명시적인 타입으로 인자를 전달하기
Object print = 10
Bound print = 10

정리

  • Generic Type
    • 정의 방법: GenericClass<T>
    • 타입 인자 전달: 객체 생성 시점
      • new GenericClass<Integer>
  • Generic Method
    • 정의 방법: <T> T GMethod(T t)
    • 타입 인자 전달: 메소드 호출 시점
      • GMethod.<Integer>callMethod(i)

제네릭 메소드는 호출하는 시점에서 타입이 결정된다.

제네릭 메소드 정리

  • 전체가 아닌 특정 클래스 단위로 제네릭 사용된다.
  • 메소드 정의에는 반환 타입에서 다이아몬드 <T> 타입 매개변수를 적어준다.
  • 메소드를 실제 호출 시점에 사용해 <Integer> 타입을 지정해주면 지정된 타입으로 메소드가 호출된다.

제네릭 메소드는 호출하는 시점에서 타입 인자를 전달해 타입을 지정해주는 것이다. 타입이 지정되면 비로서 메소드가 호출된다.


제네릭 메소드 - static, 인스턴스

인스턴스 메소드와 static 메소드에 제네릭 메소드 적용 가능하다.

class Box<T> {
    static <V> V staticMethod(V v) {} // 스태틱에 제네릭 메소드 적용
    <Z> Z instanceMethod(Z z) {} // 인스턴스에 제네릭 메소드 적용
}
  • static 메소드는 <V> V 제네릭 메소드로 사용 가능하다.
  • instant 메소드는 <Z> Z 제네릭 메소드로 사용 가능하다.

static 메소드 사용에 주의사항

객체 생성 시점인 제네릭 타입(class Box<T>)은 인스턴스 메소드에 사용이 가능하다. 그렇지만 이 제네릭으로 static 메소드에 사용은 할 수 없다. 그 이유는 static 메소드는 클래스 단위이다. 객체 시점에 사용하는 제네릭 타입은 사용할 수 없다.

static 메소드에 사용해야한다면 제네릭 메소드로 사용해주어야 한다.


제네릭 타입 - static, 인스턴스

class Box<T> {
    T instanceM(T t) {} // 가능
    // static T staticM(T t) {} // 제네릭 타입으로 T 타입 사용 불가
}
  • 제네릭 타입 Box<T> 객체 생성 시점에 타입이 결정되도록 하였다.
  • instance 메소드에 제네릭 타입을 붙여 사용 가능하다.
  • static 메소드에 제네릭 타입을 붙이면 사용이 불가능하다.

제네릭 - 파라미터 제한(상한)

제네릭 메소드는 파라미터 제한을 걸 수 있다.

class Box<T> {
    public static <T extends Number> T numberM(T t) {
        return t;
    }
}
  • 제네릭 메소드에서 extends Number 키워드를 붙여 숫자형 인자만 받도록 하였다.

Box.<Integer>numberM(100);
// Box.<String>numberM("100s"); // 오류 발생함
  • 정의한 extends Nubmer 외의 타입은 오류를 발생 시켜 타입 안정성을 높였다.


제네릭 메소드 타입 추론

호출 시 <Integer> 타입 인자를 아래와 같이 매번 전달하는 것이 불편할 수 있다.

Integer i = 10;
Integer res = Box.<Integer>numberM(i);
  • 자바 컴파일러는 numberM(...) 인자로 전달되는 i 타입을 Integer 알 수 있다.
  • 반환 타입이 Integer res 라는 힌트가 충분히 많으므로 추론이 가능하다.

Integer i = 10;
Integer res1 = Box.numberM(i);
Integer res2 = Box.numberM(100);
Double res3 = Box.numberM(100.0);
  • 메소드 호출에 사용한 다이아몬드 타입을 제거한다.
  • 자바 컴파일러가 타입을 추론하여 알아내고 타입이 맞는 메소드를 호출한다.

코드에서 메소드 호출에 사용한 타입이 제거되었고,
정의한 메소드에서도 제네릭 메소드만 있고 어떤 기본형, 참조형 타입인지 잘 모를 수 있지만, 자바 컴파일러가 알아서 추론하여 메소드로 인자를 전달하게 된다.