Generics

zwundzwzig·2022년 12월 17일
0

Java

목록 보기
2/9
post-thumbnail

Generic

JDK1.5부터 도입된 제네릭스는 다양한 타입의 객체를 다루는 메서드나 컬렉션 클래스가 컴파일할 때 타입 체크를 해주며 타입 안정성에 기여한다.

제네릭 타입은 컴파일러가 컴파일할 때만 사용되고, 제거된다. 쉽게 말해 클래스의 지역 변수이며, 임의의 참조형 타입을 의미한다.

필수적으로 사용되는 문자는 없지만 Type의 T, Element의 E, Key의 K, Value의 V 등 상황에 맞게 의미있는 문자를 클래스에서 사용하고, 인스턴스를 생성할 때는 각각의 참조 변수와 생성자에게 알맞는 타입을 제시해야 한다.

이때 해당 타입은 매개변수화된 타입이라고 불리고, 기존 클래스 이름은 원시타입이라고 불리며 제네릭이 붙은 클래스는 제네릭 클래스라고 불린다.

class WorldCup<T> {
  T team;
  
  void setTeam(T team) { this.team = team; }
  T getTeam() { retrun team; }
}

WorldCup<String> team1 = new WorldCup<String>();
team1.setTeam(new Object()); // 에러. 지정된 타입만 가능.
team1.setTeam("Republic of Korea"); // OK.
String team2 = team1.getTeam(); // 형변환 필요 없다.

WorldCup team3 = new WorldCup(); // 여기서 타입은 Object로 여겨진다.
team3.setTeam("Japan"); // 경고. unchecked or unsafe operation

WorldCup team4 = new WorldCup(); // 여기서 타입은 Object로 여겨진다.
team4.setTeam("Qatar"); // 경고. unchecked or unsafe operation

제네릭이 도입되기 전과의 호환을 위해 제네릭 클래스임에도 타입 선언 없이 인스턴스 생성이 가능하지만, 안전하지 않다는 경고문이 뜬다. 그렇기 때문에 타입을 지정해 경고 받지 말자!

다음은 제네릭이 제한되는 몇 가지 상황이다.

  • 모든 객체에 동일하게 동작하는 static 멤버에 사용할 수 없다.
  • 제네릭 배열 타입의 참조변수를 선언하는 건 가능하지만, 제네릭 타입의 배열 선언은 허용되지 않는다.
  • 그 이유로, new 연산자는 컴파일 시점에 해당 타입이 뭔지 알 수 없기 때문이다.
  • 같은 이유로 instanceof 연산자도 사용될 수 없다.
  • 대신, newInstance()와 같이 동적으로 인스턴스를 생성하는 메서드로 배열을 생성하거나, Object 배열 생성 후 복사해서 'T[]'로 형변환해야 한다.

매개변수 상속을 활용해 제네릭 사용하기

만약 여러 데이터 타입을 매개변수로 받으려면 어떻게 할까? => 제네릭 매개변수에 특정 타입을 다루는 클래스나 인터페이스를 상속 extends 하면 가능하다.

class QuaterFinal<T extends WorldCup> {
  ArrayList<T> list = new ArrayList<T>();
}

와일드 카드

앞서 static 메서드에는 타입 T를 매개변수로 사용할 수 없다고 했다. 그렇다면, 해당 메소드에는 특정 타입만 매개변수로 들어가거나, 타입 개수만큼 오버라이딩해 사용해야 할까?

이에 대한 해결책으로 ?로 표현하는 와일드 카드가 있다. 이는 static 메서드의 매개변수의 타입도 제네릭스화하여 사용할 수 있게 한다.

이 역시 상속이 가능하며,

  • <? extends T> 이렇게 사용되면 와일드카드의 상한 제한을 둬 T와 그 자손만 접근이 가능하고,
  • <? super T> 이렇게 사용되면 와일드카드의 하한 제한을 둬 T와 그 부모만 접근이 가능하며,
  • <?> 이렇게 단독으로 사용되면 모든 타입이 가능한 변수가 된다.

제네릭 메서드

와일드 카드를 통해 메서드의 타입을 제네릭화할 수 있다.

대표적인 제네릭 메서드 중 Collection 클래스의 sort 메서드 선언부를 보자.

static <T> void sort(List<T> list, Comparator<? super T> c)

메서드의 첫 번째 매개변수는 정렬할 대상이고, 두 번째 매개변수는 정렬할 방법이 정의된 Comparator이다. Comparator의 제네릭 타입에는 와일드카드를 활용해 하한 제한이 걸려있다.

이때 제네릭 클래스의 타입 매개변수와 제네릭 메서드에 정의된 타입 매개변수가 같은 T로 정의돼 있어도 서로 별개의 것이고, 제네릭 메서드는 제네릭 클래스가 아니어도 정의될 수 있다.

이를 통해 본래 타입 매개변수를 사용할 수 없는 static 멤버에서 제네릭을 사용할 수 있는 것이다.

제네릭 메서드를 호출할 땐 참조변수 or 클래스.<반환 타입>메서드 이름(매개변수) 와 같은 형식으로 사용해야 한다. 이때 참조 변수, 클래스 이름이나 반환 타입은 상황에 따라 생략 가능하다.

제네릭스와 다형성

제네릭 타입과 원시타입 간 형변환은 경고가 발생하긴 하지만 가능하다. 반면 서로 다른 제네릭 타입 간 형변환은 불가하다. 이는 앞서 공부했다.

이러한 문제를 해결해주는 게 바로 와일드카드이다. 하나의 클래스를 상속받는 와일드카드 타입을 통해 여러 타입의 인스턴스를 만들 수 있는 것이다.

profile
개발이란?

0개의 댓글