2-2. 제네릭 메서드

shin·2024년 7월 14일

1. 제네릭 메서드

  • 특정 메서드에 제네릭을 적용하는 제네릭 메서드
  • 앞서 살펴본 제네릭 타입과 살펴볼 제네릭 메서드는 둘 다 제네릭을 사용하기는 하지만 서로 다른 기능을 제공함
package generic.ex4;

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;
    }
    
 }
package generic.ex4;

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> genericMethod(T t)
  • 타입 인자 전달 : 메서드를 호출하는 시점
    • 예) GenericMethod.<Integer>genericMethod(i)
  • 제네릭 메서드는 클래스 전체가 아니라 특정 메서드 단위로 제네릭을 도입할 때 사용함
  • 제네릭 메서드를 정의할 때는 메서드 반환 타입 왼쪽에 다이아몬드를 사용해서 <T>와 같이 타입 매개변수를 적어줌
  • 제네릭 메서드는 메서드를 실제 호출하는 시점에 다이아몬드를 사용해서 <Integer>와 같이 타입을 정하고 호출함

  • 제네릭 메서드의 핵심은 메서드를 호출하는 시점에 타입 인자를 전달해서 타입을 지정하는 것임
  • 따라서 타입을 지정하면서 메서드를 호출함

인스턴스 메서드, staic 메서드

  • 제네릭 메서드는 인스턴스 메서드와 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라는 것도 알 수 있음

  • 이런 정보를 통해 자바 컴파일러는 타입 인자를 추론할 수 있음

  • 앞서 만든 MethodMain1에 다음 코드를 추가하여 실행

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

실행 결과

//추가한 내용만 출력
타입 추론
generic print: 10
bound print: 10
bound print: 20.0
  • 타입 추론 덕분에 타입 인자를 직접 전달하는 불편함이 줄어듦
  • 이 경우 타입을 추론해서 컴파일러가 대신 처리하기 때문에 타입을 전달하지 않는 것처럼 보임
  • 하지만 실제로는 타입 인자가 전달된다는 것을 기억해야 함


2. 제네릭 메서드 활용

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

package generic.ex4;

import generic.animal.Animal;

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;
    }
    
 }
  • checkup(), getBigger()라는 두 개의 제네릭 메서드를 정의함
  • 둘 다 Animal을 상한으로 제한함

package generic.ex4;

import generic.animal.Cat;
import generic.animal.Dog;

public class MethodMain2 {

	public static void main(String[] args) {
    
 		Dog dog = new Dog("멍멍이", 100);
 		Cat cat = new Cat("냐옹이", 100);
        
 		AnimalMethod.checkup(dog);
 		AnimalMethod.checkup(cat);
        
 		Dog targetDog = new Dog("큰 멍멍이", 200);
 		Dog bigger = AnimalMethod.getBigger(dog, targetDog);
 		System.out.println("bigger = " + bigger);
        
    }
    
}

실행 결과

동물 이름: 멍멍이
동물 크기: 100
멍멍
동물 이름: 냐옹이
동물 크기: 100
냐옹
bigger = Animal{name='큰 멍멍이', size=200}
  • 기존 코드와 같이 작동하는 것을 확인할 수 있음
  • 참고로 제네릭 메서드를 호출할 때 타입 추론을 사용했음


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

  • 정적 메서드는 제네릭 메서드만 적용할 수 있지만, 인스턴스 메서드는 제네릭 타입도 제네릭 메서드도 둘 다 적용할 수 있음
  • 여기에 제네릭 타입과 제네릭 메서드의 타입 매개변수를 같은 이름으로 사용하면?
package generic.ex4;

import generic.animal.Animal;

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());
 		// t.getName(); // 호출 불가 메서드는 <T> 타입이다. <T extends Animal> 타입이 아니.
 		return t;
 	}
    
}
package generic.ex4;

import generic.animal.Cat;
import generic.animal.Dog;

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.printAndReturn(cat);
 		System.out.println("returnCat = " + returnCat);
        
    }
    
 }

실행 결과

animal.className: generic.animal.Dog
t.className: generic.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) {
 		//...
    }
    
}


강의 출처 : 김영한의 실전 자바 - 중급 2편

profile
Backend development

0개의 댓글