java 제네릭, 와일드카드!

제네릭(Generics)

jdk1.5 부터 추가되었다.

다양한 타입의 객체들을 다루는 메서드, 컬렉션 클레스에 컴파일 시에 타입 체크(Complie Time Type Check)를 해주는 기능.

객체의 타입 안정성, 형변환 번거로움을 줄인다.

제네릭이 없을때 클래스 안에 여러 타입의 멤버를 사용하려면 템플릿 클래스를 써야했다.

class Box
{
	Object content
	public Box(Obejct something) {this.content = something}
}

Box 생성자에 어떤 타입의 변수가 와도 상관 없다.

하지만 항상 형변환과정을 거쳐 사용해야하고 안정성이 떨어진다.

제네릭을 사용하면 형변환을 생략할 수 있어 코드가 간결해지고 안정성이 높아진다.

이 템플릿 클래스를 지네릭 클래스로 변환해보자.

class Box <T>
{
	T content;
	public Box(T something) {this.content = something}
}

제네릭 용어

Box<T> : 제네릭클래스, 혹은 T의 Box, T Box(클래스명)
T : 타입변수, 타입매개변수
BoX : 원시타입

제네릭 제한

class Box <T extends Employee> {} - Employee의 하위 객체들만 타입변수로 올 수 있음.
class Box <T super SalesMan> {} - SalesMan의 상위 객체들만 타입변수로 올 수 있음.
class Box <T super SalesMan & 인터페이스명> {} - SalesMan의 상위 객체이고 해당 인터페이스를 구현한 객체만 타입변수로 올 수 있음.


와일드카드 <?>

다음과 같이 static메서드가 정의되어있다.

class Juicer {
	static Juice makeJuice(FruitBox<Fruit> box)	{
	...
	}
}

해당 메서드는 제네릭 메서드가 아니다. 그저 Fruit을 타입변수로 받는 FruitBox 객체를 매개변수로 받는 정적 메서드이다.
class Apple extends Fruit {} 라는 Apple클래스를 만들고
FruitBox<Apple> abox 객체를 만들어 makeJuice 메서드의 매개변수로 넣을 수 있을것 같지만 안된다.

그리고 다음과 같은 static메서드는 사용할 수 없다.
static Juice makeJuice(FruitBox<T> box)

staic메서드가 메모리에 올라갈때 T가 제너릭 타입인지, T라는 클래스인지 구분 못한다고 한다. static <T> Juice makeJuice(FruitBox<T> box)
따라서 위처럼 T가 제너릭 타입임을 알리도록 static키워드 바로 뒤에 <T>를 추가하도록 하자. 그냥 와일드 카드 쓰면 편함!

또한 FruitBox안의 타입변수만 바꿔서 오버로딩도 하지 못한다.(타입변수는 오버로딩 성립조건X)

이런 애매모호한 상황을 해결하기 위한게 와일드 카드이다.

타입변수를 모르는 제너릭클래스를 매개변수로 넣고싶을 때 ? 키워드를 사용하면 된다.

class Juicer {
	static Juice makeJuice(FruitBox<? extends Fruit> box)	{
	...
	}
}
static int numElementsInCommon(Set<?> s1, Set<?> s2) { 
	...
}

Set안의 요소가 어떤게 올지 모를때, 그래도 Set객체를 매개변수로 쓰고싶을때 와일드카드를 쓰면 된다.
매개변수 안의 타입변수가 어떤게 오던지 상관없다.

와일드 카드도 제너릭처럼 제한을 걸 수 있다.
<? extends T> - T의 하위클래스만 타입변수로 올 수 있다.
<? super T> - T의 상위 클래스만 타입변수로 올 수 있다.

그런데 왠만하면 와일드카드 쓰지 말고 제너릭과 멤버클래스로 선언하는게 안정성이 좋다고 한다. static 멤버로 올 경우만 와일드 카드를 쓰도록 하자.


제너릭 메서드

메서드 선언부, 반환자료형 앞에 제네릭 타입이 위치하는 메서드

대표적인 제너릭 메서드로 Collectionssort메서드가 있다.

Collectionssort메서드 정의

public static <T> void sort(List<T> list, Comparator<? super T> c)

이녀석도 static으로 선언된 정적 메소드이지만 와일드 카드를 쓰지 않고 제네릭을 통해 인자를 받는다.

반환타입 앞의 <T>는 제네릭 메서드라는걸 명시하는 것이고 여기에 배웠던 제네릭 제한을 걸 수 도 있다.

위에서 보았던 Juicer클래스의 와일드카드를 쓴 makeJuice 메서드를 제너릭으로 바꾸어 보자.

class Juicer {
	static Juice makeJuice(FruitBox<? extends Fruit> box)	{
	...
	}
}
class Juicer {
	static <T extends Fruit> Juice makeJuice(FruitBox<T> box)	{
	...
	}
}

해석하면 해당 메서드는 제네릭 메서드이고 인자로 FruitBox 매개변수가 오고 FruitBox의 타입변수는 제네릭에 의해 달라질 수 있다.
타입변수는 제너릭에 의해 Fruit를 상속하는 객체만 가능하다.

Collectionssort메서드

public static <T extends Comparable<? super T>> void sort(List<T> list)

이제 Collectionssort제너릭 메서드도 의미를 알수 있다.

일단 List의 요소로 Comparable인터페이스를 상속하는 객체만 올수 있다.
그냥 Comparable이 아니라 T를 정렬할수 있는 Comparable을 상속해야 한다.

ArrayList<Integer> list = new ArrayList<>();
....
Collections.sort(list);

이런식으로 많이 사용했는데 Integer레퍼클래스는 Comparable<Integer> 을 구현하고 있기 때문에 사용 가능한것.


카테고리:

업데이트: