기본 배열 타입의 API 문서를 보면, List 타입이
List<E>
로 표기되어 있는 걸 볼 수 있습니다. <…> 표시는 List를 형식 타입 매개변수를 가지는 제네릭 (또는 매개변수화된) 타입으로 지정합니다. 관례상 대부분의 타입 변수는 E, T, S, K, V 같은 single-letter 이름을 가집니다.
제네릭은 보통 타입 세이프티 때문에 사용하지만, 사실 더 많은 기능을 수행한다.
리스트가 문자열 값만 가지게 하고 싶다면, List<String>
으로 리스트 선언하면 된다. 이렇게 하면 협업 할 경우 문자열 이외의 값은 리스트에 추가 될 수 없음을 바로 알 수 있다.
// X static analysis: error/warning
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Error
제네릭을 사용하는 또 다른 이유는 코드 중복을 줄이기 위함이다. 제네릭은 정적인 분석의 이점을 챙기면서, 많은 타입들이 단일 인터페이스와 구현을 공유할 수 있게 한다.
abstract class ObjectCache {
Object getByKey(String key);
void setByKey(String key, Object value);
}
//문자열 버전
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
제네릭 타입은 위처럼 모든 인터페이스를 생성해야하는 문제를 해결해준다. 타입 매개변수를 가지는 하나의 단일 인터페이스만을 구현하면 된다.
//T는 대체 타입으로 개발자가 추후에 타입을 마음대로 지정할 수 있게 해주는 플레이스 홀더입니다.
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
List
,set
그리고 map
리터럴은 매개변수화 될 수 있다. 매개변수화된 리터럴은 list
, set
에 type
또는 map
에 <keyType, valueType>
를 시작 괄호에 추가하는 것만 빼면, 일반적으로 사용하는 리터럴과 비슷하게 생겼다.
var names = <String>['Seth', 'Kathy', 'Lars'];
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': 'Hints for web robots',
'humans.txt': 'We are people, not machines'
};
var nameSet = Set<String>.from(names);
//정수와 View타입의 값을 가지는 map
var views = Map<int, View>();
제네릭 타입은 구체화 되어있다. 제네릭 타입은 런타임에 타입들에 대한 정보를 가져온다.
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
제네릭 타입을 구현할 때, 인자로 제공되는 타입을 제한해서 인자가 특정타입의 서브타입이 되게해야할 경우가 발생한다. 이럴경우 extends
를 사용하면 된다.
Non-nullalbe인 것을 보장하기 위해, 디폴트인 Object?
대신 Object
의 서브타입으로 만들 때 자주 사용된다.
class Foo<T extends Object> {
// Foo에게 제공되는 T 타입은 반드시 non-nullable 입니다.
}
Object
이외의 타입들과 함께 extends
를 사용 할 수있다. 다음은 SomeBaseClass를 확장하는 예로, SomeBaseClass의 멤버들은 타입 T의 객체로 볼 수 있다.
class Foo<T extends SomeBaseClass> {
// 클래스 구현 ...
String toString() => "Instance of 'Foo<$T>'";
}
class Extender extends SomeBaseClass {...}
SomeBaseClass나 서브타입을 제네릭 인자로 사용하는 것도 가능하다.
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
제네릭 인자를 특정하지 않는 것도 가능하다.
var foo = Foo();
print(foo); // 'Foo<SomeBaseClass>'의 인스턴스
Non-SomeBaseClass 타입으로 특정하는 것은 에러를 발생시킨다.
var foo = Foo<Object>();
메서드와 함수에도 타입 인자를 사용할 수 있다.
T first<T>(List<T> ts) {
// 초기 작업 또는 에러 확인, 그리고 ...
T tmp = ts[0];
// 추가적인 확인 또는 프로세싱 ...
return tmp;
}
first ()에 있는 제네릭 타입 매개변수로 여러 위치에서 타입 인자인 T를 사용할 수 있다.