19. 자바 Generics (1)
이번 포스트에서는 자바에서 제공하는 자료형(Type) 추상화 기법인 Generic에 대해 알아보도록 한다.
이전 포스트
목표
- Generics가 필요한 이유
- Generics를 이용한 컬렉션 프레임워크 예
- Generics는 언제나 사용할 수 있을까?
Generics이 필요한 이유
Generics은 타입에 대한 추상화라고 했다. 이는 어떤 오브젝트를 생성할 때 내가 원하는 자료형으로 오브젝트를 생성할 수 있다는 뜻이다. 아래의 예는 Generics이 없는 세상의 이야기이다.
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("Hello World");
String myString = list.get(0);
System.out.println(myString);
}
}
위와 같은 코드가 있다고 치자. ArrayList를 만들어 이 리스트에 String을 담고 싶다. 그리고 나중에 넣었던 문자열을 가져와 모니터에 출력하고 싶다. 그래서 list.get(0)해서 문자열을 가져와 출력하려 했다. 하지만 실행하려하면 어떤 문제가 발생하는가?
Error:(9, 35) java: incompatible types: java.lang.Object cannot be converted to java.lang.String
위와 같은 에러가 나지 않는가? 위의 ArrayList는 어쩐 자료형을 담고있을까? 바로 Object형의 자료형을 담고있다. Object는 모든 클래스의 수퍼클래스라고 했다. 따라서 모든 자료형을 커버할 수 있는 자료구조를 지원하기 위해 모든 클래스의 최상위 클래스인 Object 오브젝트를 담는 리스트를 만드는 것이다. 이렇게 해서 우리는 Object클래스를 상속받는 String클래스의 오브젝트("Hello World")를 리스트에 추가할 수 있었다. 하지만 리스트에서 빼 올 때는 이야기가 다르다. 상속에서도 했듯이 서브클래스는 수퍼클래스로 암묵적 캐스팅(implicit casting)이 가능하다. 이런 캐스팅을 업 캐스팅(Up casting)이라고도 한다.
public static void main(String[] args) {
Object o = "Hello World";
String s = new Object();
}
}
즉 자바는 String은 Object형이라는 것을 알 수 있지만 Object형이 String형이라는 건은 알지 못한다.
public static void main(String[] args) {
Object o = "Hello World";
String s = (String) new Object();
}
따라서 오브젝트에 명시적 캐스팅(explicit casting)을 해 주어 이 오브젝트는 String형의 오브젝트이라 라는 것을 알려주어야 한다. 이런 캐스팅을 다운 캐스팅(Down casting)이라고도 부른다.
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("Hello World");
String myString = (String) list.get(0); //explicit type casting
System.out.println(myString);
}
다운캐스팅은 지양하는것이 보통이다. 코드를 복잡하게 하고 협업을 힘들게한다. 예를들어서 100년뒤에 다른 사람이 이 코드를 수정 할 때,이 사람은 list가 String형만 받아야 된다는 사실을 모르고 아무 오브젝트나 마구 집어넣을 수도 있다.
그렇다면 자료형이 있는 리스트를 만들면 될 것 아닌가? IntegerList, StringList, DoubleList등등 말이다. 이 또한 문제가 있다. 문제가 뭔지 생각해 보아라.
문제는 바로 내가 만드는 모든 자료형에 대하여 리스트를 구현해 줘야 한다는 것이다. 만약 내가 Student라는 클래스를 만들었다면 StudentList라는 리스트 자료형을 만들고 구현해야 한다. 내가 Teacher라는 클래스를 만들었다면 TeacherList라는 리스트 자료형을 만들고 구현해야 한다.
하지만 우리처럼 게으른 개발자들은 클래스 하나 만들 때 마다 리스트를 구현하는게 싫었고, 자바는 이런 우리를 돕기 위해 자바 1.5부터 Generics을 지원했다. 이제 Generics이 위의 문제를 어떻게 해결하는지 알아보자.
Generics를 이용한 컬렉션 프레임워크 사용 예
public static void main(String[] args) {
ArrayList<String> list = new ArrayList();
list.add("Hello World");
String myString = list.get(0);
System.out.println(myString);
}
Generics은 위처럼 <> 꺽쇠 괄호 안에 이 오브젝트가 담고 있을 또는 사용할 자료형을 지정해 주는 것이다. 하나의 리스트 클래스가 여러 자료형을 받을 수 있도록 하고, 그 자료형을 컴파일 시간에 명시적으로 지정할 수 있게 해 혼란을 없애고 코드를 더 간단하게 만들었다.
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList();
list.add("Hello World");
String myString = list.get(0);
System.out.println(myString);
}
꺽쇠 괄호의 안을 Integer로 바꿔보자. 아래의 코드에서 전부 에러가 날 것이다. 이 리스트는 Integer 오브젝트를 받는 리스트인데 String을 추가하고 빼려고 해서 그렇다.
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList();
list.add(120);
int myInt = list.get(0);
System.out.println(myInt);
}
이렇게 <> 꺽쇠괄호 안에 자료형을 넣어주면 이후부터 컴파일러가 타입 체크를 전부 해준다. 따라서 컴파일 시간에 자료형을 검사할 수 있고, 타입 캐스팅으로 인한 런타임 에러를 방지할 수 있다.
Generic은 언제나 사용할 수 있을까?
Generics은 어떤 클래스가 Generics을 이용해 구현되어 있어야 사용할 수 있다. ArrayList, List, Queue등 자바에서 제공하는 자료구조들은 기본적으로 Collection이라는 인터페이스를 구현하고 있고, Collection인터페이스가 Generics을 지원한다.
public interface Collection<E> extends Iterable<E> {
// Query Operations
/**
* Returns the number of elements in this collection. If this collection
* contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
* <tt>Integer.MAX_VALUE</tt>.
*
* @return the number of elements in this collection
*/
int size(); //... 생략 }
여기서 <E>로 되어있는게 바로 Generics를 지원한다는 뜻이다. 그렇다면 Generics을 지원하는 클래스는 어떻게 만들까? Generics를 지원하는 클래스를 만드는 법에 대해서는 다음 포스트에서 소개하도록 하겠다.
끝
여러분이 범용으로 사용 될 프로그램을 만드는게 아니라면 (예를들어 아파치 프로젝트나 프레임워크 규모의 프로젝트) Generics를 구현하는 클래스를 만들 일은 별로 없을지 모른다. 하지만 자바를 현업에서 사용한다면 Generics를 구현하는 클래스를 사용하게 될 일을 아주아주 많다. 따라서 Generics에 대해 확실히 알고 넘어가는 것이 개발 여행에 큰 도움이 될 것이라 생각한다.