도서관에서 자바 공부를 하고 싶어 이펙티브 자바를 빌렸는데 제네릭에 대한 소개가 있었는데 제네릭? 이게 모람? 이러고 개념에 대해 찾아보는 포스팅이다.
제네릭? 이라고 한국말로 보면 몬 소린가 싶은데, Generic으로 우리가 익숙하게 사용했던 general(일반적인)와 비슷한 의미를 지니는 듯 싶다.
= 데이터 타입을 일반화
한다!
List<Integer> list = new ArrayList<>()
도List<String> 매개변수화 타입
List<?> 비한정적 와일드 카드
//실제 매개변수 타입이 무엇인지 모르지만 알 필요 없을 때
//그저 타입 안정성을 위해
List 로 타입
E 정규 타입 매개변수
List<E> 제네릭 메서드
알 필요가 없다면 그만큼 의미 없다는 뜻일까, 도대체 이 친구가 갖는 존재의 이유가 뭘까 하고 제네릭에 관해 이것저것 읽어보다가
와일드 카드는 불공변성 때문에 유연성이 떨어지는 단점
을 보완하기 위해 등장했다는 사실도 알았다.
동시에 타입 인자에 제약을 줄 때 extends
와 super
이 사용된다.
<? extends RemoteController>
= RemoteController의 하위 타입을 받음 = Upper Bounded Wildcard<? super RemoteController>
= RemoteController의 상위 타입을 받음 = Lower Bounded Wildcard합쳐서 PECS(= Producer Extends Consumer Super)로,
데이터를 생산하는 컴포넌트에서는 extends를, 데이터를 소비(저장,수정..)하는 컴포넌트에서는 super를 사용한다.
예시를 들어보자.
public class Remote<Device> {
private Device dev;
public Remote(Device dev) {
this.dev = dev;
}
public Device getDev() {
return dev;
}
}
public class RemoteController {}
public class TvRemoteController {
private int number;
public TvRemoteController(int number) {
this.number = number;
}
public int getNumber() {
return number;
}
}
public class AirRemoteController {
private int temp;
public AirRemoteController(int temp) {
this.temp = temp;
}
public int getTemp() {
return temp;
}
}
TvRemoteController tv = new TvRemoteController(10);
AirRemoteController air = new AirRemoteController(26);
Remote<RemoteController> tvrc = new RemoteController<TvRemoteController>(tv)
//이렇게 쓰이면 컴파일에러!
//왜? 제네릭 타입의 불공변성 때문에 상하위 타입 관계가 성립되지 않음
//그래서 와일드 카드를 이용해본다
Remote<?> tv = new Remote<TvRemoteController>(tv);
//RemoteController의 하위 타입을 받을 수 있으므로
Remote<? extends RemoteController> tvct = new Remote<TvRemoteController>(tv);
//그러나
List<TvRemoteController> trc = new ArrayList<>();
List<? extends RemoteController> rc = trc;
list.add(new TvRemoteController(15)); //는 성립하지 않는다
//왜? extends면 참조하는 객체가 Tv이건 Air이건 extends 때문에
//RemoteController의 하위 타입이 올 수 있으므로
//Air일 수도 있기 때문에 위 객체의 add를 허락하지 않음!
아래는 제네릭에서 흔하게 쓰이는 타입의 일종이다.
E – Element // E가 보통은 가장 많이 쓰임!
K – Key
N – Number
T – Type
V – Value
그래서 어떻게 일반화를 해주냐면, 제네릭 클래스의 경우로 보자.
public class Student<T> {
private T age;
public void setAge(T age) {
this.age = age;
}
public T getAge() {
return this.age;
}
}
이렇게 요소를 T라고 설정해주고 실제로 클래스를 선언할 때에는
Student<Integer> stu = new Student<Integer>();
이렇게 구체적인 타입을 명시해준다.
여기서 주의해야 할 점이 있다면 이전에 설명했던 타입의 기본형(ex.int)은 사용되지 못해 참조형으로 바꿔(ex.Integer) 타입에 넣어주어야 한다.
이 말은 즉슨 우리가 따로 선언한 클래스도 타입에 들어갈 수 있다는 것!
public class Student {
int age;
String name;
public Student(int age, String name) {
this.age = age;
this.name = name;
}
}
public class main(String[] args) {
PriorityQueue<Student> pq = new PriorityQueue<>();
//요렇게!
}
Type Safety
https://st-lab.tistory.com/153
https://eratosthenes.tistory.com/m/13
https://dev.gmarket.com/28
https://daily-study.tistory.com/1 한번 읽어보기