本文字数: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 _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 //Dart可简写成如下形式, 但非常不推荐,因为泛型简写会被自动类型推断为dynamic(非泛型简写的类型推断不会变成dynamic)。 List numTest = [1, 2, 3]; Map mapTest = {'a': 1, 'b': 2, 'c': 3} print(numTest.runtimeType.toString()); // output“List print(mapTest.runtimeType.toString()); // output“_InternalLinkedHashMap //所以Dart的简写方式,相当于如下形式 List Map |
2、真伪泛型
在Java中,ArrayList是支持泛型的,但是它的数据存储用的却是Object[],这是因为Java在编译的时候会进行类型擦除,也可以说Java中的泛型是种伪泛型,泛型只存在编译时期,运行时泛型就会被擦除(所以运行时无法获取泛型T的真实类型信息)。
Kotlin最终也会编译生成和Java相同规格的class文件,所以Kotlin中的泛型也会被擦除,也无法使用{a is T;}进行类型判断。
不过Kotlin为泛型的类型判断做了一点改进,支持在inline函数里判断reified修饰的泛型类型。它的原理是,在编译过程中,编译器会将inline内联函数的代码替换到实际调用的地方,并且对reified定义的泛型参数,不进行泛型擦除,而把调用方的形参直接替换成具体的实参类型,这样编译的结果,就支持inline内联函数内部对reified泛型进行类型判断,举例说明:
// Kotlin inline fun 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 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 print("test---"+ T.toString()); //使用没有限制,可以获取List print("test---"+ value.runtimeType.toString()); var b = value is List 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 } // output //I/flutter (21231): test---String //I/flutter (21231): test---String //I/flutter (21231): test--- false //I/flutter (21231): test--- true //I/flutter (21231): test---List //I/flutter (21231): test---List //I/flutter (21231): test--- false //I/flutter (21231): test--- false //I/flutter (21231): test---List //I/flutter (21231): test---List //I/flutter (21231): test--- true //I/flutter (21231): test--- false |
通过示例代码和output log,我们可以看出,泛型是编译期的一个概念,在运行期,Java/Kotlin 把泛型都转换成了Object,而Dart保留了具体的实参类型,都不再是一个不确定的形参类型。
二、泛型关系:协变,逆变和不变
协变,逆变和不变是一种描述泛型类型关系变化的概念。在了解Dart中的协变,逆变和不变之前,我们先来搞清楚什么是类型关系。
1、类和类型,子类和子类型
类和类型的关系容易混淆。Dart中的类可分为两大类: 泛型类和非泛型类。
非泛型类是开发中接触最多的类。非泛型类去定义一个变量时,这个非泛型类就是这个变量的类型。例如:
定义一个非泛型类 Class Person,那么就会有个Person类型。
定义一个非泛型类 Class Boy extend Person类,会有个Boy类型
泛型类比非泛型类复杂,一个泛型类可以对应无限种类型。在定义泛型类的时候会定义泛型形参,要拿到一个真实的泛型类型,就需要在使用泛型类的地方,给泛型类传入具体的类型实参替换定义中的类型形参。
我们经常使用的集合基本都会使用泛型,所以以集合举例说明,List>都是不同的类型。
所以,每个非泛型类会对应一个类型。而每个泛型类,会对应无限种类型。
知道了类和类型的关系,我们再来看子类和子类型的关系。
子类就是派生类,它继承父类。例如: class Boy extends Person,则Boy
就是Person的子类,Person 是 Boy类的父类。
子类型没有继承关系,它的定义是: 需要A类型值的任何地方,都可以使用B类型的值来替换,则B类型就是A类型的子类型,或A类型是B类型的超类型。
举例说明:
Boy是Person的子类和子类型。
List
List
所以属于子类关系的类型,也会成为子类型关系。例如:Boy是Person的子类,它能代替Person出现的任何地方,所以它们之间存在子类型关系。而double不能替代int出现的地方,所以它们不存在子类型关系。但子类型关系,不一定是子类关系。
介绍完泛型和子类型关系概念,下面开始具体介绍类型关系变换:协变,逆变和不变。我们下面以Java为示例进行概念说明,在Java代码中使用 和 来表达协变与逆变(在Kotlin中用
2、类型关系的定义与意义
协变,逆变和不变是用来描述类型转换后的类型关系,其定义如下:
如果A,B表示类型,f(*)表示类型转换,< 表示子类型关系(比如,A
f(*)是
协变
(covariant)的,当A
f(*)是
逆变
(contravariant)的,当A
f(*)是
不变
(invariant)的,当A
上面的定义看起来比较抽象,下面以Java中的ArrayList集合举例说明:
ArrayList这种形式的泛型是
支持协变的
,它可以被赋值为ArrayList