[JAVA] 제너릭(Generic )이란

off_sujin·2022년 2월 12일
0

알고리즘 문제를 풀던 중 Collections 클래스의 sort() 메서드 코드를 보게 되었습니다.

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

제너릭 같기는한데.. 뭔가 복잡해보여서 무슨 의미인지 잘 알 수 없었습니다.
저는 약간의 충격을 받고, 이것을 계기로 제너릭을 다시 정리하고 가려고 합니다.

제너릭이란?

제너릭은 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능입니다.
다시 말해 특정 타입을 미리 지정하는 것이 아니라 필요에 따라 타입을 지정할 수 있도록 하는 일반 타입이라는 것입니다.

제너릭의 장점은 다음과 같습니다.

  1. 타입 안정성을 제공한다.
  2. 타입 체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.

제너릭 클래스

제너릭 클래스의 예는 다음과 같습니다.

class Box<T> {
	T item;
    
    void setItem(T item) {
    	this.item = item;
    }
    T getItem() {
    	return item;
    }

이처럼 특정 타입 대신에 T와 같은 타입 변수를 붙이면 됩니다.

제너릭 클래스를 생성할 때에는 참조변수와 생성자에 타입 T 대신에 사용될 실제 타입을 지정해줍니다.

Box<String> b = new Box<String>();
b.setItem(new Object());

제너릭을 사용할 때 2가지 제한사항이 있습니다.

먼저 제너릭 클래스에 있는 static 멤버에 타입 변수 T를 사용할 수 없습니다.
static 멤버는 모든 객체에 대해 동일하게 동작해야하기 때문에 인스턴스 변수로 간주되는 T는 사용할 수 없습니다.

두번째 제한은 제너릭 타입의 배열은 생성할 수 없다는 것입니다.
제너릭 배열 타입의 참조변수를 선언하는 것은 가능합니다.
하지만 new T[10] 과 같이 배열을 생성하는 것은 허용하지 않습니다.
그 이유는 new 연산자가 컴파일 시점에 타입 T를 알아야하기 때문입니다.

제너릭 클래스 Box<T> 의 객체에는 T타입의 객체만 저장할 수 있습니다.
객체를 생성할 때 참조변수와 생성자에 대입된 타입이 일치해야 합니다.
Box<Apple> appleBox = new Box<Apple>(); - ok

두 타입이 상속관계에 있어도 에러가 발생합니다.
Apple이 Fruit의 자손이라고 합시다.
Box<Fruit> appleBox = new Box<Apple>(); - error

단, 두 제너릭 클래스의 타입이 상속관계에 있고, 대입된 타입이 같은 것은 괜찮습니다.
FruitBox는 Box의 자손이라고 합시다.
Box<Apple> appleBox = new FruitBox<Apple>(); - ok

따라서 객체 생성을 할 때 대입된 타입은 항상 같아야하고, 두 제너릭 클래스의 타입은 상속관계에 있다면 달라도 괜찮습니다.

제한된 제너릭 클래스

타입 매개변수 T에 지정할 수 있는 타입의 종류를 제한해봅시다.

extends 를 사용하면 특정 타입의 자손들만 대입할 수 있습니다.

class FruitBox<T extends Fruit> {
	ArrayList<T> list = new ArrayList<T>();
    ...
}

FruitBox에는 Fruit 클래스의 자손들만 담을 수 있습니다.
만약 타입 매개변수 T에 Object를 대입하면 모든 종류의 객체를 저장할 수 있게 되는 것 입니다.

제너릭 클래스에서는 인터페이스를 구현할 때에도 extends 를 사용합니다.

class FruitBox<T extends Fruit & Interface> {
	ArrayList<T> list = new ArrayList<T>();
    ...
}

위의 예제에서 FruitBox에는 Fruit의 자손이면서 Interface를 구현한 클래스만 대입될 수 있습니다.

? (wild card)

제너릭 타입이 다른 메서드를 오버로딩하고자 와일드 카드가 고안되었습니다.
와일드 카드는 기호로 '?'이며, 어떠한 타입도 될 수 있습니다.

와일드 카드와 extends, super로 상하한을 제한할 수 있습니다.

  • <? extends T> : 와일드 카드의 상한 제한. T와 그 자손들만 가능
  • <? super T> : 와일드 카드의 하한 제한. T와 그 조상들만 가능
  • <?> : 제한 없음. <? extends Object> 와 동일

** 제너릭 클래스와 달리 와일드 카드에는 '&'을 사용할 수 없다.

제너릭 메서드

메서드의 선언부에 제너릭 타입이 선언된 메서드를 제너릭 메서드라고 합니다.
static <T> void sort(List<T> list, Comparator<? super T> c)
제너릭 타입의 선언 위치는 반환 타입 바로 앞입니다.

제너릭 메서드는 제너릭 클래스가 아닌 클래스에도 정의될 수 있습니다.
static 멤버에는 타입 매개변수를 사용할 수 없지만, 메서드에 제너릭 타입을 선언하고 사용하는 것은 가능합니다.

class FruitBox<T> {
	static <T> void sort(List<T> list, Comparator<? super T> c) {
    ...
    }
}

제너릭 클래스에 정의된 타입 매개변수와 제너릭 매서드에 정의된 타입 매개변수는 다릅니다.
같은 타입 문자 T를 사용해도 같지 않습니다.


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

제너릭을 다시 공부해보니, 이제 이 코드를 이해할 수 있을 것 같습니다.

sort(List<T> list) : 타입 T를 요소로 하는 List를 매개변수로 한다.
T extends Comparable : 타입 T는 Comparable을 구현한 클래스이다.
Comparable<? super T> : T 또는 그 조상의 타입을 비교하는 Comparable이다.

한 줄로 요약하자면
sort()는 T 또는 그 조상의 타입을 비교하는 Comparable을 구현한 T를 요소로 하는 List를 매개변수로 받는다.

아 개운하다.

참고자료

profile
학습 중..

0개의 댓글