尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Dart之泛型

Dart之泛型 Dart之泛型一、前言什么是泛型呢第一次看到ListT、FutureT这样的写法可能会觉得尖括号里藏着一个神秘字母。其实它更像快递分拣线上的“包裹类型”同一条流水线可以处理不同包裹但每个包裹的类型要先说清楚。如果没有泛型一条通用代码要么只能复制很多份要么只能把所有值都当成dynamic最后把类型错误留到运行时。泛型做的事情就是让代码保持复用同时把类型信息保留下来。一句话理解泛型是把“类型”也参数化让同一份代码安全地服务于多种数据类型。二、为什么需要泛型2.1 没有泛型代码容易重复假设我们要从列表中取出第一个元素。如果只为整数写一个函数再为字符串写一个函数代码很快就会重复intfirstInt(Listintvalues){returnvalues.first;}StringfirstString(ListStringvalues){returnvalues.first;}两个函数的逻辑完全一样只有类型不同。泛型可以把变化的类型提取出来TfirstT(ListTvalues){returnvalues.first;}调用时T可以是int也可以是String函数逻辑只保留一份。2.2 没有泛型类型检查会变弱如果把参数写成dynamic代码看起来也能复用dynamicfirstValue(Listdynamicvalues){returnvalues.first;}但调用者拿到的是dynamic编译器无法尽早提醒错误finalvaluefirstValue([1,2,3]);print(value.toUpperCase());// 运行时才发现 int 没有这个方法泛型则会保留类型firstint()的结果是intfirstString()的结果是String。错误可以更早暴露。2.3 泛型解决什么问题问题没有泛型使用泛型代码复用复制多份类型不同的代码一份代码服务多种类型类型检查依赖运行时发现错误编译阶段尽早发现代码阅读dynamic难以判断真实类型T表示明确的类型关系三、泛型是什么3.1 从快递分拣线理解把一个通用容器想成快递分拣线。分拣线本身不关心包裹来自哪家店但它需要知道当前处理的是“书籍包裹”还是“食品包裹”。类型参数T就像贴在分拣线入口的标签今天这条线处理String明天也可以处理int。classBoxT{finalTvalue;Box(this.value);TgetValue()value;}BoxString表示装字符串的盒子Boxint表示装整数的盒子。盒子的结构相同但每个盒子里的值类型清楚可见。3.2 类型参数、类型实参和类型约束泛型中经常出现三个词类型参数定义泛型时写的T它是一个待确定的类型占位符。类型实参使用泛型时传入的String、int等具体类型。类型约束限制T必须满足某个上界例如T extends num。finalstringBoxBoxString(Dart);finalintBoxBoxint(100);这里T是类型参数String和int是类型实参。四、泛型方法4.1 定义泛型方法泛型方法把类型参数放在方法名之前TgetFirstT(ListTvalues){returnvalues.first;}调用时可以显式写出类型finalnumbergetFirstint([1,2,3]);finalnamegetFirstString([Dart,Flutter]);4.2 Dart 可以推断类型很多时候不需要手动写int或StringDart 会根据参数推断TfinalnumbergetFirst([1,2,3]);finalnamegetFirst([Dart,Flutter]);推断并不等于没有类型。相反类型仍然存在只是由编译器帮我们补出来了。4.3 泛型方法的边界泛型只抽象类型变化不会自动解决业务逻辑差异。如果不同类型的处理规则完全不同强行写成一个泛型方法反而会让代码难懂。好的泛型方法应该满足一个特点算法对类型本身没有特殊偏好只依赖类型提供的共同能力。五、泛型类和泛型接口5.1 泛型类缓存、容器、结果包装器都适合使用泛型类classCacheT{T?_value;voidsave(Tvalue){_valuevalue;}T?read(){return_value;}}使用时缓存的读写类型保持一致finalcacheCacheString();cache.save(Dart);finalvaluecache.read();// String?如果误把整数放入CacheString分析器会在编译阶段提示错误。5.2 泛型接口数据仓库经常需要对不同实体提供相同的读取约定abstractclassRepositoryT{FutureT?findById(Stringid);FutureListTfindAll();}用户仓库和订单仓库可以复用接口结构同时保留各自的实体类型classUserRepositoryimplementsRepositoryUser{overrideFutureUser?findById(Stringid)asyncnull;overrideFutureListUserfindAll()async[];}classUser{finalStringname;User(this.name);}RepositoryUser表明这个仓库只负责用户数据调用方不需要再猜返回值是什么。六、泛型类型约束6.1 使用 extends 限制能力有时我们不希望T可以是任意类型而是要求它具备某些能力。例如求和函数只接受数字TaddTextendsnum(Tleft,Tright){return(leftright)asT;}T extends num的意思是T必须是num或num的子类型例如int、double。注意Dart 的num运算结果类型可能需要根据实际需求转换或统一为num。约束负责限制输入范围不会替你决定所有运算结果的精确类型。更常见也更稳妥的写法是直接返回numnum addNumbersTextendsnum(Tleft,Tright){returnleftright;}6.2 约束带来的好处TmaxValueTextendsComparableT(Tleft,Tright){returnleft.compareTo(right)0?left:right;}这里的约束保证T有compareTo()方法所以函数可以安全比较而不需要使用dynamic。6.3 约束不是类型转换T extends num不是把字符串转换成数字也不是把所有类型都变成num。它只是告诉编译器调用这个方法时T必须属于允许的类型范围。七、泛型与集合7.1 List 让元素类型明确finalListintscores[90,88,95];finalListStringnames[Ada,Linus];Listint只能放整数ListString只能放字符串。集合的泛型参数会影响添加元素、读取元素和遍历时的类型推断。7.2 MapK, V 有两个类型参数finalMapString,intscores{Dart:100,Flutter:95,};K表示 key 类型V表示 value 类型。MapString, int读起来就是“字符串键、整数值的映射”。7.3 Future 和 Stream泛型不只用于集合FutureStringloadName()asyncDart;Streamintcount()async*{yield1;yield2;}FutureString表示未来产生一个字符串Streamint表示持续产生整数。前面学过的异步类型本身就是泛型类型。7.4 List 和 List 不应混为一谈Listvalues[1,Dart];ListdynamicdynamicValues[1,Dart];ListObjectobjectValues[1,Dart];它们都能装不同值但表达的意图不同。裸List会丢失更多静态类型信息Listdynamic明确表示元素按动态类型处理ListObject仍然保留“这是对象”的静态边界。八、常见误区8.1 泛型不是模板复制泛型不是 C 模板那种简单的文本复制也不是把代码机械生成很多份。它是一种类型系统机制用来表达代码与类型之间的关系。8.2 泛型不等于 ObjectObject表示一个值至少是对象但泛型T表示“调用者传入的具体类型关系”。TidentityT(Tvalue)value;调用identity(1)时返回值仍然是int如果把参数和返回值都写成Object调用方通常还需要额外判断或转换。8.3 泛型不等于 dynamicdynamic会关闭一部分静态检查适合确实需要动态调用的边界场景。泛型则尽量保留类型信息让编译器继续帮助你。可以这样记dynamic是“暂时不检查”泛型是“换一种类型继续检查”。8.4 不要为了泛型而泛型如果一个类只服务于一种类型而且没有复用需求直接写具体类型通常更清楚。泛型的目标是表达真实的类型变化不是让每个类都出现一个T。九、总结这一篇我们系统学习了 Dart 中的泛型。泛型把类型变成参数让同一份方法、类或接口可以复用同时保留静态类型检查。ListT、MapK, V、FutureT和StreamT都是泛型在 Dart 中的实际应用。使用T extends num或T extends ComparableT可以限制类型范围让泛型代码安全地使用某些共同能力。最后记住三句话泛型让代码复用和类型安全同时存在。T是类型参数调用时可以由 Dart 自动推断。泛型不是dynamic也不是越复杂越好要围绕真实的类型关系使用。
返回列表