티스토리 뷰

다트 공식 문서 번역

다트] 제네릭

철철박사 2024. 7. 24. 10:51
반응형

제네릭

리스트(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;
}

 

 

여러 위치에서 타입 매개변수를 사용하면 사용할 수 있다.

  • 함수의 반환 타입
  • 인수 타입
  • 로컬 변수 타입
반응형
댓글
공지사항