최범균님의 강의를 듣고 정리한 내용입니다.
강의 내용이 유익하고 Generic 개념을 다시 정리해보자 글을 포스팅하려고 합니다.
좋은 강의가 많으니 한번 보시면 좋을 거 같습니다.
들어가기전.
지네릭은 컴파일러에게 타입을 알려주고 해당 타입의 안정성을 주는 장점이 있습니다.
프로그래밍에서 변성, 무변성, 공변, 반공변 등의 특징들을 많이 들어보셨을 겁니다.
이러한 특징들을 지네릭이 가지고 있어 문제점과 문제점의 개선 방법 등을 적어 보겠습니다.
지네릭과 하위 타입
public class Student extends Person {
}
Person person = new Student();
Stduent 는 Person의 하위 타입이므로 하위 타입 호환이 가능하다.
지네릭을 사용해도 하위 타입 호환이 가능할까?
Clazz<Person> persons = new Clazz<Student>(); // compilError!
Clazz<Person> 는 Clazz<Student> 과 상위 타입이 아니므로 컴파일 에러가 발생한다.
객체에서 상속 관계인데 지네릭에서는 왜 적용이 안되나?
지네릭은 타입 파라미터를 컴파일러에게 알려주는데 컴파일러는 해당 파라미터 타입만 추종을 하는데 상속 관계임이 명시가 안되어서 문제가 발생한다.
Person 이 Student의 상위 타입이고 GenericType<A>가 GenericType<B>의 상위 타입이 아니면 변성이 없다는 특징을 무변성 (invariant) 라고 합니다.
무변성일 때 문제
public class Student extends Person {
}
public class Clazz<T> {
private List<T> persons = new ArrayList<>();
public void push(T person) {
this.persons.add(person);
}
public List<T> getAll() {
return persons;
}
}
public class LunchLady {
public void distributeLunch(Clazz<Person> personClazz, LunchFood food) {
////...
}
}
LunchLady lunchLady = new LunchLady();
LunchFood food = new LunchFood();
Clazz<Student> studentClazz = new Clazz<>();
lunchLady.distributeLunch(studentClazz, food); // compileError!
학교에 있는 사람들에게 점심 급식을 배식하는 아주머니를 구현 해보았다.
여기서 지네릭의 특징 무변성이라는 문제점이 발생한다. distributeLunch() 의 파라미터 중 Clazz<Person> 타입이 들어온다.
클라이언트 쪽에선 Clazz<Student> 타입을 주어 컴파일 에러가 발생한다.
이러한 문제점으로 지네릭은 공변(covariant)을 해야만 한다.
지네릭은 공변을 하기위해 와일드 카드 <? extends T> 를 제공 한다.
extends를 유추해보면 T와 그 하위 클래스만 가능 하다는 뜻이다.
public class LunchLady {
public void distributeLunch(Clazz<? extends Person> personClazz, LunchFood food) {
////...
}
}
위와 같이 수정하면 컴파일 에러가 사라지고 Clazz<Student> 타입도 전달할 수 있다.
이렇게 공변으로 만들면 문제가 없나??
앞서 만들었던 Clazz 클래스를 살펴보자.
public class Clazz<T> {
private List<T> persons = new ArrayList<>();
public void push(T person) {
this.persons.add(person);
}
public List<T> getAll() {
return persons;
}
}
아래와 같이 정의 했다고 하자.
Clazz<? extends Person> studentClazz = new Clazz<Person>();
studentClazz.push(new Student()); // compileError!
지네릭을 공변으로 만들어 Student를 넣을 수 있게 만들었는데 컴파일 에러가 발생한다.
이는 지네릭 클래스인 Clazz에 T 타입이 Person인지 Student인지 확인이 되지 않아 컴파일러는 에러를 던지게 된다.
이 문제점을 반공변(contravariant) 으로 해결 해 보자.
반공변을 하기위해 와일드 카드 <? super T> 를 제공한다.
super 를 유추해보면 T와 그 상위 클래스만 가능 하다는 뜻이다.
아래와 같이 수정하면 컴파일 에러를 제거할 수 있다.
Clazz<? super Person> studentClazz = new Clazz<Person>();
studentClazz.push(new Person());
그리고 공변의 문제점을 개선할 수 있다.
공변과 반공변의 사용
PECS
- producer-extends, consumer-super
- 값을 제공하면 extends, 값을 사용하면 super
public void distributeLunch(Clazz<? extends Person> clazz, LunchFood food) {
List<Person> all = (List<Person>) clazz.getAll();
}
위의 메서드를 보면 clazz.getAll() 하는 부분이 있다. 이 부분에서는 값을 가져와 제공하는 입장이므로 extends를 사용한다.
Clazz<? super Student> clazz = new Clazz<>();
clazz.push(new Student());
clazz.push() 에서는 값을 사용하는 입장으로서 super을 사용한다.
'Laguage' 카테고리의 다른 글
[Java] 로또 (객체 지향 편) (0) | 2022.11.29 |
---|---|
[Java] 숫자 야구 게임 (객체 지향 편) (0) | 2022.11.27 |
[Java] Decorator Pattern (0) | 2022.07.16 |
[Java] Exception (0) | 2022.07.15 |
[Java] Stream (0) | 2022.07.14 |
[Java] Lambda expression (0) | 2022.07.14 |
[Java] Inner Class (0) | 2022.07.14 |
[Java] Generic Programing (0) | 2022.07.14 |