[Java] 제네릭 (Generic)

kai6666·2022년 5월 19일
0

TIL. Java

목록 보기
14/21
post-thumbnail

제네릭 (Generic)

코딩을 하다보면 아무리 계획적으로 프로그래밍하고 테스트를 해도 빈번히 오류를 마주하게 된다. 그 오류가 컴파일 에러면 그나마 다행인데, 이미 코딩을 많이 진행한 상태에서 어느 시점에 발생한지 알 수 없는 실행 오류면 문제가 크다. 실행 에러는 한 마디로 프로그램이 '죽는' 것이기 때문에, 문제를 금방 찾기 어렵다. 이런 상황을 방지해주는 것이 제네릭이다.
이미지출처

제네릭클래스 내의 데이터 타입을 클래스 밖에서 파라미터 형태로 지정하여 데이터 타입을 일반화하는 것이다.

List myList = new List();
myList.add(new Integer(11));
Integer x = (Integer) myList.iterator().next();

제네릭 없이 코드를 짠다면 위와 같을 것이다. 이 방법도 실행시 문제는 없다.

List<Integer>
	myList = new List<Integer>();
    myList.add(new Integer(11));
    Integer x = myList.iterator().next();

다이아몬드 연산자<>를 활용하여 제네릭을 추가해주면, 컴퓨터는 이 List가 Integer로 이루어진 List라는 것을 알 수 있다. 코드 양이 대단히 줄어든 것은 아니지만, 제네릭을 활용하면 컴파일러가 컴파일시 올바른 타입이 사용되는지 체크를 해준다.

방대한 양의 코드를 짤 때는 제네릭 덕분에 타입체크와 형변환을 생략해서 코드가 간결해질 뿐만 아니라, 전체적인 타입 안정성이 좋아지기 때문에 가급적이면 코딩시 제네릭을 활용해서 이런 이점을 누리는 것이 효율적이다.

public class 클래스명<타입 매개변수>{...}
public interface 인터페이스명<타입 매개변수>{...}

빈번히 사용하는 타입 매개변수는 다음과 같다. 타입 매개변수는 보통 하나의 대문자로 작성하고, 제네릭 클래스는 여러 개의 타입 매개변수를 가질 수 있다.


타입인자설명
<T>Type
<E>Element
<K>Key
<N>Number
<V>Value
<R>Result

타입 매개변수 자리에 와일드 카드? 기호를 사용하면 모든 타입이 사용 가능해진다. 때문에 타입이 일치하지 않는 다른 객체를 참조할 수 있다.

ArrayList<? extends Parent> list = new ArrayList<Children>(); // OK
ArrayList<Parent> list = new ArrayList<Children>(); // Error

다시 말해, <?> 혹은 <? extends Object>를 쓰면 타입에 제한이 없다. extends를 사용해 <? extends T>라고 하면, 와일드 카드에 상한 제한이 걸린 것이기 때문에, T와 그 하위/자손 타입에 제한이 생긴다. 반대로 super를 사용해 <? super T>라고 하면 하한 제한이라 T와 그 상위/조상 타입만 가능하다.


제네릭 클래스

제네릭 클래스를 정의하기 위해서는 우선

class GenericClass<T> { }

이러한 형태를 만들어준 후,

class GenericClass<T> {
	public T t;
    public T get() {return t;}
    public void set(T t) {this.t = t;}
    }

이런 식으로 속성과 메서드를 정의해준다.

GenericClass<String> generic = new GenericClass<String>();

이후 제네릭 객체를 생성하고,

generic.set("generic exercise");
System.out.println(generic.get()); // 출력: generic exercise

제네릭 객체를 사용해보면 이러한 출력값을 얻을 수 있다.

제네릭 메서드

제네릭 클래스를 선언하는 것뿐만 아니라, 클래스 내부 특정 메서드만 제네릭으로 선언할 수 있다. 제네릭 메서드는 한 마디로 타입 변수가 선언된 메서드다.

class Example<T> {
static <T> void sort(...) { ...} 
}

제네릭 클래스의 타입 변수와 제네릭 메서드의 타입 변수가 같아 보이더라도, 전혀 다른 것이다. 위 예시에서도 <T>가 둘 다 들어있어 같은 타입 변수 같겠지만, 이는 문자만 같은 것이다. 이와 같은 이유로 내외부 클래스의 타입 문자가 같아도 별개의 것이라고 이해할 수 있다.

또한, 일반적으로 static 멤버에는 타입 매개 변수를 쓸 수 없지만, 메서드에 제네릭 타입을 선언하고 쓰는 것은 가능하기 때문에 위 코드에도 static이 붙었다. 메서드에 제네릭 타입을 선언하는 건 지역 변수를 선언한 것과 비슷하기 때문에, 어차피 지역적으로 사용될 거라 메서드가 static이건 아니건 무관하다.


❓ 제네릭 메서드와 와일드카드, 각 언제 사용?

제네릭 메서드와 와일드카드는 비슷한듯 완전히 다른 개념이다. 범용할 수 있는 경우도 있으나 용도에 차이가 있고, 보통 와일드카드를 쓸 수 없을 때 제네릭 메서드를 쓴다.

  • 와일드카드:
    하나의 참조변수로 대입된 타입이 서로 다른 여러 제네릭 객체를 다룰 수 있게 하기 위함
  • 제네릭 메서드:
    제네릭 클래스처럼 호출할 때마다 다른 타입을 대입할 수 있게 하기 위함

더 깊이 알고 싶다면 공식 문서 를 참고하자.


참고 자료

profile
성장 아카이브

0개의 댓글