작지만 꾸준한 반복

제네릭 미션을 하면서 알게된 것들 본문

카테고리 없음

제네릭 미션을 하면서 알게된 것들

iamjooon2 2023. 3. 8. 23:38

제네릭 미션하는데 하루를 넘게 쓴 나를 위해 정리한다....

 

제네릭?

- 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(); // 꺼내는 것은 할 수 없다
}
  • 소비하는 곳에서는 하한 제한을 써야 한다

 

참고

https://youtu.be/PtM44sO-A6g

https://youtu.be/w5AKXDBW1gQ

http://www.tcpschool.com/java/java_generic_concept

https://sungjk.github.io/2021/02/20/variance.html