제너릭(Generic)이란?

2022년 02월 28일

TOC

제너릭이란?

제너릭이란 무엇일까요?? 제너릭이 무엇인지 몰라도 자바 프로그래밍을 하며 <T>, <E>와 같은 문자들을 접해봤을 것입니다. 딱 봐도 어려워보이는 이것, 이러한 것들을 우리는 제너릭이라고 합니다.

그럼 제너릭에 대해 알아보도록 하겠습니다.

제너릭은 하나의 값이 여러 자료형으로 쓰일 수 있도록 하는 방법을 의미합니다. 대표적인 예시를 들어보면 List가 있습니다. List 인터페이스와 ArrayList의 구현체를 보면 다음과 같습니다.

public interface List<E> extends Collection<E> {}

public class ArrayList<E> extends AbstractList<E>
  implements List<E>, RandomAccess, Cloneable, java.io.Serializable {)

이러한 인터페이스와 구현체를 통해 우리가 인스턴스를 생성할 떄는 어떤 식으로 생성하나요? 정수형의 리스트와 문자열의 리스트를 생성하면 아마 아래의 코드와 같이 생성하였을 것입니다.

List<Integer> integers = new ArrayList<>();
List<String> strings = new ArrayList<>();

이와 같이 List<E>에서 <E>위치에 Integer타입이 들어가면 정수형 리스트, String타입이 들어가면 문자열 리스트가 생성됩니다.

이렇게 클래스 내부에서 지정하는 것이 아닌 외부에서 사용자에 의해 타입을 지정하는 것을 제너릭(Generic)이라고 합니다.

제너릭의 장점

  • 잘못된 타입이 들어올 수 있는 것을 컴파일 단계에서 방지할 수 있다.
  • 특정 타입으로 제한함으로써 타입의 안정성을 제공한다.
  • 타입 체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.
  • 비슷한 기능을 지원하는 경우 코드의 재사용성이 높아진다.

제너릭 사용하기

제너릭 타입

일반적으로 제너릭을 사용할 때는 아래의 타입들을 많이 사용합니다. 꼭 해당 타입과 일치할 필요는 없지만 암묵적으로 정해진 규칙인 만큼 우리도 지키는 것이 좋습니다.👍🏻

타입 내용
<T> Type
<E> Element
<K> Key
<V> Value
<N> Number

클래스와 인터페이스 선언

public class GenericClass<T> { }
public interface GenericInterface<T> { }

제너릭 클래스와 인터페이스는 아래와 같이 선언을 합니다. 이때 제너릭 타입의 경우 1개가 아닌 두개 이상으로도 둘 수 있습니다. 2개 이상의 제너릭타입을 사용하는 대표적인 예로는 Map 인터페이스와 구현체가 있습니다.

객체 생성을 할 떄 제너릭 타입에 입력할 타입의 경우 int,double,long,char과 같은 기본 타입(Primitive type)은 들어갈 수가 없으며, 참조 타입(Reference type)만 들어갈 수 있습니다. 만약 primitive type을 사용하고 싶다면 wrapper class를 사용해야합니다.

사용 예시는 다음과 같습니다.

public class GenericClass<E> {
  private E element;
}

public class Main {
    GenericClass<Integer> integerGenericClass = new GenericClass<>();
    GenericClass<String> stringGenericClass = new GenericClass<>();
}

제너릭 메서드

제너릭을 사용한 클래스와 인터페이스의 경우, 클래스와 인터페이스 내에서 해당 제너릭을 사용할 수 있었습니다. 제너릭은 클래스 뿐만 아니라 특정 메서드 내에서만 사용할 수도 있습니다.

public <T> T genericMethod(T o) { }

제너릭 메서드의 경우 반환타입 이전에 제너릭타입(<T>)을 선언해줘야 합니다.

사용 예시는 다음과 같습니다.

public static void main(String[] args) {
    GenericTest genericTest = new GenericTest();
    System.out.println(genericTest.genericMethod("Test").getClass());
    // result : class java.lang.String
    System.out.println(genericTest.genericMethod(123).getClass());
    // result : class java.lang.Integer
}

만약 제너릭 메서드를 정적(static) 메서드로 만들 경우, 제너릭 클래스에 위치한 제너릭과 다른 제너릭을 사용해야합니다. 그렇지 않을 경우 클래스에 정의된 제너릭 타입이 지정되지 않아 에러가 발생합니다.

제한된 제너릭과 와일드 카드

제너릭 범위를 제한하고 싶다면 extends, super, 와일드카드(?)를 사용해야합니다.

범위 지정의 방법들은 다음과 같습니다.

<K extends T>	// T와 T의 자손 타입만 가능 (K는 들어오는 타입으로 지정 됨)
<K super T>	// T와 T의 부모(조상) 타입만 가능 (K는 들어오는 타입으로 지정 됨)

<? extends T>	// T와 T의 자손 타입만 가능
<? super T>	// T와 T의 부모(조상) 타입만 가능
<?>		// 모든 타입 가능. <? extends Object>랑 같은 의미

이 떄 extends는 상한 경계, super는 하한 경계라고 합니다.

Reference

Buy me a coffeeBuy me a coffee
Written by

@Seongwon

기술공유를 통해 새로운 가치 창조을 추구하는 백엔드 개발자 오성원입니다.
©SeongwonOh