제네릭의 기본 이해

제네릭이 무엇인지, 왜 제네릭을 사용하는지 그리고 자바는 제네릭을 어떻게 사용하고 있는지 알아보기

제네릭이란?

제네릭은 사전적 의미 자체적으로도 "일반적인" 이라는 뜻을 갖고 있으며, 자바에서는 클래스 내부에서 사용할 데이터 타입을 외부에서 직접 지정하는 방식이다.

제네릭은 사용할 타입을 미리 결정하지 않는 것이 중요한데, 클래스 내부에서 사용하는 타입을 클래스를 정의 하는 시점이 아닌 실제 사용하는 생성 시점에 타입이 결정되는 매커니즘이다.

제네릭 명명 관례

타입 매개변수는 변수명 처럼 아무렇게 작성해도 상관이 없다. 하지만, 일반적으로 대문자를 사용하고 용도에 맞도록 영문자의 앞 첫글자를 사용하는 관례를 따른다.

keyword
description

E

Element

K

Key

N

Number

T

Type

V

Value

S, U, V

2nd, 3rd, 4th type

제네릭은 왜 필요할까?

다양한 데이터 타입을 사용하여 데이터를 저장하고 조회할 수 있는 기능이 필요하다고 가정한다.

public class IntegerBox {

    private Integer value;

    public void set(Integer value) {
        this.value = value;
    }

    public Integer get() {
        return value;
    }

}

데이터 타입에 매칭되는 객체가 하나씩 생겨났고, 메인 로직에서 원하는 타입을 담고 싶을 때 마다 새로운 객체를 만들어내야 한다.

만약, 여기서 Double, Boolean 타입도 저장하고 조회할 수 있는 기능이 필요하다면 어떨까?

제네릭을 사용하지 않고 Object 의 다형성 특성을 활용하여 해결 해보기

위 예시에서 제네릭이라는 기법을 모르는 상태로 이 문제를 해결하고자 할 때 쉽게 사용할 수 있는 다형성의 특성을 살리면 된다.

모든 객체는 Object 의 하위 타입이라는 것을 알고 있으니, Object 로 저장하고 조회하면 된다.

public class ObjectBox {

    private Object value;

    public void set(Object value) {
        this.value = value;
    }

    public Object get() {
        return value;
    }

}

Object 를 이용해서 문제를 해결해보니 데이터 타입에 맞는 새로운 객체를 만들지 않아도 되어 재사용이 가능한 이점이 있다.

하지만, Object 타입은 모든 객체의 상위 타입이기 때문에 숫자 타입을 조회하고 싶었으나 의도치 않게 문자 타입이 들어가거나 하는 등 의도하지 않은 타입으로 인해 캐스팅이 불가능하여 예외가 발생할 수 있다.

Object 로 해결하는 방법은 재사용을 통해 여러 객체가 생성 되는 것을 보완 하였지만 타입의 안정성을 지키지 못하였다.

제네릭 적용 하여 문제 해결 하기

public class GenericBox<T> {

    private T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }

}

Object 로 풀었던 방식과 비슷하지만, Generic 클래스를 구성하게 되어 객체의 타입을 클래스 내부에서 지정하지 않고 외부에서 정의하게 되어 데이터를 저장하고, 조회할 때 같은 타입을 바라보게 되었다.

이로써 제네릭을 사용하지 않을 때의 문제점인 코드 재사용성 과 객체를 저장할 때와 반환할 때 타입이 동일한 타입 안정성 을 모두 해결할 수 있다.

제네릭 사용시 주의할 점

1

제네릭 타입을 지정하지 않는 경우 Object 로 타입 추론된다.

2

제네릭 타입은 참조형 객체만 사용할 수 있다. -> 원시 타입은 사용할 수 없다.

자바에서는 제네릭을 어떻게 사용하고 있을까?

자바에서는 이런 제네릭을 굉장히 많이 쓰고있는데, 그 중 개발을 하다보면 자주 마주치는 컬렉션인 ArrayList 를 살펴보자.

ArrayList<String> stringArray = new ArrayList<>();
stringArray.add("hello");

for (String s : stringArray) {
    System.out.println("s = " + s);
}

대충 배열에 값을 담기 위해 ArrayList 객체를 생성하게 되는데 이 때 마찬가지로 제네릭을 사용하는 문법이 등장한다.

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable
{
    ...
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
}

그 외에도 HashMap 도 동일하게 이런 제네릭을 사용하여 사용자가 자유롭게 다양한 타입을 이용할 수 있는 컬렉션을 구성한 것을 알 수 있다.

참고 자료


Last updated