12장 지네릭스(Generics)

slee2·2021년 9월 23일
0

Java의 정석

목록 보기
22/28
post-thumbnail

지네릭스

지네릭스(Generics)란?

컴파일시 타입을 체크해 주는 기능(compile-time type check)
객체의 타입 안정성을 높이고 형변환의 번거로움을 줄여줌

//	Tv객체만 저장할 수 있는 ArrayList를 생성
ArrayList<Tv> tvList = new ArrayList<Tv>();

tvList.add(new Tv());		// OK
tvList.add(new Audio());	// 컴파일 에러. Tv 외의 다른 타입은 저장 불가

장점

  1. 타입 안정성을 제공한다.
  2. 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해 진다.
  3. 컴파일 타임에서 미리 오류를 발생시켜 수정할 수 있게 해준다.

타입 변수

클래스를 작성할 때, Object타입 대신 타입 변수(E)를 선언해서 사용

일반 클래스가 지네릭클래스로 바뀜. 타입 변수는 E를 많이 쓰지만 아무거나 써도 상관없음.

타입 변수에 대입하기

객체를 생성시, 타입 변수(E) 대신 실제 타입(Tv)을 지정(대입)

// 타입 변수 E 대신에 실제 타입 Tv를 대입
ArrayList<Tv> tvList = new ArrayList<Tv>();

  ArrayList<Tv> tvList = new ArrayList<Tv>();
  tvList.add(new Tv());
  Tv t = tvList.get(0);	// 형변환 불필요 반환타입이 Tv이기 때문

지네릭스 용어

Box<T> 지네릭 클래스. 'T의 Box' 또는 'T Box'라고 읽는다.
T 타입 변수 또는 타입 매개변수.(T는 타입문자)
Box 원시 타입

지네릭 타입과 다형성

참조 변수와 생성자의 대입된 타입은 일치해야 한다.

ArrayList<Tv>		list = new ArrayList<Tv>();	// OK. 일치
ArrayList<Product>	list = new ArrayList<Tv>();	// 에러. 불일치

지네릭 클래스간의 다형성은 성립.(여전히 대입된 타입은 일치해야)

List<Tv>	list = new ArrayList<Tv>();	// OK. 다형성. ArrayList가 List를 구현
List<Tv>	list = new LinkedList<Tv>();	// OK. 다형성. LinkedList가 List를 구현

매개변수의 다형성도 성립

List<Tv>		list = new ArrayList<Tv>();
list.add(new Product());
list.add(new Tv());	// OK.
list.add(new Audio());	// OK.

Iterator<E>

클래스를 작성할 때, Object타입 대신 T와 같은 타입 변수를 사용



Iterator도 타입 변수로 설정이 되었기 때문에 제네릭스를 사용하면 형변환을 할 필요없이 코드작성이 가능하다.

HashMap<K,V>

여러개의 타입 변수가 필요한 경우, 콤마(,)를 구분자로 선언

대입하면 아래 그림처럼 바뀌게 된다.

제한된 지네릭 클래스

extends로 대입할 수 있는 타입을 제한

class FruitBox<T extends Fruit> {	// Fruit의 자손만 타입으로 지정가능
}
FruitBox<Apple> appleBox = new FruitBox<Apple>();	// OK.
FruitBox<Toy> toyBox = new FruitBox<Toy>();		// 에러. Toy는 Fruit의 자손이 아님

인터페이스의 경우에도 extends 사용.

interface Eatable {}
class FruitBox<T extends Eatable> {}	// X implements

지네릭스의 제약

타입 변수에 대입은 인스턴스 별로 다르게 가능

Box<Apple> appleBox = new Box<Apple>();
Box<Grape> grapeBox = new Box<Grape>();

1. static 멤버에 타입 변수 사용 불가

class Box<T> {
	static T item; // 에러
   	static int compare(T t1, T t2) {...} // 에러
   	...

왜 못쓸까요. static 멤버는 모든 인스턴스의 공통으로 쓰는건데 지네릭스는 인스턴스 별로 다르게 사용하게 한거에 static을 쓰면 어쩌자는거! 라는 느낌입니다.

2. 배열 생성할 때 타입 변수 사용불가. 타입 변수로 배열 선언은 가능

class Box<T> {
	T[] itemArr;	// OK.
   	...
   	T[] toArray() {
   		T[] tmpArr = new T[itemArr.length];	// 에러. 지네릭 배열 생성 불가
   	...

그니까 new 다음에 T가 오면 안된다는 뜻. new 다음에 어떤 타입이 오는지 확정지어야 하는데 T는 어떤 타입이 올지 모르기 때문.

와일드 카드 <?>

하나의 참조 변수로 대입된 타입이 다른 객체를 참조 가능

ArrayList<Product> list = new ArrayList<Tv>();	// 에러.
ArrayList<? extends Product> list = new ArrayList<Tv>();	// OK
ArrayList<? extends Product> list = new ArrayList<Audio>();	// OK

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

메서드의 매개변수에 와일드 카드를 사용

static Juice makeJuice(FruitBox<? extends Fruit> box) {
}	// Fruit과 Fruit 의 자손을 받을 수 있음.
...

System.out.println(Juicer.makeJuice(new FruitBox<Fruit>()));
System.out.println(Juicer.makeJuice(new FruitBox<Apple>())); // Fruit의 자손인 Apple 사용 가능

지네릭 메서드

지네릭 타입이 선언된 메서드(타입 변수는 메서드 내에서만 유효)

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

클래스의 타입 매개변수 <T>와 메서드의 타입 매개변수 <T>는 별개

class FruitBox<T> {	// 지네릭 클래스의 타입변수 <T>
	...
	static <T> void sort(List<T> list, Comparator<? super T> c) {
	...	// 위에 클래스에 있는 <T>와 다른 타입변수 <T>이다.
	}
}

같아도 되고 달라도 되니 그냥 다르게 쓰자. 헷갈릴수도 있으니.

메서드를 호출할 때마다 타입을 대입해야(대부분 생략 가능)

메서드를 호출할 때 타입을 생략하지 않을 때는 클래스 이름 생략 불가(드물다)


너무 신경쓰지말고 에러날때 그냥 하면됨.

지네릭 타입의 형변환

지네릭 타입과 원시 타입 간의 형변환은 바람직하지 않다.(경고 발생)

와일드 카드가 사용된 지네릭 타입으로는 형변환 가능

Box<Object> objBox = (Box<Object>)new Box<String>();	// 에러. 형변환 불가능
Box<? extends Object> wBox = (Box<? extends Object>)new Box<String>();	// OK
Box<? extends Object> wBox = new Box<String>();	// 위 문장과 동일
// 매개변수로 FruitBox<Fruit>, FruitBox<Apple>, FruitBox<Grape> 등이 가능
static Juice makeJuice(FruitBox<? exitends Fruit> box) { ... }

FruitBox<? extends Fruit> box = new FruitBox<Fruit>();	// OK
FruitBox<? extends Fruit> box = new FruitBox<Apple>();	// OK

지네릭 타입의 제거

컴파일러는 지네릭 타입을 제거하고, 필요한 곳에 형변환을 넣는다.

지네릭 타입의 경계(bound)를 제거. JDK 이전 버전과 같이 호환되게 하기 위해 타입을 제거하도록 만듬.

지네릭 타입 제거 후에 타입이 불일치하면, 형변환을 추가
와일드 카드가 포함된 경우, 적절한 타입으로 형변환 추가

0개의 댓글