티스토리 뷰
제네릭
리스트(List)에 대한 API 문서를 보면 실제로는 타입이 List<E>으로 되어 있다. <E> 표기법은 List가 제네릭 타입임을 나타낸다. 관례상 대부분의 타입 변수는 E, T, S, K, V와 같은 단일 문자를 사용한다.
왜 제네릭을 사용할까?
다트는 타입 안전성 언어이므로 타입을 지정해야 한다. 제니릭은 타입을 코드 작성 시 결정할 수 있으므로 반복 코드를 줄일 수 있고, 타입을 지정해서 다른 타입을 할당하는 실수를 막을 수 있다.
List에 문자열만 포함하려면 List<String>으로 선언할 수 있다. 이렇게 하면 List에 문자열이 아닌 타입을 할당하는 실수를 감지할 수 있다.
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);
}
하지만 나중에 정수(int) 버전이 필요하다면 또 다른 인터페이스를 만들어야 할 것이다.
제네릭 타입은 이러한 모든 타입에 대한 인터페이스를 작성하는 수고를 덜어준다. 다음처럼 제네릭을 사용하면 한 개의 인터페이스만 만들면 된다.
abstract class Cache<T> {
T getByKey(String key);
void setByKey(String key, T value);
}
컬렉션 리터널 사용하기
List, Set, Map을 매개변수화(parameterized)할 수 있다. 매개변수화된 리터널은 <타입> 또는 <키 타입, 값 타입>을 추가한다는 점을 제외하고는 다른 리터널과 동일하다.
다음은 매개변수화한 타입 리터널을 사용 예제이다.
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);
다음 코드는 int 키와 View 타입 값을 가지는 맵을 만든다.
var views = Map<int, View>();
제네릭 컬렉션과 그들이 포함하는 타입
다트의 제네릭 타입은 refied이다. 즉, 실행 시간에 그들의 타입 정보를 가지고 있다.
예를 들어, 다음과 같이 컬렉션 타입을 테스트할 수 있다.
var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
매개변수화된 타입 제한
제네릭 타입을 구현할 때, 인수로 제공할 수 있는 타입을 제한하여 인수가 특정 타입의 하위 타입으로 만들 수 있다. 타입 제한은 extends 키워드를 사용한다.
일반적인 사용 예로는 타입을 Object의 하위 타입으로 지정하여 널을 사용할 수 없도록 하는 것이다.
class Foo<T extends Object> {
// Any type provided to Foo for T must be non-nullable.
}
다음은 SomeBaseClass로 제한하는 예제 코드이다.
class Foo<T extends SomeBaseClass> {
// Implementation goes here...
String toString() => "Instance of 'Foo<$T>'";
}
class SomeBaseClass {...}
class Extender extends SomeBaseClass {...}
SomeBaseClass 또는 SomeBaseClass의 하위 타입을 제네릭 인수로 사용하면 된다.
var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();
제네릭 인수를 지정하지 않아도 된다. 그러면 자동으로 SomeBaseClass가 제네릭 인수로 사용된다.
var foo = Foo();
print(foo); // Instance of 'Foo<SomeBaseClass>'
SomeBaseClass와 그 하위 타입이 아닌 타입을 지정하면 오류가 발생한다.
var foo = Foo<Object>(); // Error
제네릭 메서드 사용
메서드와 함수도 타입 매개변수를 사용할 수 있다.
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;
}
여러 위치에서 타입 매개변수를 사용하면 사용할 수 있다.
- 함수의 반환 타입
- 인수 타입
- 로컬 변수 타입
'다트 공식 문서 번역' 카테고리의 다른 글
다트] 다트 타입 시스템 (0) | 2024.07.25 |
---|---|
다트] 타입 별칭, 인라인 함수 타입 (0) | 2024.07.24 |
다트] 컬렉션 - 리스트(list), 셋(set), 맵(map) (0) | 2024.07.22 |
다트] 레코드 - 구문, 필드, 타입, 동등성, 집합 반환 (0) | 2024.07.22 |
다트] 함수, 화살표 구문, 매개변수, 익명 함수, 렉시컬 스코프, 렉시컬 클로저, tear-off, 반환 값, 제너레이터, 외부 함수 (0) | 2024.07.20 |