티스토리 뷰

Backend/Java

제네릭(Generic)의 기본 개념

ellie.strong 2022. 3. 7. 19:47
728x90

네오의 Generic 수업을 들으며 "아.. 제네릭 공부해야겠다.."라고 생각했지만.. 계속 미루고만 있었다. 그러다 로또 미션 학습 로그 말하기 때 어썸오가 "Type Erasure"를 주제로 얘기 하는 걸 들으면서 "아 맞다 얼른 제네릭 공부해야지.."라고 생각했다. 드디어 오늘 이펙티브 자바 스터디에서 제네릭에 관한 아이템들을 발표하는 것을 들으며 "오늘 안에는 제네릭 공부 시작한다!!"가 되었고, 이렇게 글을 작성하게 되었다. 난 좀 맞아야할 것 같다 👊

 

사실 제네릭이 뭔지는 알고 있었다. 대학교에서 자바 수업을 들을 적 당연히 배웠고, 과제도 하고, 시험도 봤다. 하지만 정작 이게 뭔지 이해를 한 적도, 내 코드에 어떻게 적용되고 있는 지에 대한 고민을 한 적도 없었다. 

대2 때 공부한 흔적..

일단 제네릭의 기본 개념부터 시작해서, 제네릭 생성, 와일드 카드 등 확장해나가보려한다. 

 

제네릭(Generic)

제네릭은 모든 종류의 타입을 다룰 수 있도록, 인터페이스, 클래스, 메소드를 타입 매개변수(Type Parameter)를 이용하여 선언하는 기법이다.

우리가 평소 자주 사용하는 List 인터페이스와 ArrayList 클래스는 다음과 같이 타입 매개변수를 사용하여 선언한다. 

이러한 인터페이스, 클래스를 제네릭 인터페이스, 제네릭 클래스라한다. 

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

그렇기에 String, Integer, Double 혹은 사용자가 작성한 타입 등 다양한 타입을 지정해서 사용할 수 있다. 

List<Integer> numbers = new ArrayList<>();

- <>를 사용해 ArrayList가 관리하는 타입을 지정한다. -> 구체화된(specialized) ArrayList

 

타입 매개변수(Type Parameter)

E : Element, 컬렉션의 요소

T : Type

V : Value

K : Key

 

로 타입(Raw Type)

제네릭 타입을 하나 정의하면 그에 딸린 로 타입(Raw Type)도 함께 정의된다. 
로 타입은 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때를 말한다. 
List array = new ArraysList();

- 제네릭 타입 정보가 전부 지워진 것처럼 동작한다. 

- 제네릭이 JDK 1.5부터 도입되었기 떄문에, 이전 코드와 호환되도록 하기 위한 궁여지책이다. 

 

타입 매개변수로 타입을 지정하지 않았기 때문에..

// String 인스턴스만 취급한다.
List stringList = new ArrayList();
stringList.add("1");
stringList.add("2");
stringList.add(3); // 컴파일 에러 X

- 실수로 다른 타입을 삽입해도 컴파일 에러, 런타임 에러가 발생하지 않는다. 

- 인텔리제이에서 보면, 경고를 알려주긴 하지만 컴파일 오류를 내지는 않으며 잘 실행된다. 

for (Iterator i = stringList.iterator(); i.hasNext(); ) {
    String s = (String) i.next(); // 런타임 에러!!
    //필요한 작업
}

- 형변환이 필요하다. 

- List 안에 String 외의 다른 타입이 존재할 수 있으며, 이럴 경우 형변환 과정에서 런타임 오류(ClassCastException)가 발생한다. 

 

물론 로 타입을 사용하면 한 List 안에 여러 타입을 사용할 수 있게 된다. 하지만 이게 과연 장점일까? 

@Test
void noGeneric() {
    ArrayList list = new ArrayList();
    list.add("this is string");
    list.add(1);
    list.add(new Point(1, 2));

    String first = (String) list.get(0);
    int second = (int) list.get(1);
    Point third = (Point) list.get(2);
}

 

🤡 로 타입을 쓰면 제네릭이 안겨주는 안전성과 표현력을 모두 잃게 된다. 

 

제네릭 타입(Generic Type)

List<String> strings = new ArrayList<>();
strings.add("1");
strings.add("2");
strings.add(3); // 컴파일 에러

- 지정한 타입만 추가할 수 있다. 실수로 다른 타입의 인스턴스를 삽입하면 컴파일 에러가 발생한다. 

 

정리해보자면

✅  특정 타입으로 제한함으로써 타입 안정성을 제공한다. 

        - 동적으로(런타임에) 타입이 결정되지 않고, 컴파일 타임에 타입이 결정된다. 

        - 런타임 타입 충돌 문제를 방지한다. (ClassCastExceptoin 방지)

✅  타입 체크와 형변환을 생략할 수 있으므로 코드가 간결해진다. 

 

🤡  특별히 예외적인 상황이 아니라면 제네릭을 사용하자!!

 

 

Ref.

[이펙티브 자바 3/E] 아이템 26 : 로 타입은 사용하지 말라

명품 JAVA Programming

 

728x90
댓글
공지사항
최근에 올라온 글