19. 자바 Generics (2)
지난 포스트에서는 자바의 컬렉션 프레임워크가 구현한 지네릭 자료구조를 사용하는 방법에 대해 알아보았다. 이번 포스트에서는 Generics 클래스를 만드는 법에 대해서 알아보도록 하겠다.
이전 포스트
자바 Generics 클래스
아주 간단한 FixedList라는 자료구조를 만들어 보자. 이 자료구조는 생성시 리스트의 크기를 받고 딱 그만큼의 자료만 저장한다. 더 이상 넣으려고 하면 에러가 나는 자료구조이다. 이전 포스트에서 말했듯이 Generic을 사용하지 않으면 클래스마다 FixedList자료구조를 따로따로 만들어 주어야 한다. (e.g, IntegerFixedList, StringFixedList..) Generic을 사용하면 Type에 상관 없이 이 자료구조를 사용할 수 있도록 할 수 있다.
public class FixedList<T> {
private T[] list;
private int size;
public FixedList(int capacity) {
size = 0;
list = (T[]) new Object[capacity];
}
}
Generic클래스는 클래스 다음에 <T>를 붙여준다. T는 Type의 약자인데 T이든 D이든 Type이든 아니면 단어이든 상관 없다. 중요한건 꺽쇠 괄호 안에 들어 있는 녀석을 마치 이 클래스가 가지고 있을 오브젝트의 자료형/클래스라고 여긴다는 것이다. 클래스이름<T>는 '이 클래스는 타입이 아직 결정되지 않은 변수(list)를 사용한다. 그리고 그 변수의 타입을 T라고 가정할 것이다'라는 뜻이다. 우리는 지금 이 클래스를 작성하는 시점에서 어떤 오브젝트를 배열에 담고 있을 건지 모른다. 이 오브젝트 배열(list)의 형이 Integer일 수도 있고, String일 수도 있고, 아니면 우리가 만든 다른 클래스 형일 수도 있다. 이렇게 형을 모르기 때문에 T처럼 일단 아무 이름이나 갖다 붙인 것이다. 마치 변수 이름짓듯이 말이다. 이 클래스는 T라는 형의 오브젝트를 담고 있을 것이다. T는 이 클래스를 사용하는 사람이 결정한다.
public class Main {
public static void main(String[] args) {
FixedList<Integer> integerFixedList = new FixedList<>(5);
FixedList<String> stringFixedList = new FixedList<>(5);
FixedList<Double> doubleFixedList = new FixedList<>(5);
}
}
다시말해 위처럼 T안에 들어갈 형을 사용하는 사람이 넣어준다. Integer, String, Double가 T를 대체할 것이다. 이렇게 하면 사용자가 형을 정할 수 있고, FixedList클래스 자체는 사용자가 원하는 형에 구애받지 않는다. 그러면서도 클래스 사용자에게 본인이 정한 자료형 T를 일관적으로 사용하도록 강요할 수 있다.
FixedList를 더 구현해 보자.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class FixedList<T> {
private T[] list;
private int size;
public FixedList(int capacity) {
size = 0;
list = (T[]) new Object[capacity];
}
public void add(T data) {
if (data == null) {
throw new NullPointerException();
}
list[size++] = data;
}
public List<T> asList() {
if (list == null) return new ArrayList<>();
return Arrays.asList(list);
}
}
add(T data)를 보자. 이 클래스의 리스트에 오브젝트를 담을 것이다. 그렇다면 해당 오브젝트의 형인 data를 받아서 넣으면 된다. 보통 메서드는 메서드이름(매개변수클래스/자료형 매개변수이름)으로 쓴다. 여기서는 매개변수가 무엇인가? 바로 이후에 사용자가 결정하는 T이다. 이 클래스는 오브젝트 생성 선언시 T가 결정된다.
FixedList<Integer> integerFixedList = new FixedList<>(5);
따라서 이 클래스는 클래스의 전반에 걸쳐 사용자가 올바른 클래스/자료형 오브젝트를 넘기도록 해야한다. 그게 바로 T의 목적이다. 아무 클래스/자료형이나 사용할 수 있도록 하되, 한번 정했으면 그것만 사용하도록 강요 할 수 있다.
public class Main {
public static void main(String[] args) {
FixedList<Integer> integerFixedList = new FixedList<>(5);
integerFixedList.add(3);
integerFixedList.add(6);
System.out.println(integerFixedList.asList().toString());
}
} // 실행 결과 [3, 6, null, null, null]
add에 다른 자료형을 넣으려고 하면 에러가 날 것이다. T = Integer인데 Integer가 아닌 다른 형의 오브젝트가 넘어가기 때문이다.
StringFixedList로도 실행 해 보자.
public class Main {
public static void main(String[] args) {
FixedList<String> stringFixedList = new FixedList<>(5);
stringFixedList.add("Hello");
stringFixedList.add("World");
System.out.println(stringFixedList.asList().toString());
}
} // 실행 결과 [Hello, World, null, null, null]
끝
이렇게 사용하는 사람이 형을 정할 수 있고 클래스를 작성하는 당시에 정확히 어떤 타입인지 결정하지 않아도 되므로 Generics는 자료형(Type)을 추상화한다고 한다. Generics는 프레임워크나 라이브러리에 많이 사용되므로 알아두면 매우 좋다.