2 minute read

📖 제네릭 (Generic)

제네릭은 컴파일 타임 유형 안정성을 제공하면서 다양한 유형의 객체에서 작동하는 유형 또는 메소드를 허용하기 위해 추가되었다.

  • 파라미터 타입이나 리턴 타입의 정의를 미룬다.
  • 타입에 대한 유연성과 안정성을 확보
  • 런타임 환경에 아무런 영향이 없는 컴파일 시점의 전처리 기술


🍄 📖 제네릭 사용법

class GenericClass<T> {
  // 제네릭 메소드
    private T t;

    public void setT(T t) {
        this.t = t;
    }

    public T getT() {
        return t;
    }
}

public void main(String[] args){
  GenericClass<Integer> genericClass = new GenericClass<>();
  genericClass.setT(1);
  System.out.println(genericClass.getT());
}

원하는 타입을 클래스 선언시에 선언해주는 방식이기 떄문에, 다른 타입이지만 같은 기능에 대한 확정성 좋다.
<T>와 같이 <>부호 안에 타입 파라미터를 위치시킨다.
원하는 반환타입이나 받는 매개인자를 제네릭을 사용하여 정의해줄 수 있다.


🍄 📖 자주 사용하는 타입인자

image

  • 일반적으로 대문자이며, 위와 같이 사용한다.
  • 하지만, 꼭 위의 인자를 지켜야 선언되는 것은 아니다.


📖 와일드카드

와일드 카드는 자바의 공변성을 일종의 편법으로 해결하기 위해 나온 개념이다.


🍄 자바의 공변성

변성이란 타입의 상속 계층 관계에서 서로 다른 타입 간에 어떤 관계가 있는지를 나타내는 지표이다.

  • 공변 -> ST의 하위 타입이면
    • S[]T[]의 하위 타입
    • class<S>class<T>의 하위 타입

따라서, 다음 다운캐스팅같은 것이 가능하다.

Object[] object = new Integer[5];

하지만, 제네릭 배열 코드는 다음과 같은 것이 불가능하다.

//불가능
GenericClass<Object> genericClass = new GenericClass<Integer>();

따라서, PriorityQueue같은 자료구조를 사용할 때, 뒤의 <>에는 아무것도 넣지 않는다.

PriorityQueue<Integer> pq = new PriorityQueue<>();

하지만, 이는 자바에서 다음과 같이 List의 모든값을 출력하는 코드가 있을 때, 오류가 발생하게 된다.

public static void print(List<Object> arr) {
    for (Object e : arr) {
        System.out.println(e);
    }
}

public static void main(String[] args) {
    List<Integer> integers = Arrays.asList(1, 2, 3);
    print(integers); // ! Error
}

이를 해결하기 위해서는 모든 자료형마다의 print 메서드를 만들어 줘야 하기 때문에 자바의 객체지향을 전혀 이용 못하게 되는 코드로 작성해야 한다.
이를 해결하기 위한 방법이 바로 와일드카드이다.


🍄 와일드카드

이를 해결하기 위하여 <?>와 같은 형태로 물음표만 가지고 정의를 한다.
내부적으로 Object로 정의되어서 사용하고 있는 모든 타입의 인자를 받을 수 있다.

public MyArrayList(Collection<?> in) {
    for (T elem : in) {
        element[index++] = elem;
    }
}

public void clone(Collection<?> out) {
    for (Object elem : element) {
        out.add((T) elem);
    }
}

이때, 위와 같이 단순히 ?만 사용하여 정의하면 논리적인 오류가 발생한다.
왜냐하면 어떠한 타입도 올 수 있기 때문에 매개변수를 꺼내거나 저장하는 로직에서 논리적인 에러가 발생한다.
따라서 다음과 같이 바운디드 타입인 extends, super을 사용하여 타입의 범위를 정해주어야 한다.

  • <? extends T>
    • 상한 경계 바운디드 타입
  • <? super T>
    • 하한 경계 바운디드 타입


🍄 Generic Type Erasure

컴파일 타임에만 타입 제약 조건을 정의하고, 런타임에는 타입을 제거한다는 것이다.
다음은 위에서 봤던 GenericClass의 일부분이다.

class GenericClass<T> {
  // 제네릭 메소드
    private T t;

    ...
}

위 코드가 컴파일이 된 후에는 다음과 같이 코드가 변경된다.

class GenericClass {
  // 제네릭 메소드
    private Object t;

    ...
}



개인 공부 기록용 블로그입니다.
틀리거나 오류가 있을 경우 제보해주시면 감사하겠습니다.😁