IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Kotlin泛型 -> 正文阅读

[移动开发]Kotlin泛型

Kotlin中声明和使用泛型类及泛型函数的基本概念和Java类似。
同时Kotlin引入了新的概念,比如实化类型参数和声明点变型。这些概念对于我们来说可能很新奇。
实化类型参数: 允许你在运行时的内联函数调用引用作为类型实参的具体类型(对普通的类和函数来说,这样行不通,因为类型实参在运行时会被擦除)。
声明点变型: 可以说明一个带类型参数的泛型类型,是否是另一个泛型类型的字类型或者超类型,它们的基础类型相同但类型参数不同。
例如,它能调节是否可以把List<Int> 类型的参数传给期望List<Any>的函数。使用点变型在具体使用一个泛型类型时,做同样的事,达到和Java通配符一样的效果。

泛型类型参数

泛型允许你定义带类型形参的类型,当这种类型的实例被创建出来的时候,类型形参被替换成称为类型实参的具体类型,例如,如果有一个List类型的变量,弄清楚这个列表中可以存储哪些事物是很有意义的。类型形参可以准确清晰地进行描述,就像这样“这个变量保存了字符串列表”,而不是“这边变量保存了一个列表”。

List<String>    class Map<K, V>     Map<String, Person>

Kotlin的声明概念和Java没有什么不一样,Kotlin编译器也常常能推导出类型实参:

val authors = listOf("Dmitry", "Svetlana")

需要注意的是,和Java不同,Kotlin始终要求类型实参要么被显式地说明,要么能被编译器推导出来。
因为泛型是在1.5版本才引入到Java的,它必须保证和基于老版本Java编写的代码兼容。而Kotlin从一开始就有泛型,所以它不支持原生态类型,类型实参必须定义。

泛型函数

泛型函数有它自己的类型形参。这些类型形参在每次函数调用时都必须替换成具体的类型实参。如:

// 先声明类型形参 
fun <T> List<T>.slice(indices: IntRange): List<T>

声明泛型类

和Java一样,Kotlin通过在类名称后加上一对尖括号,并把类型参数放在尖括号内来声明泛型类及泛型接口。一旦声明之后,就可以在类的主体内像其他类型一样使用类型参数。

interface List<T> {
	Operator fun get(index: Int): T
}

如果你的类继承了泛型类(或者实现了泛型接口),你就得为基础类型的泛型形参提供一个类型实参。它可以是具体的类型或者另外一个类型形参:

class StringList:List<String> {
	override fun get(index: Int): String = ...
}

class ArrayList<T> : List<T> {
	override fun get(index: Int): T = ... 
}

类型参数约束

类型参数约束可以限制作为(泛型)类和(泛型)函数的类型实参的类型。
如果你把一个类型指定为泛型类型形参的上界约束,在泛型类型具体的初始化中,其对应的类型实参就必须是这个具体类型或者它的子类型。
以计算列表元素只和的函数为例。它可以用在List<Int>List<Double>上,但不可以用在List<String>这样的列表上。可以定义一个类型参数约束,说明sum的类型形参必须是数字,来表达这个限制。
在Java中,用的是关键字extends来表达一样的概念:

// Java
<T extends Number> T sum(List<T> list)

// Kotlin
fun <T :Number>  List<T>.sum(): T

声明带类型参数约束的函数,这个函数的实参必须是可比较的元素。

fun <T: Comparable<T>> max(first: T, second: T): T {
	return if (first > second) first else second
}

// 字符串按字母表顺序比较, String 类继承了Comparable<String>
>>> println(max("kotlin", "java"))
kotlin

为一个类型参数指定多个约束

fun <T> ensureTrailingPerid(seq: T) where T : CharSequence, T : Appendable {
	if(!seq.endsWith('.')) {
    	seq.append('.')
    }
}

>>> val helloWorld = StringBuilder("Hello World")
>>> ensureTrailingPeriod(helloWorld)
>>> println(helloWorld)
hello World.

这种情况下,可以说明作为类型实参的类型必须实现CharSequence和Appendable两个接口。这意味着改类型的值可以使用访问数据(endsWith)和修改数据(append)两种操作。

让类型形参非空

如果你声明的是泛型类或者泛型函数,任何类型实参,包括那些可空的类型实参,都可以替换它的类型形参。事实上,没有指定上界的类型形参将会使用Any?这个默认的上界。看看下面这个例子:

// value是可空的,所以要用安全调用
class Processor<T> {
	fun process(value: T) {
		value?.hashCode()
	}
}

如果你想保证替换类型形参的始终是非空类型,可以通过指定一个约束来实现。如果你出来可空性之外没有任何限制,可以使用Any代替默认的Any?作为上界:

class Processor<T : Any> {
	fun process(value: T) {
		value.hashCode()
	}
}

运行时的泛型:擦除和实化类型参数

你可能知道,JVM上的泛型一般是通过类型擦除实现的,就是说泛型类实现的类型实参在运行时是不保留的。在本节中,我们讲讨论类型擦除对Kotlin的实际影响,以及如何通过将函数声明为inline来解决其局限性。可以声明一个inline函数,使其类型实参不被擦除(或者,按照Kotlin术语,称作实化)。我们将详细讨论实化类型参数,并查看一些有用的例子。

运行时的泛型:类型检查和转换

和Java一样,Kotlin的泛型在运行时也被擦除了。这意味着泛型类实力不会携带用于创建它的类型实参的信息。
因为类型实参没有被存储下来,你不能检查它们。例如,你不能判断一个列表是一个包含字符串的列表还是包含其它对象的列表。一般而言,在is检查中不可能使用类型实参中的类型。下面这样的代码不会编译:

