2 분 소요

이번 글은 Generics (<…>) 에 대해 이해해보도록 하겠습니다.

Map의 API 공식문서를 보면 Map<K, V>라고 되어있습니다. 이처럼 <…> 로 Map의 제네릭 타입을 표시해서 배열 안에 들어갈 변수의 타입을 지정할 수 있어요.

제네릭을 쓰는 이유는?

제네릭은 보통 배열 내 데이터들의 타입을 명확히 하기 위해 필요로 하는데, 또 다른 두 가지 이점이 있습니다.

  • 제네릭 유형을 잘 지정하면 더 나은 코드를 만들 수 있고
  • 코드 중복을 줄일 수 있습니다.

만약 문자열로만 이뤄진 List을 만들고 싶다면 List< String > 이렇게 선언합니다. 그럼 개발자 동료나 Dart Pad가 이 List에는 문자열말고 다른 타입의 데이터는 입력할 수 없음을 알 수 있습니다. 여기 예시가 있습니다.

var names = <String>[];
names.addAll(['ChangHwan', 'SeokHyun', 'YeSeul']);
names.add(3); // 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);
}

collection literals 사용하기

배열을 만들 때 제네릭을 지정함으로서 타입을 명확히 할 수 있습니다.

//List: <Type>[ ]
var names = <String>['ChangHwan', 'SeokHyun', 'YeSeul'];
//Set: <Type>{ } 
var uniqueNames = <String>{'ChangHwan', 'SeokHyun', 'YeSeul'};
//Map: <Type> { : }
var pages = <String, String>{
  'Member 1': 'ChangHwan',
  'Member 2': 'SeokHyun',
  'Member 3': 'YeSeul'
};

생성자와 제네릭 사용하기

생성자를 사용할 때도 < > 안에 데이터 Type을 지정함으로서 배열을 생성할 수 있어요.

예시1) Set(E).from(Iterable elements) 생성자

var memberName = Set<String>.from(names);

예시2) Map<K, V> ( ) 생성자

var Hack_up = Map<String, String>( );

배열이 담고 있는 타입이 무엇인지 알 수 있습니다.

var names = <String>[];
names.addAll(['ChangHwan', 'SeokHyun', 'YeSeul']);
print(names is List<String>); // true

공식 문서에 따르면 Java 언어에서는 어떤 객체가 리스트인지 아닌지의 여부만 테스트가 가능하고 List, 즉 배열이 담고 있는 데이터 타입이 무엇인지는 알 수 없다고 합니다.

파라미터(인수)의 타입을 제한하기

클래스 상속과 비슷한 개념인데요, 간혹 인수로 받을 데이터의 타입을 제한하고 싶을 때가 있습니다. 이 때 저희가 제네릭을 지정하게 되면 그 인수는 저희가 지정한 데이터 타입의 하위 형식이어야 하는데요, 이는 ‘extends’ 키워드로 구현할 수 있습니다.

흔히 사용하는 사례로는 어떤 type을 객체(Object)의 하위 유형으로 만들어 null을 사용하는 것을 제한할 수 있습니다.

class Foo<T extends Object> {
  // 클래스 Foo에는 null이 아닌 모든 유형의 타입을 쓸 수 있음. 
}

메서드나 함수에 Generic 사용하기

메서드(Methods)나 함수(Functions)의 인자(arguments)에도 제네릭 사용을 쓸 수 있습니다.

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

위 예제에서 여러 위치에서 Generic type을 사용할 수 있는 것을 볼 수 있습니다.

  • 함수의 결과값의 타입도 T
  • 인수의 타입이 ‘List'
  • 지역 변수 tmp의 타입도 T

여기서 T는 아직 데이터 타입이 정해지지 않은 것으로 나중에 여러분이 정의할 수 있습니다.

여기까지 제네릭에 대해서 파헤쳐봤는데요 !

확실히 하나 이상의 데이터를 다룰 때, 데이터 타입 별로도 잘 정리할 수 있고 클래스, 함수, 배열 등의 제네릭을 보면서 거기에 사용된 데이터 타입을 유추할 수 있다는 점에서도 유용한 것 같습니다.

부족한 글 읽어주셔서 감사합니다:)

댓글남기기