제네릭, 와일드카드

2024. 2. 11. 22:58Backend 취업준비/Java

  • 제네릭 ⇒ 클래스, 인터페이스, 그리고 메소드를 정의할 때 타입을 파라미터로 사용할 수 있도록 한다
  • 타입 체크를 통하여 런타임 시 발생할 수 있는 에러를 컴파일 타임에 알 수 있도록 한다.
  • 불필요한 타입 변환을 막아 성능을 향상 한다.

그럼 그냥 일반 클래스로 하고 필드에 타입을 주면 되는 거 아닌가?

  • 위와 같이 필드에서 타입을 주게 되면 당연히 필드로 Integer만 올 수 있다.
  • 하지만, 제네릭을 사용한다면 다양한 타입으로 설정할 수 있고, 동일한 기능의 메서드들을 다양한 타입에 적용할 수 있다.
  • 타입은 객체를 생성할 때 결정 된다.

 

타입 파라미터 컨벤션

  • 제네릭에서 사용하는 타입 파라미터에는 아무런 문자나 넣어도 코드가 동작한다.
  • 특히 자바 내장 클래스들과 같은 이름의 문자로 넣어도 동작하는데, 이렇게 한다고 하더라도 해당 타입으로 제한이 생기는 것은 아니다. ⇒ 밑의 두 사진은 동일하게 동작하는 클래스
  • 하지만 이름을 마음대로 정하게 된다면 자칫 타입 제한이 있다고 착각하는 등 혼란을 야기할 수 있으므로 컨벤션이 존재한다. 컨벤션은 다음과 같다

 

 

 

제한된 타입 파라미터 (제네릭 클래스 선언부)

 

  • 제네릭 클래스를 설계할 때 사용
  • 제네릭타입<T> : Unbounded Wildcards (제한 없음)
    • 모든 클래스나 인터페이스 타입 가능, <T extends Object>와 동일
  • 제네릭 타입<T extends M> : Upper Bounded Wildcards (상한 제한)
    • M와 그 자손들 가능
  • 제네릭 타입<T super M> : 사용 불가. T가 M의 부모이면 클래스 설계할 때 M클래스의 속성이나 메서드를 보장할 수 없으며, 제네릭의 취지와 맞지 않음

 

제네릭 메서드

  • 메서드의 선언부에 제네릭 타입이 선언된 형식
  • 앞서 살펴본 제네릭 타입과 유사해 보일 수 있다.
  • 타입 매개변수의 범위가 메서드에서 국한된다는 차이점 이 있다.
  • 제네릭 메서드는 제네릭 클래스가 아닌 다른 곳에서도 정의할 수 있다.
  • 제네릭 메서드의 매개변수 타입을 더 우선한다

 

 

 

 

와일드카드

 

  • 꺽쇠 안에 물음표 기호<?>를 써서 나타내며, 이는 알 수 없는 타입을 의미한다.
  • 위의 제네릭 클래스 선언부와 원리는 비슷하나, 차이점이 있고, 클래스 선언부에서 <?>를 쓸 수 없다 (클래스 내부에서는 가능)
  • 제네릭 클래스 설계를 할 때 쓰이는 것이 아니다.
  • 제네릭 타입의 유연성을 위해 존재한다
  • 제네릭 클래스 선언부에서 쓰이는 <T>, <T extends M>
    • <T> : 모든 클래스나 인터페이스 타입이 가능하며, 객체 생성 시 해당 타입으로 결정된다
    • <T extends M> : M와 그 자손들 가능하며, 객체 생성 시 해당 타입으로 결정된다
  • 와일드카드
    • <?> : 모든 클래스나 인터페이스 타입이 가능하며, 특정 타입 하나로 결정되지는 않는다.
    • <? extends T> : T와 그 자손들 타입을 받아들일 수 있으며, 특정 타입 하나로 결정되지는 않는다.
    • <? extends T> : T와 그 조상들 타입으로 받아들일 수 있으며, 특정 타입 하나로 결정되지는 않는다.

 