>>> if (value is List<String>) {...}
ERROR: Cannot check for instance of erased type

尽管在运行时可以完全断定这个值是一个List,但你依然无法判断它是一个含有字符串的列表,还是罕有人,或者含有其他什么:这些信息被擦除了。注意擦除泛型类型信息是有好处的:应用程序使用的内存总量较小,因为要保存在内存中的类型信息更少。
如前所述,Kotlin不允许使用没有指定类型实参的泛型类型。那么你可能想知道如何检查一个值是否是列表,而不是set或者其他对象。可以使用特殊的星号投影语法来做这种检查:

if (value is List<*>)  {...}

声明带实化类型参数的函数

前面我们已经讨论过,Kotlin泛型在运行时会被擦除,这意味着如果你有一个泛型类的实例,你无法弄清楚在这个实例创建时用的究竟是哪些类型实参。泛型函数类型实参也是这样。在调用泛型函数的时候,在函数中你不能决定调用它用的类型实参:

>>> fun <T> isA(value: Any) = value is T
Error: Cannot check  for instance of erased type: T

通常情况下是这样,只有一种例外可以避免这种限制:内联函数。内联函数的类型形参能够被实化,意味着你可以在运行时引用实际的类型实参。
如果用inline关键字标记一个函数,编译器会把每一个函数调用都替换成函数实际的代码实现。使用内联函数还可能提升性能,如果该函数使用了lambda实参:lambda的代码也会内联,所以不会创建任何的匿名类。这一节会展示inline函数大显身手的另一种场景:它们的类型参数可以被实化。
如果你把前面的例子中的isA函数声明成inline并且用reified标记类型参数,你就能够用该函数检查value是不是T的实例。

inline fun <reified T> isA(value: Any) = value is T
>>> println(isA<String>("abc") )
true
>>> println(isA<String>(123))
false

接下来我们看看使用实化类型参数的一些稍微有意义的例子。一个实化类型参数能发挥作用的最简单的例子就是标准库函数filterIsInstance。这个函数接收一个集合,选择其中那些指定类的实例,然后返回这些被选中的实例。下面展示了这个函数的用法。

>>> val items = listOf("one", 2, ''three")
>>> println(items.filterIsInstance<String>())
[one, three]

通过指定<String> 作为函数的类型实参,你表明感兴趣的只是字符串。因此函数的返回类型是List。这种情况下,类型实参在运行时是已知的,函数filterIsInstance使用它来检查列表中的值是不是指定为该类型实参的类的实例。
下面是Kotlin标准函数库filterIsInstance声明的简化版本。

// reified 声明了类型参数不会在运行时被擦除
// 可以检查元素是不是指定为类型实参的类的实例
inline fun <reified T>
			Iterable<*>.filterIsInstance(): List<T> {
			val destination = mutableListOf<T>()
			for (element in this) {
				if (element is T) {
					destination.add(element)
				}
			}
}

为什么实化只对内联函数有效
这是什么原理?为什么在inline函数中允许这样写element is T,而普通的类或函数却不行?
正如在8.2节中讨论的,编译器把实现内联函数的字节码插入每一次调用发生的地方。每次你调用带实化类型参数的函数时,编译器都知道这次特定调用中用作类型实参的确切类型。因此,编译器可以生成引用作为类型实参的具体类的字节码。实际上,对代码清单9.8中的filterIsInstance<String>调用来说,生成的代码和下面这段代码是等价的:

for (element in this) {
	if (element is  String) {
		destination.add(element)
	}
}

因为生成的字节码引用了具体类,而不是类型参数,它不会被运行发生的类型参数擦除影响。
注意,带reified类型参数的inline函数不能在Java中调用-------他们可以被调用而不能被内联。带实化类型参数的函数需要额外的处理,来把类型实参的值替换到字节码中,所以它们必须永远是内联的。这样它们不可能用Java那样普通的方式调用。
一个内联函数可以有多个实例化类型参数,也可以同时拥有非实例化类型参数和实化类型参数。注意,filterIsInstance函数虽然被标记成inline,而它并不期望lambda作为实参。在8.2.4中,我们提到把函数标记成内联只有在一种情况下有性能优势,即函数拥有函数类型的形参并且对其对应的实参----lambda----和函数inline,这里这样做是为了能够使用实化类型参数。
为了保证良好的性能,你仍然需要跟踪了解标记为inline的函数的大小。如果函数变得庞大,最好把不依赖实化类型参数的代码抽取到单独的非内联函数中。

使用实化类型参数代替类引用

另一种实化类型参数常见使用场景是为接收java.lang.Class类型参数的API构建适配器。一个这种API的例子是JDK中ServiceLoader,它接收一个代表接口或抽象类的java.lang.Class,并返回实现了该接口(或继承了该抽象类)的类的实例。

val serviceImpl = ServiceLoader.load(Service::class.java)

::class.java 的语法展示了如何获取 java.lang.Class 对应的Kotlin类。这和Java中的Service.class是完全相同的。
现在让我们用带实化类型参数的函数重写这个例子:

val serviceImpl = loadService<Service>()

代码是不是短了不少?要加载的服务类现在被指定成了loadService函数的类型参数。

inline fun <reified T> loadService() {
	return ServiceLoader.load(T::class.java)
}

变型:泛型和子类型化

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-16 11:52:22  更:2021-08-16 11:53:05 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 10:28:57-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码