跳转到正文
YGJoon 技术博客

Dart 泛型详解

发布于:
·1.4k 字 · 约 4 分钟·作者:
从为什么需要泛型讲到单/多类型参数的泛型类、带约束的泛型方法与泛型接口,写出类型安全又可复用的代码。
Dart 泛型详解文章封面
目录

泛型基础

为什么使用泛型

// 不使用泛型
Object getFirst(List list) {
  return list[0];
}

// 使用泛型
T getFirst<T>(List<T> list) {
  return list[0];
}

基本语法

// 泛型类
class Box<T> {
  T value;
  Box(this.value);
}

// 泛型方法
T transform<T>(T value) {
  return value;
}

泛型类

单类型参数

class Stack<T> {
  final List<T> _items = [];
  
  void push(T item) => _items.add(item);
  T pop() => _items.removeLast();
  bool get isEmpty => _items.isEmpty;
}

// 使用示例
var numberStack = Stack<int>();
var stringStack = Stack<String>();

多类型参数

class Pair<K, V> {
  final K first;
  final V second;
  
  Pair(this.first, this.second);
}

// 使用示例
var pair = Pair<String, int>('age', 25);

泛型方法

基本用法

T findFirst<T>(List<T> items, bool Function(T) test) {
  for (var item in items) {
    if (test(item)) return item;
  }
  throw StateError('No element');
}

// 使用示例
var numbers = [1, 2, 3, 4, 5];
var firstEven = findFirst<int>(numbers, (n) => n.isEven);

泛型方法约束

// 注意:不能写成 T extends Comparable<T>——int 实现的是 Comparable<num> 而不是
// Comparable<int>,那样 max<int> 会编译不过。用 Comparable<dynamic> 放宽上界。
T max<T extends Comparable<dynamic>>(T a, T b) {
  return a.compareTo(b) > 0 ? a : b;
}

// 使用示例
void main() {
  print(max<int>(10, 20)); // 20
  print(max<String>('hello', 'world')); // world
}

泛型接口

abstract class Cache<T> {
  T getItem(String key);
  void setItem(String key, T value);
  void removeItem(String key);
}

class MemoryCache<T> implements Cache<T> {
  final Map<String, T> _cache = {};
  
  @override
  T getItem(String key) => _cache[key]!;
  
  @override
  void setItem(String key, T value) => _cache[key] = value;
  
  @override
  void removeItem(String key) => _cache.remove(key);
}

完整示例

// 通用数据结构示例
class DataContainer<T> {
  final List<T> _items = [];
  final void Function(T)? _onAdd;
  final void Function(T)? _onRemove;
  
  DataContainer({
    void Function(T)? onAdd,
    void Function(T)? onRemove,
  })  : _onAdd = onAdd,
        _onRemove = onRemove;
  
  void add(T item) {
    _items.add(item);
    _onAdd?.call(item);
  }
  
  bool remove(T item) {
    final removed = _items.remove(item);
    if (removed) {
      _onRemove?.call(item);
    }
    return removed;
  }
  
  List<R> map<R>(R Function(T) transform) {
    return _items.map(transform).toList();
  }
  
  List<T> where(bool Function(T) test) {
    return _items.where(test).toList();
  }
  
  T? find(bool Function(T) test) {
    for (var item in _items) {
      if (test(item)) return item;
    }
    return null;
  }
}

// 使用示例
void main() {
  // 创建整数容器
  var numbers = DataContainer<int>(
    onAdd: (number) => print('Added: $number'),
    onRemove: (number) => print('Removed: $number'),
  );
  
  numbers.add(1);
  numbers.add(2);
  numbers.add(3);
  
  // 转换操作
  var doubled = numbers.map((n) => n * 2);
  print('Doubled: $doubled');
  
  // 过滤操作
  var evenNumbers = numbers.where((n) => n.isEven);
  print('Even numbers: $evenNumbers');
  
  // 查找操作
  var firstEven = numbers.find((n) => n.isEven);
  print('First even number: $firstEven');
  
  // 创建字符串容器
  var words = DataContainer<String>(
    onAdd: (word) => print('Added word: $word'),
    onRemove: (word) => print('Removed word: $word'),
  );
  
  words.add('hello');
  words.add('world');
  
  // 字符串操作
  var upperWords = words.map((w) => w.toUpperCase());
  print('Upper words: $upperWords');
  
  var longWords = words.where((w) => w.length > 4);
  print('Long words: $longWords');
}

