사용하다보니 객체가 아닌 다른 타입용 인터페이스가 필요해지면 아래와 같이 타입에 맞추어 다른 인터페이스를 생성해야한다.
abstract class StringCache {
String getByKey(String key);
void setByKey(String key, String value);
}
abstract class NumberCache {
Number getByKey(String key);
void setByKey(String key, Number value);
}
...
이럴 때 제네릭은 이 모든 인터페이스 생성에 대한 문제를 해결해준다.
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
Using collection literals
리스트, 세트, 맵 리터럴은 매개변수화 되어있다. 무슨 뜻인지 잘 모르겠다. 예시로 알아보자.
void main() {
/// 타입을 지정하지 않고 선언을 하면
/// 런타임 시점에 타입추론을 통해서
/// Object타입의 배열로 정의된다.
final coll1 = ["hi", 1, 2, true]; // JSArray<Object>
/// 아래와 같이 선언시, 문자열 배열이기 때문에
/// 에러가 발생한다.
/// The element type 'int' can't be assigned to the list type 'String'.
final coll2 = <String>["hi","hello", 1];
final coll3 = <String>["say", "yes"]; // JSArray<String>
final coll4 = <String>{'say', 'yes'}; // _LinkedHashSet<String>
final coll5 = <String, String>{ // JsLinkedHashMap<String, String>
'name': 'womillism',
'hi': 'hello',
};
}
다음과 같이 생성자에 매개변수화된 타입을 사용할 수도 있다.
var nameSet = Set<String>.from(names);
var views = Map<int, View>();
Restricting the parameterized type
제네릭 타입을 구현할 때 인자로 제공되는 타입에 대한 제한을 하고 특정 타입의 하위 타입만 가능하도록 하고 싶을 수 있다.
이때 extends 키워드를 사용하여 할 수 있다.
class Foo<T extends Object> {
// T에 어느 타입이든 가능하지만 null이 아니어야 합니다.
}
class Foo<T extends SomeBaseClass> {
// 구현은 여기서...
String toString() => "Instance of 'Foo<$T>'";
}
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
메소드와 함수에서도 마찬가지로 제네릭을 사용할 수 있다.
T first<T>(List<T> ts) {
// Do some initial work or error checking, then...
T tmp = ts[0];
// Do some additional checking or processing...
return tmp;
}