일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- forloop
- remove
- ssh-action
- object
- CMC
- 동빈북
- equlas
- 동등성
- 이것이취업을위한코딩테스트다
- CICD
- 나동빈
- HashCode
- 컴공선배
- 딕셔너리
- 라이징캠프
- 우아한테크코스
- nestJS
- Hackathon
- 우테코
- 동일성
- Payload
- makeus
- 이코테
- 너디너리
- 왕실의나이트
- JWT
- 해커톤
- Signature
- github action
- loop
- Today
- Total
작지만 꾸준한 반복
제네릭 미션을 하면서 알게된 것들 본문
제네릭 미션하는데 하루를 넘게 쓴 나를 위해 정리한다....
제네릭?
- JDK 1.5부터 도입된, 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법이다.
- 컴파일 타임에 타입을 체크함으로써 코드에 안정성을 높여준다
왜 쓰는걸까?
컴파일 타임에 타입 검사가 가능하기 때문이다
// 제네릭 미사용시
List names = new ArrayList<>();
names.add("gavi");
names.add(1);
String result = (String) names.get(0) * (String) names.get(1); // Runtime Error
// 제네릭 사용시
List names = new ArrayList<>();
names.add("gavi");
names.add(1); // Compile Error
매번 캐스팅을 해줄 필요가 없다
// 제네릭 미사용
List names = new ArrayList<>();
names.add("gavi");
String name = (String) names.get(0); // 매번 캐스팅 필요
// 제네릭 사용
List names = new ArrayList<>();
names.add("gavi");
String name = names.get(0); // 캐스팅 불필요
사용법
// 생성시
class GaviArray<T> {
private T element;
public GaviArray(T element) {
this.element = element;
}
public T getElement() {
return this.element;
}
}
// 사용시
GaviArray<Integer> exampleArray = new GaviArray<>(37);
변성? 무공변? 공변? 반공변?
이친구들은 대체 뭘까...
변성?
변성을 제대로 이해하려면
"타입 S가 T의 하위 타입일 때, Box[S]가 Box[T]의 하위 타입인가?"
라는 질문에서 시작하는게 좋다.
- 변성(variance)은 타입의 계층관계(Type Hierarchy)에서 서로 다른 타입간의 어떤 관계가 있는지를 나타내는 개념이다.
무려 자바1 에 등장한 Object 클래스는 모든 클래스가 상속받았다.
Object 타입과 자바의 모든 컬렉션은 변성을 가지고 있다.
무공변?
- 타입 S가 T의 하위 타입일 때, Box[S]와 Box[T] 사이에 상속 관계가 없는 것을 말한다
블랙잭 게임을 예로 들면, Participant의 하위 타입인 Dealer가 있더라도
List<Participant>에 List<Delaer> 사이에는 상속 관계가 없기에 무공변이다.
공변?
- 타입 S가 T의 하위 타입일 때, Box[S]가 Box[T]의 하위 타입인 것
<? extends T> 로 표현할 수 있다
// Number 배열에 하위 타입 값을 추가
Number[] numbers = new Number[2];
numbers[0] = new Integer(42);
numbers[1] = new Double(3.14);
// Number 배열에 Interger 배열을 할당할 수 있다
Integer[] ints = {1, 2, 3, 4};
Number[] numbers = ints;
Integer는 Number의 하위타입입니다.
Number[]에 , Integer[]를 할당할 수 있는데, 이것이 공변이다
반공변?
- 타입 S가 T의 하위 타입일 때, Box[S]가 Box[T]의 상위 타입인 것
<? super T>
그렇다면 말랑이가 쏘아올린 와일드카드는 뭘까?
와일드카드?
- 제네릭 객체를 메서드의 파라미터로 받을 때, 그 객체의 타입 변수를 제한하는 것
@Test
void genericTest() {
List<Integer> list = Arrays.asList(1, 2, 3);
printCollection(list); // 컴파일 에러
}
void printCollection(Collection<Object> c) {
for (Object e : c) {
System.out.println(e);
}
}
제네릭은 불공변이고, Collection<Object>는 Collection<Integer>의 하위타입이 아니므로 컴파일 에러가 발생한다
오히려 제네릭보다 실용성이 떨어지는데, 그리하여 등장한 것이 이 와일드카드이다
와일드카드에는 세가지 종류가 있다.
1. Unbounded Wildcards : <?>
- 모든 타입이 가능하다
2. Upper Bounded Wildcards : < ? extends MyArray>
MyArray와, MyArray의 하위 타입만 가능하다
- <? extends Collection> : Collection, Map, List....
- 상한 경계(Upper Bound)는 해당 클래스와, 해당 클래스의 자식클래스이다
- 공변이 가능하다
3. Lower Bounded Wildcards: <? super MyArray>
MyArray와 MyArray의 상위 타입만 가능하다
- <? super Collection> : Collection, Iterable, Object....
- 하한 경계(Lower Bound)는 해당 클래스와, 클래스의 부모클래스이다
- 반공변이 가능하다
- 와일드카드가 제한된다
개념을 찾아 돌아다닐 때 마다 나오는 것이 PECS라는 놈이 꼭 나온다
이 PECS는 무엇일까?
입력 파라메터 컬렉션 성격에 따라(생산하는지, 소비하는지. 다른말로 꺼내는지 집어넣는지)
적절히 extends , super 키워드를 사용한 '한정적 와일드카드 타입'을 적용하는 것.
이것이 'PECS' 의 핵심 개념이라고 할 수 있다.
상한 제한
public void pop(Test<? extends String> myArray) {
String element = myArray.get(); // 꺼내는 것은 가능하지만
element.set(new String()); //저장은 할 수 없다
}
- 생성하는 곳에서 상한 제한을 써야한다
하한 제한
public void push(Test<? super String> myArray) {
element.set(new String()); // 저장은 할 수 있지만
String element = myArray.get(); // 꺼내는 것은 할 수 없다
}
- 소비하는 곳에서는 하한 제한을 써야 한다
참고
http://www.tcpschool.com/java/java_generic_concept
https://sungjk.github.io/2021/02/20/variance.html