泛型约束

extends 关键字

class NumberBox<T extends num> {
  T value;
  
  NumberBox(this.value);
  
  void increment() {
    if (value is int) {
      value = (value + 1) as T;
    } else if (value is double) {
      value = (value + 1.0) as T;
    }
  }
}

单上界:Dart 不支持多重约束

// 注意:Dart 的泛型一次只能写一个 extends 上界,
// 不支持 Java 式的 `T extends A & B` 交叉约束写法,那样会直接语法报错。
// 需要多个能力时,把共用的那个抽成一个接口,或收紧到最具体的一个上界:
class JsonBox<T extends Comparable<T>> {
  T value;

  JsonBox(this.value);

  String toJson() => '{"value": "$value"}';

  bool isGreaterThan(T other) => value.compareTo(other) > 0;
}

注意事项

  1. Dart 的泛型在运行时是具化的(reified),类型参数不会被擦除:[1,2] is List<int> 为 true,is List<String> 为 false,运行时拿得到真实类型
  2. 注意类型安全和类型检查
  3. 合理使用泛型约束
  4. 避免过度使用泛型
  5. 注意泛型和异步操作的结合

最佳实践

  1. 优先使用具体类型而不是 dynamic
  2. 合理使用泛型约束
  3. 为泛型参数使用有意义的名称
  4. 适当使用类型推断
  5. 注意泛型的性能影响

实战案例:我的代码里泛型无处不在,但只用了一种

翻我的两个项目,泛型出现的地方:

// ygking_json_to_dart_model
void _generateClass(Map<String, dynamic> map, ...)   // JSON 对象
final dynamic jsonData = json.decode(jsonStr);        // 注意:这里故意不用泛型

// flutter_brightness
StreamController<String>? _controller;                // 串口数据流
Stream<String> get dataStream => ...                  // 对外暴露的类型

有意思的是两种相反的选择:

  1. StreamController<String> 必须写死泛型。串口吐出来的是文本,我在 SerialService 内部就把字节转成了 String,流的类型定死为 String。这样 UI 层 listen 到的 data 直接就是 String,不用到处 as。如果写成 StreamController<dynamic>,每个订阅者都要自己判断类型——类型信息在源头就丢了,后面全是坑。

  2. json.decode 的返回值我故意不加泛型。json.decode 返回 dynamic,我直接 final dynamic jsonData = ... 接住,然后用 is 判断(jsonData is Map<String, dynamic>)。为什么不在 decode 时就定死?因为输入不可信——用户粘进来的可能是对象、可能是数组、可能是个纯字符串,decode 之前我根本不知道。泛型的意义是”给确定的东西加约束”,输入都不确定时,硬加泛型就是自欺欺人。

所以我的泛型观很简单:输出(我生产的数据)泛型写死,输入(别人给的数据)保持 dynamic + is 判断。Map<String, dynamic> 这个组合就是这么来的:key 一定是 String(JSON 规范定的),value 不确定,所以是 dynamic。一句话:泛型不是”越具体越好”,而是”在确定的地方具体,在不确定的地方诚实”。

总结

泛型就干两件事:把”类型”变成参数传进去,让编译器替你检查。记住三个坑就行:约束上界别写错(int 是 Comparable 不是 Comparable)、Dart 没有多重约束、运行时类型没被擦除可以 is。剩下的都是熟练度问题。


分享这篇文章:


相关文章

上一篇
Dart 库和可见性详解
下一篇
Dart Mixin 和扩展详解