Skip to main content

Dart의 제너릭

Dart2.19.6About 1 min

원문 : https://dart.dev/language/genericsopen in new window

기본 배열타입인 Listopen in new window에 대한 API 문서를 보다보면, 실제 List<E>와 같은 타입을 볼 수 있습니다.
<...> 표기법은 리스트의 제너릭(또는 매개변수화된) 타입(형식 타입 매개변수를 가지는 타입)을 표기합니다.
관례에 따르면open in new window, 대부분 타입 변수는 단일문자 이름(E 및 T, S, K, V 같은)을 가집니다.

왜 제너릭을 사용하는가?

제너릭은 자주 타입보장이 필요할 때 사용되지만 코드가 실행가능하도록 많은 이점을 가지고 있습니다.

  • 제너릭타입을 적절하게 지정하면 코드가 더 잘 생성됩니다.
  • 제너릭을 사용하여 코드중복을 줄일 수 있습니다.

목록에 문자열만 포함하려고 하는 경우 List<String>(문자열 목록으로 부름)으로 선언하면 됩니다.
이것으로 당신과 같이 일하는 프로그래머, 도구 등이 목록에 문자열이 아닌 것이 할당되면 잘못됨을 감지할 수 있습니다.
아래 예제가 있습니다.

// 정적분석 : 에러/경고
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);
}

나중에, 이 인터페이스의 숫자용 버전이 필요함을 발견하고....
아이디어를 생각해봅시다.

제너릭 타입은 이 모든 인터페이스의 생성에 대한 문제를 해결해줍니다.
기존과 다르게 타입 매개변수를 받는 단일 인터페이스를 생성합니다.

abstract class Cache<T> {
  T getByKey(String key);
  void setByKey(String key, T value);
}

코드에서 T는 들어오는 타입입니다.
개발자가 나중에 정의할 타입의 자리표시자로 생각하시면 됩니다.

콜렉션 리터럴에서 사용하기

리스트, 세트, 맵 리터럴은 매개변수화 되어있습니다.
매개변수화된 리터럴은 기존에 사용하는 리터럴과 유사하지만 중괄호를 열기전 <type>(리스트, 세트) 또는 <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>();

제너릭 콜렉션과 가지고있는 타입

Dart의 제너릭 타입은 _구체화_되어있습니다.
다시말해 런타입동안 타입 정보를 가지고 다니게 됩니다.
예로 들어, 콜렉션의 타입에 대한 테스트가 가능합니다.

var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true

Note

대조적으로 자바의 제너릭은 _지워 없앰_을 사용합니다.
다시 말해, 제너릭 타입 매개변수가 런타임에 삭제됩니다.
자바에서 객체가 List인지 테스트는 가능하지만 List<String>인지는 테스트가 불가능합니다.

매개변수화 타입 제한하기

제너럭 타입을 구현할 때 인자로 제공되는 타입에 대한 제한을 하고 특정 타입의 하위 타입만 가능하도록 하고 싶을 수 있습니다.
이때 extends 키워드를 사용하여 할 수 있습니다.

일반적인 사용사례는 Object의 하위 유형으로 만들어진 null이 아닌 타입(기본값 Object?open in new window 대신)으로 확정하는 것 입니다.

class Foo<T extends Object> {
  // T에 어느 타입이든 가능하지만 null이 아니어야 합니다.
}

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>의 인스턴스

SomeBaseClass가 아닌 것으로 타입을 지정하면 에러가 발생합니다.

// 정적 분석 : 에러/경고
var foo = Foo<Object>();

제너릭 함수 사용하기

메소드와 함수에서도 타입 인자가 허용됩니다.

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;
}

여기서 first<T>의 제너릭 타입 매개변수는 다양한 위치에서 타입 인자 T를 사용할 수 있습니다.

  • 함수의 반환 타입 (T)
  • 인자의 타입 (List<T>)
  • 로컬 변수의 타입 (T map)