List, Set, Map 모두에서 <>를 사용했는데 그 부분에 타입 매개변수(Type parameter)를 지정한다. 이렇게 <>에 타입 매개변수를 선언하는 것을 매개변수화 타입(Parameterized type)을 정의한다고 한다.
타입 매개변수
타입 매개변수가 뭘까? 매개변수는 클래스 생성 시 생성자에서 사용하거나 함수 호출 시 인자 값을 전달하기 위해 사용한다. 타입 매개변수는 말 그대로 전달하는 것이 인자 값이 아니라 타입이라고 생각하면 된다.
List를 예로 들어보자.
abstract class List<E> implements EfficientLengthIterable<E> {
...
void add(E value);
...
}
다트의 List 클래스는 위와 같이 선언되어 있다. <E>가 존재하기 때문에 타입 매개변수를 사용할 수 있다는 것을 알 수 있다. (자바에서 <E>의 E는 형식 타입 매개변수(Formal type parameter)라고 한다.) 따라서 List 객체 생성 시 아래와 같이 사용할 수 있는 것이다.
List<String> colors = List();
colors.add('Red');
위 경우는 List에서 타입 매개변수를 <String>으로 지정한 것이다. (자바에서 <String>의 String을 실제 타입 매개변수(Actual type parameter)라고 한다.) 이것은 해당 List에 String 타입으로 넘겨준 것을 의미한다. 따라서 해당 List에는 String 타입이 값만 넣을 수 있다. 하지만 List 생성 시 타입 매개변수를 다른 타입으로 줄 수도 있다.
List<int> numbers = List();
numbers.add(1);
타입 매개변수를 int로 지정하면 int 타입 값만 넣을 수 있는 List가 생성된다. 이런 식으로 매개변수에 값을 넘겨주듯이 타입을 넘겨줄 수 있는 것이 제네릭의 핵심이다.
이렇게 제네릭을 사용해서 얻는 이점이 뭘까? 바로 코드를 중복으로 선언할 필요가 없는 것이다.
다시 List로 돌아와서 생각해보자.
String을 다루는 List와 int를 다루는 List를 각각 생성한다고 가정해보자. 그러면 백번 양보해서 오버로딩을 사용해도 add() 메서드만 2개가 필요하다. 다른 타입에 다른 메서드들까지 고려하면 그 수가 아주 많아진다. 그러나 제네릭을 사용하면 단 하나의 코드로 다양한 타입에 대한 커버가 가능한 것이다.
매개변수화 타입을 제한하기
제네릭을 사용할 때 매개변수화 타입을 제한할 수도 있다.
타입 매개변수에 extends를 사용해서 특정 클래스를 지정하면 된다. 그러면 해당 특정 클래스와 그 클래스의 자식 클래스가 실제 타입 매개변수가 될 수 있는 것이다.(다형성을 생각하면 된다.)
Line 13의 Manager 클래스는 타입 매개변수로 <T extends Person>을 선언했다. 그러면 Person 클래스와 그 자식 클래스가 실제 타입 매개변수가 될 수 있다. Line 30과 같이 실제 타입 매개변수 없이 그냥 Manager 클래스 생성도 가능하다.
Line 7의 Student 클래스는 Person의 자식 클래스이다. 따라서 Line 28과 같이 Manager 클래스의 실제 타입 매개변수가 될 수 있다.
Line 19의 Dog 클래스는 그냥 Dog 클래스이다. Person 클래스와 아무런 관계가 없다. 따라서 Dog 클래스는 Manager 클래스의 실제 타입 매개변수가 될 수 없다. 따라서 Line 32와 같이 사용하려고 하면 에러가 발생한다.
제네릭 메서드
제네릭은 클래스뿐만 아니라 메서드에도 사용할 수 있다. 메서드의 리턴 타입, 매개변수를 제네릭으로 지정할 수 있다.
Line 2 : getName() 메서드의 리턴 타입과 매개변수가 제네릭 타입으로 지정되었다.
Line 9 : 실제 타입 매개변수를 String으로 지정하고 인자로 'Kim'을 넘겨주고 다시 'Kim'을 리턴 받아서 출력하고 있다.