자바의정석 - 지네릭스
지네릭스
지네릭스는 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입체크(compile-time type check)
를 해주는 기능이다. 객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어든다. 또한 원하지 않는 종류의 객체가 포함되는 것을 막아주는 역할도 한다.
[지네릭스의 장점]
타입 안정성을 제공한다.
타입체크와 형변환을 생략할 수 있으므로 코드가 간결해 진다.
지네릭 클래스의 선언
GenericsBox
package com.java.mystudy.generis;
// 일반적인 class
public class NomalModel {
String item;
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
}
package com.java.mystudy.generis;
// 지네릭스를 활용한 class
public class GenericsModel<T> {
T gItem;
public T getgItem() {
return gItem;
}
public void setgItem(T gItem) {
this.gItem = gItem;
}
}
public String main() {
// Only String
NomalModel nm = new NomalModel();
// String type
GenericsModel<String> gm1 = new GenericsModel<>();
// Integer type
GenericsModel<Integer> gm2 = new GenericsModel<>();
nm.setItem("Nomal Model");
gm1.setgItem("Generis(String) Model");
// Generis(Integer) Model
gm2.setgItem(2);
StringBuilder sb = new StringBuilder();
sb.append("Nomal Model: ").append(nm.getItem())
.append(" Generis(String) Model: ").append(gm1.getgItem())
.append(" Generis(Integer) Model: ").append(gm2.getgItem());
return sb.toString();
}
[출력결과]
지네릭스의 제한
위의 예제소스의 GenericsModel을 통해 객체별로 다른 타입을 지정하는것은 적절하며 그러한 용도로 사용하기 위해 지네릭스를 사용한 것이다. 하지만 모든 객체에 대해 동일하게 동작해야하는 static맴버
에 타입 변수 T를 사용할 수 없다. static멤버는 인스턴스변수를 참조할 수 없기 떄문이다.
// 지네릭스를 활용한 class
public class GenericsModel<T> {
static T gItem; // error
}
추가로 지네릭 타입의 배열을 생성하는 것도 허용되지 않는다. 지네릭 배열 타입의 참조 변수를 선언하는 것은 가능하지만 new T[10]
과 같이 배열을 생성하는 것은 안된다. 이유는 new연산자는 컴파일 시점에 타입 T가 뭔지정확히 알아야 한다. 그런데 new T[10]
과 같이 정의된 코드를 컴파일하는 시점에서 T가 어떤 타입이 될지 알 수 없다. (instanceof 연사자도 같음)
지네릭 클래스의 객체 생성과 사용
class Box<T> {
ArrayList<T> list = new ArrayList<T>();
void add(T item) {
list.add(item);
}
T get(int i) {
return list.get(i);
}
ArrayList<T> getList() {
return list;
}
int size() {
return list.size();
}
public String toString() {
return list.toString();
}
}
public String generics() {
GenericsParent<String> pList = new GenericsParent<>();
pList.add("Mother");
pList.add("Father");
return "Parent Size: " + pList.size() + " | Parent List: " + pList.get(0) + " " + pList.get(1);
}
위와 같은 지네릭 클래스가 있다고 하자.
Box<Apple> appleBox = new Box<Apple>(); // OK
Box<Apple> appleBox = new Box<Grape>(); // error
Box<Grape> appleBox = new Box<Grape>(); // OK
Box
Box<Fruit> appleBox = new Box<Apple>(); // error
두 타입이 상속관계에 있어도 마찬가지 타입이 다르기에 에러가 발생한다.
Box<Apple> appleBox = new FruitBox<Apple>(); // OK
단, 두 지네릭 클래스의 타입이 상속관계에 있고 대입된 타입이 같은 것은 error가 발생하지 않는다.
아래는 지끔까지의 지네릭스 사용법을 통한 간단한 예제이다.
class Box<T> {
ArrayList<T> list = new ArrayList<T>();
void add(T item) {
list.add(item);
}
T get(int i) {
return list.get(i);
}
ArrayList<T> getList() {
return list;
}
int size() {
return list.size();
}
public String toString() {
return list.toString();
}
}
public String generics() {
GenericsParent<String> pList = new GenericsParent<>();
pList.add("Mother");
pList.add("Father");
return "Parent Size: " + pList.size() + " | Parent List: " + pList.get(0) + " " + pList.get(1);
}
출력결과
제한된 지네릭 클래스
여태까지 지네릭의 사용법은 마치 Ojbect 타입으로 선언하여 사용하는 방식이였다. 이제 정리할 내용은 모든 타입이 아닌 특정한 타입의 종류만으로 제한할 수 있는 방법이 있다. 그 방법은 extends
를 활용한 방법이다.
class Family<T extends Parent> { // Parent의 자손만 타입으로 지정가능
List<T> list = new ArrayList<>();
}
// 사용시
Family<Father> f = new Family<Father>(); // OK
Family<Mother> m = new Family<Mother>(); // OK
Fmaily<Parent> p = new Family<Parent>(); // OK
p.add(new Father()); // OK
p.add(new Mother()); // OK
다형성에서 조상타입의 참조변수로 자손타입의 객체를 가리킬 수 있는 것처럼, 매개변수화된 타입의 자손 타입도 가능한 것이다. 만일 클래스가 아니라 인터페이스를 구현해야 한다는 제약이 필요하더라도 extends
를 사용한다 implements
를 사용하면 안되는 것을 명심해야한다. 만일 위의 상황에서 인터페이스를 구현해야 한다면 class Family<T extends Parent & interfaceName>
으로 & 기호를 활용하여 정의하면 된다.
와일드 카드
와일드 카드는 기호 ‘?’로 푠현하는데, 와일드 카드는 어떠한 타입도 될 수 있다. ‘?’만으로는 Object타입과 다른게 없으므로, 다음과 같이 extends
와 super
로 상한과 하한을 제한할 수 있디.
<? extends T> 와일드 카드의 상한 제한. T와 그 자손들만 가능.
<? super T> 와일드 카드의 하한 제한. T와 그 조상들만 가능.
<?> 제한 없음. 모든 타입이 가능. <? extends Object>와 동일
static juice makeJuice(FruitBox<? extends Fruit> box) {
String tmp = "";
for(Fruit f : box.getList()) tmp += f + " ";
return new juice(tmp);
}
지네릭 메서드
메서드의 선언부에 지네릭 타입이 선언된 메서드를 지네릭 메서드라 한다. Collections.sort()가 지네릭 메서드이며, 지네릭 타입의 선언 위치는 반환 타입의 바로 앞이다.
class Collections<T> {
static <T> void sort(List<T> list, Comparator<? super T> c)
}
지네릭 클래스에 정의된 타입 매개변수와 지네릭 메서드에 정의된 타입 매개변수는 전혀 별개의 것이다. 같은 타입 문자를 사용한다 해도 같은 것이 아니라는 것에 주의를 해야한다. 예를 들어 위의 Collections<T>
와 static<T>
에서의 T는 서로가 아무런 상관이 없는 타입이다.
Leave a comment