generics
지네릭스는 JDK1.5에서 처음 도입되었다. 이젠 지네릭스를 모르고는 JAVA API문서를 제대로 보기 어려울만큼 중요한위치를 차지하였다.
지네릭스란?
메서드나 컬렉션클래스에 컴파일시의 타입체크를 해주는 기능이다.
객체의 타입 안정성을 높이고 형변환의 번거로움을 줄여준다.
타입안정성 = 의도하지 않은 타입의 객체가 저장되는것을 막고 저장된 객체를 꺼내올때 원래의 타입과 다른 타입으로 잘못 형변환되어 발생할 수 있는 오류를 줄여준다.
지네릭 클래스 선언
class Box<T> {
T item;
void setItem(T item){
this.item = item;
}
T getItem() {
return item;
}
}T : 타입변수, T가 아닌 다른것을 사용해도 된다. 이는 임의의 참조형 타입을 의미한다.
기존에는 Object로 참조변수를 많이 사용했는데 그로인해 형변환이 불가피 했다 허나 이젠 Object 대신 원하는 타입을 지정하기만 하면 된다.
타입을 지정해주지 않을때
위처럼 지네릭이 도입되기 이전의 코드와의 호환을 위해 예전방식으로 객체를 생성하는것이 허용된다만, 타입을 지정하지 않아 안전하지 않다는 경고가 표시된다.
왠만하면 반드시 타입을 지정해주자
매개변수와의 유사성
Box<String>과 Box<Integer>는 지네릭 클래스 Box<T>에 서로 다른 타입을 대입해 호출한 것일 뿐, 이 둘이 별개의 클래스를 의미하지 않는다. (같은 클래스라는 말이다.)
컴파일 후에 둘다 모두 이들의 원시타입인 Box로 바뀐다. 지네릭 타입이 제거된다는 의미이다.
지네릭 클래스의 제한
static 멤버에 타입변수 T를 사용할 수 없다.
T는 인스턴스 변수로 간주되는데 static 멤버는 인스턴스 변수를 참조할 수 없다.
static멤버는 타입이 동일한 것이여야 한다. 어떤 객체에서 호출해도 모두 동일하게 동작하며 공유되기 때문이다.
지네릭 타입의 배열을 생성하는것도 허용되지 않는다.
그 이유는 new 연산자 때문인데 이 연산자는 컴파일 시점에 타입T가 뭔지 정확히 알아야한다. Box<T>를 컴파일 하는 시점에 T가 어떤 타입이 될지 알수 없기 때문에 instanceof 도 같은 이유로 사용할수 없다
지네릭 클래스의 객체 생성과 사용
Box.java
FruitBox.java
main.java
제한된 지네릭 클래스
매개변수 T에 지정할 수 있는 타입의 종류를 제한할 수 있는 방법
제한하지 않으면 모든 종류의 타입이 지정되기 때문에 fruitBox에 Toy를 담을수도 있다
extends 사용
FruitBoxExtendsFruit.java
main.java
Fruit와 Eatable 인터페이스 구현
인터페이스를 구현해야 한다는 제약이 존재한다면
Eatable.interface
Fruit의 자손이면서 Eatable을 구현한 클래스
main.java
와일드 카드
<? extends T>: 와일드 카드의 상한 제한, T와 그 자손들만 가능<? super T>: 와일드 카드의 하한 제한, T와 그 조상들만 가능<?>: 제한 없음 모든 타입이 가능하다<? extends Object와 동일
외일드 카드의 필요성
Juice.java
Juicer.java
main.java
Collections.sort()를 이용한 정렬
Fruit
Apple
main.java
이는 Collections.sort()를 이용해 appleBox에 담긴 과일을 무게별로 정렬하는 것이다. Collections의 선언부는 다음과 같다
이는 지네릭 메서드이다. list는 정렬할 대상, c는 정렬할 방법이 정의 된 Comparator이다. 지금 와일드 카드가 사용되어 new FruitComp로도 Apple을 정렬할 수 있다. 만일 와일드 카드를 사용하지 않는다면 Apple은 Comparator<Apple>로 Grape는 Comparator<Grape>로만 정렬할 수 있을것이다. 새로운 과일이 생길때마다 ~Comp.java를 만들어줄수는 없으니 와일드카드로 하한 제한을 해주는것이다.
T에 Apple이 대입되면 다음과 같다
Comparator<? super Apple>은 Comparator의 타입 매개변수로 Apple과 그 조상이 가능하다는거다. 그래서 new FruitComp로 다른 과일들도 정렬가능하다.
몰론 과일의 조상을 Fruit로 상속해주어야 한다.
지네릭 메서드
메서드 선언부에 지네릭 타입이 선언된 메서드를 지네릭 메서드라 한다.
지네릭 클래스에 정의된 타입 매개변수와 지네릭 메서드에 정의 된 매개변수는 전혀 별개의 것이다.
static 멤버에는 타입 매개변수를 사용할 수 없지만 메서드에 지네릭 타입을 선언하고 사용하는 것은 가능하다.
이 타입 매개변수는 메서드 내에서만 지역적으로 사용되기 때문에 지역변수를 선언한 것과 같다고 생각하면 이해하기 쉽다. 그렇기에 static 이든 아니든 상관이 없다.
makeJuice를 지네릭 메서드로 바꾸면 다음과 같다.
이 메서드를 호출할 땐 아래와 같이 타입 변수에 타입을 대입해야 한다.
하지만 대부분의 경우 컴파일러가 타입을 추정할 수 있어 생략해도 된다.
한 가지 주의할 점은 지네릭 메서드를 호출할 때 대입된 타입을 생략할 수 없는 경우에는 참조변수나 클래스 이름을 생략할 수 없다는 것이다. 단지 기술적인 이유이므로 지켜야한다.
매개변수의 타입이 복잡할 때 유용하게 사용가능하다.
지네릭 타입의 형변환
지네릭 타입과 지네릭 타입이 아닌 타입간의 형변환은 항상 가능하다.
하지만 대입된 타입이 다른 지네릭 타입 간에는 형변환이 불가하다.
Optional 클래스
정리하면
Optional<Object>를Optional<String>으로 직접 형변환 하는것은 불가능하지만 와일드 카드가 포함된 지네릭 타입으로 형변환 하면 가능하다는 것이다.참고로 와일드 카드가 사용된 지네릭 타입끼리도 다음과 같은 경우에 형변환이 가능하다. 다만 미확정 타입으로 형변환 하는 것이라는 경고가 뜬다.
지네릭 타입의 제거
컴파일러는 지네릭 타입을 이용해 소스파일을 체크 한뒤 필요한 곳에 형변환을 넣어준다. 그리고 지네릭 타입을 제거한다.
즉 컴파일된 .class 파일에는 지네릭 타입에 대한 정보가 없다.
그 이유는 지네릭이 도입되기 이전의 소스코드와의 호환성을 유지하기 위해서이다.
지네릭 타입의 제거 과정은 꽤 복잡하다. 기본적인 제거 과정만 알아보자.
지네릭 타입의 경게를 제거한다. 지네릭 타입이 <T extends Fruit> 라면 T는 Fruit로 치환되고 <T>인 경우는 Object로 치환된다. 클래스옆의 선언은 제거된다.
지네릭 타입을 제거한 후에 타입이 일치하지 않으면 형변환을 추가한다.
위와같이 와일드 카드가 포함되어 있는 경우에 다음과 같이 적절한 타입으로의 형변환이 추가된다.
Last updated