와일드카드 사용 예시

 

 

  • Integer가 Object의 자손이기 때문에 List<Object>를 매개변수로 하는 함수에 List<Integer>타입을 넣어줘도 될 것 같지만, 이는 컴파일 오류가 발생한다.
  • 이는 제네릭 타입이 무공변 이어서, 해당 타입(List<Object>)으로만 인자를 받을 수 있기 때문이다. 이를 해결하기 위해 우측처럼 와일드 카드를 사용한다.
  • 컬렉션 프레임워크 상속관계는 당연히 유효함으로 이를 혼동하지 말자
  • 와일드카드는 함수의 매개변수에서 가장 많이 사용된다
  • 매개변수에서 T extends Object와 같은 형태로 쓰려는 것은 불가능하며, 컴파일 오류가 발생한다.

 

 

와일드 카드 값 꺼내기/넣기 intro

 

  • 간단한 getter와 setter로 구성된 제네릭 클래스 Product 이다.
  • 객체를 생성할 때 와일드 카드<?>를 사용하고, 이후 getter를 사용하여(값 꺼내기) 출력할 때는 잘 작동한다
  • 후에 setter를 사용(값 넣기)할 때는 오류가 발생한다.
  • <?> 는 <? extends Object>와 같다

 

 

와일드카드 값 꺼내기 / 넣기

 

 

상속관계 ⇒ Object - Machine - Car - 아반떼, 소나타, 그랜져

 

 

<? extends T> 값 꺼내기 / 넣기

 

  • 꺼내기 (getter) : 안전하게 꺼내려면 T타입으로 꺼낸다
    • T 가 Car이라면 Car, 아반떼, 소나타, 그랜져 취급 가능
    • 아반떼로 값을 꺼내면 소나타, 그랜져 가 전해질 때 오류 발생
  • 넣기 (setter) : 모든 타입 불가
    • 하위 타입에 상위 타입이 대입 될 위험이 존재 ⇒ 불가
    • null은 가능

<? super T> 값 꺼내기 / 넣기

 

  • 꺼내기 (getter) : Object 타입으로만 꺼내기 가능
    • Car타입으로 꺼내고, 매개변수로 Machine타입이 들어오면 오류 발생할 수 있으므로
    • 최상위 Object라면 그럴 위험 없음
  • 넣기 (setter) : T와 T 자손 가능
    • 매개변수로 오는 최상위 타입이 Car로 정해져 있으므로, Car와 그 자손을 넣게 되면 하위 타입에 상위 타입이 대입 될 위험이 없다.

 

와일드카드 extends / super 사용 시기

 

 

PECS 공식 : PECS란, Producer-Extends / Consumer-Super 라는 단어의 약자인데 다음을 의미한다.

  • 외부에서 온 데이터를 생산(Producer) 한다면<? extends T> 를 사용
    • 매개변수로 온 <? extends T> 는 T타입으로 데이터를 꺼낼 때 안전
  • 외부에서 온 데이터를 소비(Consumer) 한다면<? super T> 를 사용
    • 매개변수로 온 <? super T> 는 T타입과 T자손으로 데이터 저장할 때 안전
class MyArrayList<T> {
    Object[] element = new Object[5];
    int index = 0;

    // 외부로부터 리스트를 받아와 매개변수의 모든 요소를 내부 배열에 추가하여 인스턴스화 하는 생성자
    public MyArrayList(Collection<? extends T> in) {
        for (T elem : in) {
            element[index++] = elem;
        }
    }

    // 외부로부터 리스트를 받아와 내부 배열의 요소를 모두 매개변수에 추가해주는 메서드
    public void clone(Collection<? super T> out) {
        for (Object elem : element) {
            out.add((T) elem);
        }
    }
}

 

 

 

출처

 

(도서) 이것이 자바다

자바의 정석 유튜브 강의

https://inpa.tistory.com/entry/JAVA-☕-제네릭-와일드-카드-extends-super-T-완벽-이해

https://tecoble.techcourse.co.kr/post/2020-11-09-generics-basic/

https://www.youtube.com/watch?v=m9aw5a50aDw

https://www.youtube.com/watch?v=w5AKXDBW1gQ