일상기록
Java - 제네릭 메소드 본문
제네릭 메소드
메소드의 선언부에 제네릭 타입이 선언된 메소드를 제네릭 메소드라 한다.
static <T> void sort(List<T> list, Comparator<? super T> c)
제네릭 클래스에 정의된 타입 매개변수와 제네릭 메소드에 정의된 타입 매개변수는 전혀다른 별개의 것이다. 같은 타입 T를 사용해도 같은 것이 아니라는 것에 주의해야 한다.
제네릭 메서드는 제네릭 클래스가 아닌 일반 클래스에서도 정의될 수 있다.
class FruitBox<T>{
...
static <T> void sort(List<T> list, Comparator<? super T> c){
...
}
}
위의 코드에서 제네릭 클래스 FruitBox에 선언된 타입 매개변수 T와 제네릭 메소드 sort()에 선언된 타입 매개변수 T는 타입 문자만 같을 뿐 서로 다른 것이다. sort()가 static메소드라는 것을 보면 static맴버에는 매개변수를 사용할 수 없지만, 이처럼 메소드에 제네릭 타입을 선언하고 사용하는 것은 가능하다.
메소드에 선언되 제네릭 타입은 지역 변수를 선언한 것과 같다고 생각하면 이해하기 쉽다. 이 타입 매개변수는 메소드 내에서만 지역적으로 사용되는 것이므로 static이건 아니건 상관 없다.
그렇기에 내부 클래스에 선언된 타입 문자가 외부 클래스의 타입 문자와 같아도 구별될 수 있다.
와일드 카드 메소드 VS 제네릭 메소드
와일드 카드 메소드
static Juice makeJuice(FruitBox<? extends Fruit> box) {
String tmp = "";
for(Fruit f : box.getList()){
tmp += f + " ";
}
return new Juice(tmp);
}
- 타입 파라미터 요소에 관심이 없다.
- 하나의 참조 변수로 여러 객체를 다루기 위해 사용
제네릭 메소드
static <T extends Fruit> Juice makeJuice(FruitBox<T> box) {
String tmp = "";
for(Fruit f : box.getList()){
tmp += f + " ";
}
return new Juice(tmp);
}
- 타입 파라미터 요소에 관심이 있다.
- 제네릭 메소드는 제네릭 클래스 처럼 호출시 다른 타입 사용가능
- 와일드 카드를 사용할 수 없을때 제네릭 메소드를 사용
위의 메소드를 호출할 때는 아래와 같이 타입 변수에 타입을 대입(지정)해야 한다.
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
...
System.out.println(Juicer.<Fruit>makeJuice(fruitBox));
System.out.println(Juicer.<Apple>makeJuice(appleBox));
그러나 대부분 경우 컴파일러가 타입을 추정할 수 있기 때문에 생략해도 된다. 위의 코드에서도 fruitBox와 appleBox의 선언부를 통해 대입된 타입을 컴파일러가 추정할 수 있다.
System.out.println(Juicer.makeJuice(fruitBox)); // 대입된 타입을 생략할 수 있다.
System.out.println(Juicer.makeJuice(appleBox));
한 가지 주의할 점은 제네릭 메소드를 호출할 때, 대입된 타입을 생략할 수 없는 경우에는 참조변수나 클래스 이름을 생략할 수 없다는 것이다.
System.out.println(<Fruit>makeJuice(fruitBox)); // 에러 클래스 이름 생략 불가
System.out.println(this.<Fruit>makeJuice(fruitBox)); // Ok
System.out.println(Juicer.<Fruit>makeJuice(fruitBox)); // Ok
같은 클래스 내에 있는 맴버들끼리는 참조변수나 클래스이름, 즉 'this.'이나 '클래스이름.'을 생략하고 메소드 이름만으로 호출이 가능하지만 대입된 타입이 있을 때는 반드시 써줘야 한다.
제네릭 메소드는 매개변수의 타입이 복잡할 때도 유용하다. 만일 아래와 같은 코드가 있다면 타입을 별도로 선언함으로써 코드를 간략히 할 수 있다.
public static void printAll(ArrayList<? extends Product> list, ArrayList<? extends Product> list2) {
for(Unit u : list){
System.out.println(u);
}
}
// 타입을 별도로 선언
public static <? extends Product> void printAll(ArrayList<T> list, ArrayList<T> list2) {
for(Unit u : list){
System.out.println(u);
}
}
조금더 복잡한 예시를 보면 Collections클래스의 sort()이다.
public static <T extends Comparable<? super T>> void sort(List<T> list)
이해를 쉽게 하기위해 와일드 카드를 잠시 없다고 생각하고 보면
public static <T extends Comparable<T>> void sort(List<T> list)
List<T>의 요소가 Comparable인터페이스를 구현하는 것이어야 한다는 뜻이다. 인터페이스라고 해서 'implements'라고 쓰지 않는다.
public static <T extends Comparable<? super T>> void sort(List<T> list)
- List<T> : 타입 T를 요소로 하는 List를 매개변수로 허용한다.
- <T extends Comparable<? super T>> : 'T'는 Comparable을 구현한 클래스이어야 하며(<T extends Comparable>), 'T'또는 그 상위클래스(조상)의 타입을 비교하는 Comparable이어야 한다는 것 (Comparable<? super T>)을 의미한다. 만일 T가 Student이고, Person의 하위클래스(자손)라면, <? super T>는 Student, Person, Object가 모두 가능하다.
'Java' 카테고리의 다른 글
Java - 제네릭 타입의 제거 (0) | 2023.04.18 |
---|---|
Java - 제네릭 타입의 형변환 (0) | 2023.04.18 |
Java - 와일드 카드 (0) | 2023.04.17 |
Java - 제네릭(Generic) (0) | 2023.04.17 |
Java - OOP (0) | 2023.04.16 |