Flutter Dart:泛型的协变与逆变

本文详细介绍了Dart中的泛型机制,包括如何使用泛型、类型省略以及真伪泛型的区别。接着,文章深入探讨了泛型的协变、逆变和不变的概念,通过与Java和Kotlin的对比,解释了它们在多态实现中的应用。Dart的泛型默认支持协变,但存在安全问题,因为允许读写操作。最后,文章提到了Dart中的协变关键字`convariant`用于增强类型安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

41b3eda7735ccdc5488daf94e980cccc.gif

本文字数:16281 

预计阅读时间 41 分钟

Flutter Dart也支持泛型和泛型的协变与逆变,并且用起来比Java,Kotlin更方便。那么Dart中的泛型协变和逆变,应该如何理解和使用呢?它与Java,Kotlin中的逆变和协变又有什么区别呢?文章将从浅到深跟大家一起来探讨学习。

一、Dart泛型

Dart中的泛型和其他语言的泛型一样,都是为了减少大量的模板代码,举例说明:

// Dart

//这是一个打印int类型msg的PrintMsg

class PrintMsg {

  int _msg;

  set msg(int msg) { this._msg = msg;}

  void printMsg() { print(_msg);}

}

当打印需求发生变化,需要支持打印更多种类的数据类型,不支持范型的话,代码会大量增加,如下面这样:

// Dart

//非范型写法,现在需要新增支持String,double和自定义类的Msg,

class Msg {

  @override

  String toString() { return "This is Msg";}

}

class PrintMsg {

  int _intMsg;

  String _stringMsg;

  double _doubleMsg;

  Msg _msg;

  set intMsg(int msg) { this._intMsg = msg;}

  set stringMsg(String msg) { this._stringMsg = msg;}

  set doubleMsg(double msg) { this._doubleMsg = msg;}

  set msg(Msg msg) { this._msg = msg;}

  void printIntMsg() { print(_intMsg);}

  void printStringMsg() { print(_stringMsg);}

  void printDoubleMsg() { print(_doubleMsg);}

  void printMsg() { print(_msg);}

}

而有范型的支持后,不管增加多少种数据类型,打印类都可以简化成如下几行:

// Dart

//泛型写法,简化成几行代码,且支持无数种数据类型:

class PrintMsg<T> {

    T _msg;

    set msg(T msg) { this._msg = msg; }

    void printMsg() { print(_msg); }

}

1、泛型类型省略

Dart中可以指定实际的泛型参数类型,也可以省略。实际上,编译器会自动进行类型推断,把泛型参数类型转为dynamic类型。举例说明:

// Dart

List<int> numTest = [1, 2, 3];   //注意int在Dart中是个类,继承自num类。和Java中的基础类型int不一样,java中的int是不能作为泛型的实参的,因为int不是Object的子类。

Map<String, int> mapTest = { 'a': 1, 'b': 2, 'c': 3};

//Dart可简写成如下形式, 但非常不推荐,因为泛型简写会被自动类型推断为dynamic(非泛型简写的类型推断不会变成dynamic)。

List numTest = [1, 2, 3];

Map mapTest = { 'a': 1, 'b': 2, 'c': 3}

print(numTest.runtimeType.toString());   // output“List<dynamic>”

print(mapTest.runtimeType.toString());   // output“_InternalLinkedHashMap<dynamic,dynamic>”

//所以Dart的简写方式,相当于如下形式

List<dynamic> numTest = [1, 2, 3];

Map<dynamic, dynamic> mapTest = { 'a': 1, 'b': 2, 'c': 3};

2、真伪泛型

在Java中,ArrayList是支持泛型的,但是它的数据存储用的却是Object[],这是因为Java在编译的时候会进行类型擦除,也可以说Java中的泛型是种伪泛型,泛型只存在编译时期,运行时泛型就会被擦除(所以运行时无法获取泛型T的真实类型信息)。

Kotlin最终也会编译生成和Java相同规格的class文件,所以Kotlin中的泛型也会被擦除,也无法使用{a is T;}进行类型判断。

不过Kotlin为泛型的类型判断做了一点改进,支持在inline函数里判断reified修饰的泛型类型。它的原理是,在编译过程中,编译器会将inline内联函数的代码替换到实际调用的地方,并且对reified定义的泛型参数,不进行泛型擦除,而把调用方的形参直接替换成具体的实参类型,这样编译的结果,就支持inline内联函数内部对reified泛型进行类型判断,举例说明:

// Kotlin

inline fun <reified T> PrintMsg(value:T) {

    LogUtil.d("test", T::class.toString())  // 非inline函数的reified泛型,不能调用T::class

    LogUtil.d("test", value!!::class.toString())  //不管是否inline函数,都会打印参数的真实类型信息

    var b = value is List<*>  // * 不能换成任何具体类型,编译器会报错。注意,PrintMsg方法里的泛型T,不是List<E>中的泛型E, E在Kotlin中运行时都会被擦除成为Object了。

    LogUtil.d("test", b.toString())

    b = "abc" is T  // 非inline函数的reified泛型,则不能调用 is T

    LogUtil.d("test", b.toString())

}

fun main() {

    PrintMsg("test")

    PrintMsg(arrayListOf(1, 2, 3))

    PrintMsg(arrayListOf("a", "b", "c"))

}

// output

//D/test: class java.lang.String   // 在inline函数里,泛型T直接换成了实参类型String

//D/test: class java.lang.String   // 读取的是实参的真实类型信息。

//D/test: false

//D/test: true

//D/test: class java.util.ArrayList   //泛型T换成了实参ArrayList,ArrayList中的元素E已经都换成了Object

//D/test: class java.util.ArrayList   //此处ArrayList中的元素E已经都换成了Object

//D/test: true

//D/test: false

//D/test: class java.util.ArrayList    //泛型T换成了实参ArrayList,此处ArrayList中的元素E已经都换成了Object

//D/test: class java.util.ArrayList   //此处ArrayList中的元素E已经都换成了Object

//D/test: true

//D/test: false

而Dart的泛型是真泛型,在编译期和运行期都可以通过泛型拿到其真实类型,所以Dart中可以直接用泛型进行类型判断,用代码举例说明 :

// Dart

void PrintMsg<T>(T value) {

  print("test---"+ T.toString());   //使用没有限制,可以获取List<E>中的泛型E的具体类型

  print("test---"+ value.runtimeType.toString());

  var b = value is List<String>;   // 可以直接判断范型结合体的具体类型

  print("test---2 "+ b.toString());

  b = "abc" is T;   //使用没有限制,T就是一种类型

  print("test---3 "+ b.toString());

}

void main() {

  PrintMsg("abc");

  PrintMsg(List<int>.from({ 1,2,3}));

 PrintMsg(List&l

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值