1. 제네릭
제네릭은 클래스나 함수에서 다양한 데이터 타입을 유연하게 사용할 수 있도록 해주는 기능입니다.
예제 코드를 통해 더 알아볼게요!
int getFirstInt(List<int> items) {
return items[0];
}
String getFirstString(List<String> items) {
return items[0];
}
void main() {
print(getFirstInt([1, 2, 3])); // 1
print(getFirstString(["apple", "banana"])); // apple
}
getFirstInt 함수와 getFirstString 함수는 모두 매개변수로 받은 리스트의 첫 번째 요소를 반환하는 함수예요.
다른 점이라고 한다면, getFirstInt에서는 int타입의 리스트를 매개변수로 받고요.
getFirstString은 String 타입의 리스트를 매개변수로 받아요.
이렇게 데이터의 타입이 다르기 때문에 리스트의 첫 번째 요소를 반환한다는 공통점이 있어도 코드를 중복으로 만들어야 해요.
하지만! 제네릭을 사용하면 더 깔끔한 코드를 작성할 수 있습니다!
T getFirst<T>(List<T> items) {
return items[0];
}
void main() {
print(getFirst<int>([1, 2, 3])); // 1
print(getFirst<String>(["apple", "banana"])); // apple
}
getFirstInt 함수와 getFirstString함수가 사라지고 대신 getFirst 함수가 보이네요!
그리고 처음 보는 T가 생겨났어요.
제네릭이 다양한 데이터 타입을 사용할 수 있다는 것 기억하시나요?
그러니까 int와 String에 한정되지 않고 다양한 데이터 타입을 반환하고, 매개변수를 받는 거죠!
이것을 정의하는 게 T입니다.
T는 제네릭 변수라고도 하는데요.
이제부터 제네릭의 구성요소에는 뭐가 있는지 알아볼게요.
2. 제네릭 타입 변수 (Generic Type Variable)
제네릭 변수는 특정 타입을 고정하지 않고, 사용할 때(함수를 호출할 때) 타입을 정할 수 있도록 하는 변수예요.
즉, 클래스나 함수에서 타입을 유연하게 설정하는 변수입니다.
위 예제에서는 T가 제네릭 타입 변수에 해당하겠죠?
변수 | 의미 | 사용 예제 |
T | Type (일반적인 데이터 타입) | class Box<T> |
K | Key (맵의 키) | class MyMap<K, V> { } |
V | Value (맵의 값) | class MyMap<K, V> { } |
E | Element (리스트나 컬렉션 요소) | class MyList<E> { } |
근데 T가 모든 타입을 정의할 수 있는데 왜 K, V, E를 따로 지정할까요? 🤔
물론 T를 사용하면 모든 타입을 표현할 수 있는 건 사실이에요!
그런데 굳이 K, V, E 같은 별도의 제네릭 변수를 쓰는 이유는 가독성과 역할을 명확히 하기 위해서입니다.
제네릭 변수는 단순히 타입을 일반화하는 것이 아니라 이 타입이 어떤 역할을 하는지도 표현해요.
K, V를 쓰면 그 변수가 각각 Key와 Value 역할을 한다는 것을 직관적으로 알 수 있어요.
만약, K, V가 아닌 T, Z 와 같은 변수를 사용한다면 무엇이 Key 역할을 하고, Value 역할을 하는지 알 수 없겠죠?
이 변수들은 제네릭 타입을 의미하는 약속된 네이밍이기 때문에 자주 사용되니까 기억해 두시면 좋아요.
3. 제네릭 함수
제네릭 함수는 제네릭을 이용하여 다양한 데이터 타입을 유연하게 처리할 수 있는 함수예요.
T 함수이름<T>(매개변수) {
return 값;
}
맨 앞의 T는 반환할 값을 의미해요.
T라는 건 다양한 데이터 타입을 반환할 수 있다는 뜻이겠죠?
<T>는 이 함수가 제네릭임을 선언하는 역할을 해요.
Dart 컴파일러는 T가 제네릭인지, 일반 타입인지 구별할 수 없어요.
그래서 <T>를 선언해야 Dart가 이 함수는 제네릭 함수임을 알 수 있어요.
T getFirst<T>(List<T> items) {
return items[0];
}
void main() {
print(getFirst<int>([1, 2, 3])); // 1
print(getFirst<String>(["apple", "banana"])); // apple
}
앞서 설명했던 코드예요.
리스트의 첫 번째 요소를 반환하는 함수이죠.
앞서 말했듯 T는 함수의 반환 타입으로 다양한 데이터 타입을 반환할 수 있어요.
그리고 List<T> items를 통해 T타입을 가진 List가 매개변수로 온다는 것을 알 수 있죠.
매개변수로 받은 items 리스트의 첫 번째 요소를 반환하는 것이 getFirst 제네릭 함수의 기능입니다.
4. 제네릭 클래스
제네릭 클래스는 다양한 타입을 처리할 수 있도록 타입을 일반화한 클래스입니다.
class 클래스이름<T> {
T 변수이름;
클래스이름(this.변수이름);
}
class 클래스이름<T> 는 제네릭 클래스를 선언하는 부분이에요.
이전의 제네릭 함수와 비슷하죠?
T 변수 이름; 은 클래스 내부에서 사용할 변수의 타입을 T로 설정한 것입니다.
클래스이름(this.변수이름); 은 생성자를 통해 T타입의 변수를 초기화한다는 뜻입니다.
클래스를 사용하기 위해서는 객체를 생성해야 하는데요.
객체를 생성할 때 값을 전달하면 해당 값이 변수에 저장되는 것입니다.
T는 알아봤으니 이제 K와 V에 대해 알아볼게요.
K와 V는 Map의 Key-Value 타입을 의미합니다.
class MyMap<K, V> {
Map<K, V> data = {};
void add(K key, V value) {
data[key] = value;
}
// 반환 타입이 V?라는 것은 nullable한 Value값을 반환한다는 뜻
// 매개변수로 K를 받아 해당 key에 해당하는 V 반환
// 굳이 nullable한 Value값을 반환하는 이유는
// 만약 key에 해당하는 value값이 없을 때 null을 반환하기 위함
V? getValue(K key) {
return data[key];
}
void showAll() {
print("Map contains: $data");
}
}
void main() {
// ageMap 이름을 가진 MyMap 객체 생성
// Key-String, Value-int로 선언
MyMap<String, int> ageMap = MyMap<String, int>();
// "Alice" : 25, "Bob" : 30 요소 추가
ageMap.add("Alice", 25);
ageMap.add("Bob", 30);
ageMap.showAll(); // Map contains: {Alice: 25, Bob: 30}
print(ageMap.getValue("Alice")); // 25
// idMap 이름을 가진 MyMap 객체 생성
// Key-int, Value-String으로 선언
MyMap<int, String> idMap = MyMap<int, String>();
// 101 : "Laptop", 102 : "Smartphone" 요소 추가
idMap.add(101, "Laptop");
idMap.add(102, "Smartphone");
idMap.showAll(); // Map contains: {101: Laptop, 102: Smartphone}
print(idMap.getValue(103)); // null
}
다음은 E 제네릭 변수를 가진 제네릭 함수를 알아볼게요.
E는 Element(요소)를 의미하는 제네릭 타입 변수예요.
class MyList<E> {
List<E> elements = [];
void add(E element) {
elements.add(element);
}
// E 타입(리스트의 요소)의 데이터 반환
E getFirst() {
return elements.first;
}
void showAll() {
print("List contains: $elements");
}
}
void main() {
// numberList라는 이름을 가진 MyList<int> 객체 생성
MyList<int> numberList = MyList<int>();
numberList.add(10);
numberList.add(20);
numberList.showAll(); // List contains: [10, 20]
print(numberList.getFirst()); // 10
// stringList라는 이름을 가진 MyList<String> 객체 생성
MyList<String> stringList = MyList<String>();
stringList.add("Dart");
stringList.add("Flutter");
stringList.showAll(); // List contains: [Dart, Flutter]
print(stringList.getFirst()); // Dart
}
5. 참고자료
https://dart.dev/language/generics
Generics
Learn about generic types in Dart.
dart.dev
Effective Dart: Design
Design consistent, usable libraries.
dart.dev
'Dart' 카테고리의 다른 글
[Dart] 함수형 프로그래밍 (Functional Programming) (0) | 2025.03.12 |
---|---|
[Dart] 함수 (Functions) (0) | 2025.03.11 |
[Dart] 열거형 (Enumerated types) (0) | 2025.03.11 |
[Dart] 컬렉션 (Collections) - List, Set, Map (0) | 2025.03.11 |