Haneul's Blog

[Java] 제네릭 본문

Java

[Java] 제네릭

haneulss 2022. 10. 26. 22:34

목표 [백기선 자바 스터디 14주차]

자바의 제네릭에 대해 학습하세요.

학습할 것

  • 제네릭 사용법
  • 제네릭 주요 개념 (바운디드 타입, 와일드 카드)
  • 제네릭 메소드 만들기
  • Erasure

 

제네릭(Generics)이란?

제네릭은 다양한 타입의 객체를 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크(compile-time type check)를 해주는 기능입니다. 객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움을 줄여줍니다.

 

그렇다면 타입 안정성을 높인다는 것을 정확히 무슨 의미일까요?

의도하지 않은 타입의 객체가 저장되는 것을 막고, 저장된 객체를 꺼내올 때 원래의 타입과 다른 타입으로 잘못 형변환 되어 발생할 수 있는 오류를 줄여준다는 것입니다.

 

제네릭을 사용해야 하는 이유는?

  • 제네릭 타입을 사용함으로써 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있기 때문입니다.
  • 자바 컴파일러는 코드에서 잘못 사용된 타입 때문에 발생하는 문제점을 제거하기 위해 제네릭 코드에 대한 강한 타입 체크를 해줍니다.
    실행 시에 타입 에러가 나는 것보다는 컴파일 시에 미리 타입을 강하게 체크하여 에러를 사전에 방지하는 편이 좋습니다.
  • 제네릭 코드를 사용하면 타입을 국한하기 때문에 요소를 찾아올 때 타입 변환을 할 필요가 없어 프로그램 성능이 향상되는 효과를 얻을 수 있습니다.

 

아래는 제네릭을 정의하는 예시입니다.

class Test<T> {
    T value;
    void setValue(T value) {
        this.value = value;
    }

    T getValue() {
        return value;
    }

}

 

 

제네릭 주요 개념(바운디드 타입, 와일드 카드)

바운디드 타입

제네릭 타입에서 타입 인자로 사용할 수 있는 타입을 제한하려는 경우가 있을 수 있습니다. 이럴 때 사용하는 것이 바운디드 타입이라고 할 수 있습니다.

 

바운디드 타입 파라미터를 선언하려면 타입 파라미터의 이름, extends 키워드, 상위 바운드를 나열합니다.

<T extends 상위 바운드>

여기서의 extends는 implements의 기능까지 포함하여 상위 바운드는 인터페이스도 될 수 있습니다.

 

또한 아래와 같이 여러 개의 바운드를 가질 수 있습니다.

<T extends A & B & C>

 

만약 여러 개의 바운드 중 클래스가 있다면 클래스가 아래와 같이 앞으로 와야 합니다.

<T extends 클래스 & 인터페이스1 & 인터페이스2>

 

 

아래는 ArraysList 클래스에 Double형 자료들만 넣을 수 있도록 제네릭을 이용한 예시입니다.

public class ArrayList <T extends Number> {
    private int size;
    private Object[] arr = new Object[5];

    public void add(T value) {
        arr[size++] = value;
    }

    public T get(int idx) {
        return (T)arr[idx];
    }
}

// main
public class Main {
    public static void main(String[] args){
        ArrayList<Double> list = new ArrayList();
        list.add(2.1);
        list.add(1.1);

        Double value1 = list.get(0);
        Double value2 = list.get(1);

        System.out.println(value1 + value2);
    }
}

-> extends 예약어를 통해 Number의 하위클래스(Double)의 자료형만 갖는 ArrayList를 선언하였습니다. 인터페이스로 제한하더라도 extends 예약어를 사용함에 유의해야 합니다. 코드를 보면 Object형의 배열을 선언하여 사용하고 있는데, 제네릭 타입은 new를 사용할 수 없기 때문입니다. 왜냐하면 new 연산자는 힙 영역에 메모리 공간을 확보해야하는데, 컴파일 시점에서는 T의 자료형이 무엇인지 알 수 없기 때문에 private T[] arr = new T[5] 와 같은 코드는 작성할 수 없습니다.

와일드 카드

제네릭 타입 코드에서 와일드 카드라고 하는 물음표(?)는 알 수 없는 유형을 나타냅니다.
와일드 카드는 파라미터 변수, 필드 또는 지역변수의 타입 등 다양한 상황에서 사용할 수 있고, 제네릭 메서드 호출, 제네릭 클래스 인스턴스 생성 또는 수퍼 타입의 타입 인자로는 사용될 수 없습니다.

 

와일드 카드는 아래와 같이 ? 기호와 extends, super를 사용하여 만들 수 있습니다.

<? super T> : 와일드 카드의 상한 제한. T와 그 자식들만 가능 -> Lower bounded
<? extends T> : 와일드 카드의 하한 제한. T와 그 조상들만 가능 -> Upper bounded
<?> : 제한 없이 모든 타입 가능. <? extends Object> 와 동일  -> Unbounded
  • Lower bounded : 특정 클래스의 부모 클래스만 인자로 받습니다.
  • Upper bounded : 특정 클래스의 자식 클래스만 인자로 받습니다.
  • Unbounded : Object로 정의되어 사용되고, 어떤 객체든 받을 수 있습니다.

 

제네릭 메서드 만들기

제네릭 메서드란 메서드의 선언부에 타입 변수를 사용한 메서드를 의미합니다. 이때 타입 변수의 선언은 메서드 선언부에서 반환 타입 바로 앞에 위치합니다.

 

아래는 제네릭 메서드를 정의하는 예시입니다.

public static <T> void test(List<? extends T> lst){}

 

Erasure

제네릭은 타입의 안전성을 보장하며 실행시간에 오버헤드가 발생하지 않도록 하기 위해 추가되었습니다. 컴파일러는 컴파일 시점에 제네릭에 대하여 type erasure라고 불리는 프로세스를 적용합니다.

 

type erasure는 모든 타입의 파라미터를 제거하고 나서 그 자리를 제한하고 있는 타입으로 변경하거나 타입 파라미터의 제한 타입이 지정되지 않았을 경우에는 Object로 대체합니다. 따라서 컴파일 후에 바이트 코드는 새로운 타입이 생기지 않도록 보장하는 일반 클래스들과 인터페이스, 메서드들만 포함합니다. Object 타입도 컴파일 시점에 적절한 캐스팅이 적용됩니다.

'Java' 카테고리의 다른 글

[Java] TDD란?  (0) 2022.11.03
[Java] 람다식  (0) 2022.10.27
[Java] I/O  (0) 2022.10.25
[Java] 애노테이션  (0) 2022.10.24
[Java] Enum  (0) 2022.10.23