•
난 제네릭이라고 부른다.
제네릭
•
데이터 타입을 일반화하는 것을 의미한다.
•
제네릭은 클래스나 메소드에서 사용하는 매개변수나 필드들의 데이터 타입을 컴파일 시에 미리 정의해두는 방법이다.
◦
이를 통해 컴파일 시 타입 체크를 진행할 수 있다.
◦
클래스나 메소드 내부에서 사용되는 객체 타입의 안정성을 확보할 수 있다.
◦
반환값에 대한 타입 변환 및 타입 검사를 진행하지 않아 구현단계가 줄어든다.
•
제네릭은 자바 5 버전에서 처음 도입되었는데, 이전의 경우 여러 타입을 반환하는 대부분의 클래스나 메소드에서 인수나 반환값으로 (Object)와 같은 키워드를 사용해야 했었다.
◦
이 경우, 해당 결과를 전달받은 메소드에서 원하는 타입으로 다시 변환해야했으며 이를 구현하는 과정에서 복잡성이 증가하므로 오류를 발생시킬 가능성도 있었다.
•
제네릭을 사용하면 컴파일 시에 미리 타입이 지정되므로 타입 검사나 타입 변환과 같은 번거로운 작업을 생략할 수 있게 되었다.
◦
추상화 수준의 향상으로 인한 이득이라고 할 수 있지 않을까?
⇒ 아무튼 TypeSafe를 보장하기 위한 방법!
제네릭을 사용하는 이유
•
제네릭 타입을 사용하므로써 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있다.
•
자바 컴파일러는 코드에서 잘못 사용된 타입 때문에 발생하는 문제점을 제거하기 위해 제네릭이 사용된 코드에 대해 강한 타입 체크를 한다.
•
뭐든지, 런타임 단계에서 거르는 에러보단 컴파일 단계에서 거를 수 있는 에러가 좋다.
•
메소드를 사용할 때, 제네릭을 사용하면 요청되는 타입으로 범위를 제한시켜 반환하기 때문에 타입 변환에 대한 구체적인 구현이 필요 없어진다.
제네릭 사용법
•
제네릭의 선언 및 생성
class Sample<T> {
T age;
public void setAge(T age) {
this.age = age;
}
public T getAge() {
return this.age;
}
}
class Sample<T> {
T age;
K key;
public void setAge(T age, K key) {
this.age = age;
this.key = key;
}
public T getAge() {
return this.age;
}
public K getKey() {
return this.key;
}
}
Java
복사
•
타입 변수
◦
임의의 참조형 타입을 의미한다. 여기선 T로 표현되었다.
◦
아래의 네이밍을 참조해서 지켜주는 것이 좋다.
▪
E: 요소, K: 키, V: 값, T: 타입, N: 숫자, S,U,V: 두 번째, 세 번째, 네 번째에 선언된 타입
◦
타입 변수 클래스뿐만 아니라 메소드의 매개변수나 반환값으로도 사용 가능하다.
◦
바이트코드 단계에서 Object로 변환되서 사용된다. 즉, 제네릭은 컴파일 시점까지만 유효하다.
제네릭 주요 개념
•
바운드 타입과 와일드카드에 대해 설명한다. 두 개념 모두 타입 형식을 제한하는 기능이다. 그럼에도 불구하고 두 개념이 존재하는 이유는 뭘까?
•
먼저 변성에 대해서 알아보자.
◦
변성이란 제네릭을 사용할 때, 상속에 관련한 이슈가 발생할 때 접할 수 있다.
◦
먼저 간단한 예시를 통해서 설명하자면 다음과 같다.
▪
Integer는 Number를 상속받아 만들어진 클래스다.
▪
따라서 Integer는 Number의 하위 타입이다.
▪
다음과 같이 표현할 수 있다.
public void test() {
List<Number> list;
list.add(Integer.valueOf(1));
}
Java
복사
◦
하지만 List<Integer>는 List<Number>의 하위 타입이 될 순 없다.
◦
이런 상황에서 Java는 제네릭을 사용할 때, 타입 경계를 명시하여 SubType, SuperType을 가능하게 해주는데 이를 변성이라고 한다.
◦
제네릭은 기본적으로 불공변성을 가지고 있다.
•
바운드 타입
◦
바운드 타입은 특정 타입의 서브 타입으로 제한하는 기능이다.
◦
클래스나 인터페이스를 설계할 때, 가장 흔하게 사용되는 개념이다.
class Sample<T extends Number> {
T age;
public void setAge(T age) {
this.age = age;
}
public T getAge() {
return this.age;
}
}
// 오류
Sample test2 = new Sample();
test2.setAge("1"); // Number의 하위항목으로 제한시켰으나 하위항목이 아닌 String이 들어옴
System.out.println(test2.getAge());
Java
복사
•
와일드카드
◦
제네릭을 이용해 구현된 메소드의 경우, 선언된 타입의 매개변수만 입력 가능하다.
◦
자식 클래스나 부모 클래스를 사용할 수 없으며 여러 타입이 사용되어지는 경우 대응하기 쉽지 않다.
◦
이를 제공하기 위해 와일드카드를 사용한다.
◦
Unbounded WildCard
▪
List<?>와 같은 형태로 물음표만 가지고 정의되는 형태다.
▪
내부적으로 Object로 정의되어 사용되며 모든 타입의 인자를 받을 수 있다. 다음과 같은 경우에서 사용하는 것이 일반적이다.
•
Object 클래스에서 제공되는 기능을 사용하여 구현할 수 있는 메서드를 작성하는 경우
•
타입 파라미터에 의존적이지 않는 일반 클래스의 메소드를 사용하는 경우
◦
List.clear(), List.size() 등
◦
Upper Bounded WildCard
▪
List<? extends Foo>와 같은 형태로 정의되는 형태다.
▪
특정 클래스의 자식 클래스만을 인자로 받는다는 것으로 임의의 Foo 클래스를 상속받는 어느 클래스가 와도 되지만 사용할 수 있는 기능은 Foo 클래스에 정의된 기능만 사용 가능하다.
◦
Lower Bounded WildCard
▪
List<? super Foo>와 같은 형태로 정의되는 형태다.
▪
Upper Bounded Wildcard와 다르게 특정 클래스의 부모 클래스만을 인자로 받는다.
•
PECS(Producer-Extends, Consumer-Super) 공식
◦
그렇다면 도대체 언제 super를 사용해야 하고, 언제 extends를 사용해야 하는지 헷갈릴 수 있다.
◦
이펙티브 자바에서는 PECS라는 공식을 만들었는데, 이는 Producer-Extends, Consumer-Super의 줄임말이다.
◦
즉, 컬렉션으로부터 와일드카드 타입의 객체를 생성 및 만들면 extends를, 갖고 있는 객체를 컬렉션에 사용 또는 소비하면 super를 사용하라는 것이다.