10) 제네릭1 - 제네릭과 타입 변수

dev-mage·2022년 12월 9일
0

Hello Java World!

목록 보기
31/32
post-thumbnail

Java의 제네릭과 타입 변수

제네릭(Generics)

제네릭은 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시 타입 체크를 제공하는 기능(compile-time type check)이다. 객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성(type safety)을 높이고 형변환의 번거로움이 줄어든다. 타입 안정성을 높인다는 것은 의도하지 않은 타입의 객체를 저장하는 것을 막고, 저장된 객체를 꺼낼 때 원래의 타입과 다른 타입으로 형변환되어 발생할 수 있는 오류를 줄여주는 것을 뜻한다.

예를 들어 ArrayList를 생성할 때, 저장할 객체의 타입을 지정해 주면 지정한 타입 외에 다른 타입의 객체가 저장하려는 경우 에러가 발생한다.

ArrayList<Coffee> coffees = new ArrayList<>();
coffees.add(new Coffee());
coffees.add(new Bread()); // 컴파일 에러. Coffee 타입만 저장 가능.

그리고 이미 어떤 타입의 객체가 저장되어 있는지 알고 있기 때문에 저장된 객체를 꺼낼 때는 형변환할 필요가 없다.

ArrayList coffees = new ArrayList();
coffees.add(new Coffee());
Coffee coffee = (Coffee) coffees.get(0);

////////////////////////////////////////////////////////////////////////

ArrayList<Coffee> coffees = new ArrayList<>();
coffees.add(new Coffee());
Coffee coffee = coffees.get(0); // 형변환 불필요.
  • 제네릭의 장점

    • 타입 안정성 제공.
    • 타입 체크와 형변환 생략 가능으로 간결해진 코드.
  • 제네릭 용어

    • Menu: 제네릭 클래스. ‘T의 Menu’ 또는 ‘T Menu’라고 읽음.

    • T: 타입 변수 또는 타입 매개변수(T는 타입 문자).

    • Menu: 원시 타입(raw type).

      타입 문자 T는 제네릭 클래스 Menu의 타입 변수 또는 타입 매개변수라고 하는데, 메서드의 매개변수와 유사한 면이 있기 때문이다. 그래서 다음과 같이 타입 매개변수에 타입을 지정하는 것을 ‘제네릭 타입 호출’이라 하며 지정된 타입 ‘Coffee’를 매개변수화된 타입이라고 한다.

      예를 들어 Menu와 Menu는 제네릭 클래스 Menu에 서로 다른 타입을 대입하여 호출한 것일 뿐, 이 둘이 별개의 클래스를 의미하는 것이 아니다. 이는 마치 매개변수의 값이 다른 메서드 호출, 즉 add(1, 2)와 add(3, 5)가 서로 다른 메서드를 호출하는 것이 아님과 같다.

      컴파일 후에 Menu와 Menu는 이들의 원시 타입인 Menu로 바뀐다. 즉 제네릭 타입이 제거된다.

타입 변수(type variable)

ArrayList 클래스의 선언에서 클래스 이름 옆의 ‘<>’안에 명시된 클래스를 타입 변수라고 한다.

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

타입 변수가 여러 개인 경우에는 Map<K, V>와 같이 콤마(,)를 구분자로 나열하면 된다. 제네릭이 도입되기 이전에는 다양한 종류의 타입을 다루는 메서드의 매개변수나 리턴 타입으로 Object 타입의 참조 변수를 많이 사용했기에 형변환이 불가피했지만, 제네릭 덕분에 원하는 타입의 타입 변수를 지정해 주기만 하면 된다.

ArrayList와 같은 제네릭 클래스를 생성할 때는 다음과 같이 참조 변수와 생성자에 타입 변수 E 대신 실제 타입을 지정해 주어야 한다.

ArrayList<Coffee> coffees = new ArrayList<Coffee>();

이때 타입 변수 E 대신 지정된 타입 Coffee를 매개변수화된 타입 또는 대입된 타입(parameterized type)이라고 한다. 타입이 대입되고 나면 ArrayList의 선언에 포함된 타입 변수 E가 아래와 같이 대입된 타입으로 바뀐다고 생각하면 된다.

public class ArrayList<E> extends AbstractList<E> ... {
		...
		transient E[] elementData;
		public boolean add(E e) { ... }
		public E get(int index) { ... }
		...
}

////////////////////////////////////////////////////////////////////////

public class ArrayList extends AbstractList ... {
		...
		transient Coffee[] elementData;
		public boolean add(Coffee e) { ... }
		public Coffee get(int index) { ... }
		...
}

제네릭 타입과 다형성

제네릭 클래스의 객체를 생성할 때 참조 변수에 지정해준 제네릭 타입과 생성자에 지정해 준 제네릭 타입은 상속 관계의 부모-자식과 상관 없이 일치해야 한다.

public class Menu { ... }
public class CoffeeMenu extends Menu{ ... }

////////////////////////////////////////////////////////////////////////

ArrayList<Menu> menus1 = new ArrayList<Menu>();
ArrayList<Menu> menus2 = new ArrayList<CoffeeMenu>(); // 에러. 타입 불일치

그러나 제네릭 타입이 아닌 클래스 타입 간 다형성을 적용하는 것은 가능하다. 이 경우에도 제네릭 타입은 일치해야 한다.

List<Menu> menus = new ArrayList<Menu>(); // ArrayList가 List를 구현

제네릭 클래스의 객체를 생성할 때는 참조 변수와 생성자에 지정한 제네릭 타입이 같아야 하지만 값을 저장할 때는 상속 관계에 있는 자식 객체를 저장하는 것이 가능하다. 대신 저장된 객체를 꺼낼 때 형변환이 필요하다.

List<Menu> menus = new ArrayList<Menu>();
menus.add(new CoffeeMenu());
CoffeeMenu coffeeMenu = (CoffeeMenu) menus.get(0);

References

0개의 댓글