本文字数: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 |