티스토리 뷰

Java

[자바] 제네릭스(Generics) - 1

Alledy 2019. 5. 29. 22:41

제네릭스(Generics) - 1

제네릭스란?

다양한 타입의 객체를 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크(compile-time type check)를 해주는 기능이다. 제네릭 타입은 클래스메서드에 선언할 수 있다.

 

제네릭스의 장점?

객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성이 높아지고, 형변환의 번거로움이 줄어든다.

 

제네릭 클래스 선언

// 일반적인 클래스
class Box {
  Object item;
  
  void setItem(Object item) {this.item = item;}
  Object getItem() {return item;}
}

// 제네릭 클래스
class Box<T> {
  T item;
  
  void setItem(T item) {this.item = item;}
  T getItem() {return item;}
}

 

T란 무엇인가?

위에 제네릭 클래스를 선언할 때 뒤에 <T> 가 붙었는데, 여기서 T는 타입 변수(type variable)이라고 한다. 일종의 매개변수 같은 역할을 하면서, 그 자리에 어떠한 참조형 타입이 들어가게 될 것이라는 표시를 해주는 것이다. 임의의 변수이므로 반드시 T가 아니어도 되고 상황에 따라 E, K, V 등 다양한 문자를 사용할 수 있다.

Box<String> b= new Box<String>(); // 실제 타입을 지정
b.setItem("ABC"); // OK
b.setItem(3); // Err! 스트링 타입만 넣어야 함

인스턴스를 만들 때에 T자리에 넣고 싶은 참조변수 타입을 넣으면 된다. 그러면 그 클래스 내에 T가 있던 자리를 그 참조변수 타입이 대체하는 것과 마찬가지다.(실제로 컴파일 뒤에는 제네릭 타입은 제거되고 원시타입 Box로 대체된다.) 위처럼 String을 넣었으면 item의 타입은 String이 될 것이고, Integer를 넣었으면 item의 타입은 Integer가 된다.

 

용어 정리

헷갈리는 용어들을 짚고 넘어가자.

Box<T> // 제네릭 클래스. T Box라고 읽는다.
T // 타입 변수 또는 타입 매개변수
Box // 원시 타입(raw type)

 

제네릭스의 제한

제네릭 클래스에 허용되지 않는 것이 있다.

  1. static 멤버에 대해 타입 매개변수를 사용하는 것.
  2. 제네릭 타입의 배열을 생성하는 것.
class Box<T> {
  static T item; // Err! 
  T[] itemArr; // OK
  itemArr = new T[10]; // Err!
}

static 멤버에 타입변수를 쓸 수 없는 이유는, T는 인스턴스 변수로 간주되기 때문이다. static멤버는 대입된 타입에 관계 없이 항상 같은 값을 유지해야 한다. 즉 Box<Grape>.itemBox<Apple>.item이 달라져서는 안된다는 뜻이다.

제네릭 타입의 배열 생성이 안 되는 이유는 new 연산자 때문인데, 이 연산자는 컴파일 시점에서 타입 T가 정확히 무엇인지 알기를 원하기 때문이다. 그러나 위 코드에서는 T가 어떤 참조변수 타입으로 선언될지 전혀 할 수 없기 때문에 에러가 난다. (그러나 제네릭 배열을 꼭 만들고 싶은 경우에는 Reflection API의 newInstance()와 같은 메서드를 사용하거나 Object 배열을 생성하여 복사한 다음 T로 형변환하는 경우가 있다고 한다.)

 

제네릭 클래스의 객체 생성

Box<Apple> appleBox = new Box<Apple>(); // OK
Box<Apple> appleBox = new Box<Grape>(); // Err
Box<Apple> appleBox = new FruitBox<Apple>(); // OK. 다형성

Box의 객체를 생성할 때에는 참조변수와 생성자에 대입된 타입이 일치해야 한다. 즉 <> 안의 타입은 일치해야 한다. 그래서 실제로 사용할 때에는 new 연산자 뒤의 <> 안에 타입을 생략할 수 있다. (항상 일치해야하므로 앞에만 써도 유추가능하다) 다만 class의 경우 상속관계에 있다면 기존의 다형성이 적용된다.

Box<Fruit> fruitBox = new Box<>(); 
fruitBox.add(new Fruit()); 
fruitBox.add(new Grape()); // OK!

다만 Box의 참조변수 타입이 Fruit이라고 선언되었을 때, void add 메서드에서 Fruit의 자손 인스턴스들은 메서드의 매개변수가 될 수 있다.

 

제한된 제네릭 클래스

그렇다면 제네릭 클래스 Box 에서는 실제로 대입할 때에는 어떠한 참조변수가 들어가도 막을 방법이 없는 것일까?

FruitBox<Toy> fruitbox = new FruitBox<>(); // OK, but...

위 예시에서는 FruitBox에 Toy라는 타입이 지정되어 있다. 구문적으로 문제는 없으나, T에 들어갈 수 있는 타입을 제한하고 싶을 수 있다.

class FruitBox<T extends Fruit> {
  ...
}

이 경우 제네릭 클래스 선언 시, extends를 사용함으로써 특정 클래스의 자손만 받도록 제한할 수 있다. 만약 클래스가 아니라 인터페이스를 구현해야 한다고 해도 implements 가 아닌 extends를 사용한다는 점에 주의해야 한다.

'Java' 카테고리의 다른 글

[자바] 제네릭스(Generics) - 2 (와일드카드)  (0) 2019.05.30
[자바] 제네릭스 문제로 이해하기  (0) 2019.05.30
[표준입출력] nextInt(), nextLine() 차이  (0) 2019.05.28
JDBC 데이터 삽입, 수정, 삭제  (0) 2019.05.22
JDBC  (0) 2019.05.22
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함