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 파일에는 지네릭 타입에 대한 정보가 없다.

  • 그 이유는 지네릭이 도입되기 이전의 소스코드와의 호환성을 유지하기 위해서이다.

  • 지네릭 타입의 제거 과정은 꽤 복잡하다. 기본적인 제거 과정만 알아보자.

  1. 지네릭 타입의 경게를 제거한다. 지네릭 타입이 <T extends Fruit> 라면 T는 Fruit로 치환되고 <T>인 경우는 Object로 치환된다. 클래스옆의 선언은 제거된다.

  2. 지네릭 타입을 제거한 후에 타입이 일치하지 않으면 형변환을 추가한다.

  • 위와같이 와일드 카드가 포함되어 있는 경우에 다음과 같이 적절한 타입으로의 형변환이 추가된다.

Last updated