当前位置: 首页 > wzjs >正文

滕州市住房城乡建设局网站网站建设公司文案

滕州市住房城乡建设局网站,网站建设公司文案,莆田网站建设哪家好,酷炫网站模板对泛型进行实化 泛型实化这个功能对于绝大多数Java 程序员来讲是非常陌生的,因为Java 中完全没有这个概 念。而如果我们想要深刻地理解泛型实化,就要先解释一下Java 的泛型擦除机制才行。 在JDK 1.5之前,Java 是没有泛型功能的,…

对泛型进行实化

泛型实化这个功能对于绝大多数Java 程序员来讲是非常陌生的,因为Java 中完全没有这个概
念。而如果我们想要深刻地理解泛型实化,就要先解释一下Java 的泛型擦除机制才行。

在JDK 1.5之前,Java 是没有泛型功能的,那个时候诸如List之类的数据结构可以存储任意类型
的数据,取出数据的时候也需要手动向下转型才行,这不仅麻烦,而且很危险。比如说我们在
同一个List中存储了字符串和整型这两种数据,但是在取出数据的时候却无法区分具体的数据类
型,如果手动将它们强制转成同一种类型,那么就会抛出类型转换异常。

于是在JDK 1.5中,Java 终于引入了泛型功能。这不仅让诸如List之类的数据结构变得简单好
用,也让我们的代码变得更加安全。

但是实际上,Java 的泛型功能是通过类型擦除机制来实现的。什么意思呢?就是说泛型对于类
型的约束只在编译时期存在,运行的时候仍然会按照JDK 1.5之前的机制来运行,JVM是识别不
出来我们在代码中指定的泛型类型的。例如,假设我们创建了一个List集合,虽然
在编译时期只能向集合中添加字符串类型的元素,但是在运行时期JVM并不能知道它本来只打算
包含哪种类型的元素,只能识别出来它是个List。

所有基于JVM的语言,它们的泛型功能都是通过类型擦除机制来实现的,其中当然也包括了
Kotlin 。这种机制使得我们不可能使用a is T或者T::class.java这样的语法,因为T的实际
类型在运行的时候已经被擦除了。

然而不同的是,Kotlin 提供了一个内联函数的概念,我们在第6章的Kotlin 课堂中已经学过了这
个知识点。内联函数中的代码会在编译的时候自动被替换到调用它的地方,这样的话也就不存
在什么泛型擦除的问题了,因为代码在编译之后会直接使用实际的类型来替代内联函数中的泛
型声明,其工作原理如图

在这里插入图片描述

可以看到,bar()是一个带有泛型类型的内联函数,foo()函数调用了bar()函数,在代码编
译之后,bar()函数中的代码将可以获得泛型的实际类型。

这就意味着,Kotlin 中是可以将内联函数中的泛型进行实化的。

那么具体该怎么写才能将泛型实化呢?首先,该函数必须是内联函数才行,也就是要用inline
关键字来修饰该函数。其次,在声明泛型的地方必须加上reified关键字来表示该泛型要进行
实化。示例代码如下:

inline fun <reified T> getGenericType() { 
} 

上述函数中的泛型T就是一个被实化的泛型,因为它满足了内联函数和reified关键字这两个前
提条件。那么借助泛型实化,到底可以实现什么样的效果呢?从函数名就可以看出来了,这里
我们准备实现一个获取泛型实际类型的功能,代码如下所示:

inline fun <reified T> getGenericType() = T::class.java 

虽然只有一行代码,但是这里却实现了一个Java 中完全不可能实现的功能:
getGenericType()函数直接返回了当前指定泛型的实际类型。T.class这样的语法在Java
中是不合法的,而在Kotlin 中,借助泛型实化功能就可以使用T::class.java这样的语法了。

现在我们可以使用如下代码对getGenericType()函数进行测试:

fun main() { val result1 = getGenericType<String>() val result2 = getGenericType<Int>() println("result1 is $result1") println("result2 is $result2") 
} 

这里给getGenericType()函数指定了两种不同的泛型,由于getGenericType()函数会将
指定泛型的具体类型返回,因此这里我们将返回的结果进行打印。
现在运行一下main()函数,结果如图

在这里插入图片描述

泛型实化功能的运行结果

可以看到,如果将泛型指定成了String,那么就可以得到java.lang.String的类型;如果
将泛型指定了Int,就可以得到java.lang.Integer的类型。

关于泛型实化的基本用法就介绍到这里,接下来我们看一看,泛型实化在Andr oid 项目当中具体
可以有哪些应用。

泛型实化的应用

泛型实化功能允许我们在泛型函数当中获得泛型的实际类型,这也就使得类似于a is T、
T::class.java这样的语法成为了可能。而灵活运用这一特性将可以实现一些不可思议的语法
结构,下面我们赶快来看一下吧。

到目前为止,我们已经将Andr oid 的四大组件全部学完了,除了ContentP rovider 之外,你会
发现其余的3个组件有一个共同的特点,它们都是要结合Intent 一起使用的。比如说启动一个
Activity 就可以这么写:

val intent = Intent(context, TestActivity::class.java) 
context.startActivity(intent) 

有没有觉得TestActivity::class.java这样的语法很难受呢?当然,如果在没有更好选择
的情况下,这种写法也是可以忍受的,但是Kotlin 的泛型实化功能使得我们拥有了更好的选择。

新建一个reified.kt 文件,然后在里面编写如下代码:

inline fun <reified T> startActivity(context: Context) { val intent = Intent(context, T::class.java) context.startActivity(intent) 
} 

这里我们定义了一个startActivity()函数,该函数接收一个Context参数,并同时使用
inline和reified关键字让泛型T成为了一个被实化的泛型。

接下来就是神奇的地方了,Intent 接收的第二个参数本来应该是一个具体Activity 的Class类型,但由于现在T已经是一个
被实化的泛型了,因此这里我们可以直接传入T::class.java。最后调用Context的
startActivity()方法来完成Activity 的启动。

现在,如果我们想要启动TestA ctivity ,只需要这样写就可以了:

startActivity<TestActivity>(context) 

Kotlin 将能够识别出指定泛型的实际类型,并启动相应的Activity 。怎么样,是不是觉得代码瞬
间精简了好多?这就是泛型实化所带来的神奇功能。

不过,现在的startActivity()函数其实还是有问题的,因为通常在启用Activity 的时候还可
能会使用Intent 附带一些参数,比如下面的写法:

val intent = Intent(context, TestActivity::class.java) 
intent.putExtra("param1", "data") 
intent.putExtra("param2", 123) 
context.startActivity(intent) 

而经过刚才的封装之后,我们就无法进行传参了。

这个问题也不难解决,只需要借助高阶函数就可以轻松搞定。回到
reified.kt 文件当中,这里添加一个新的startActivity()函数重载,如下所示:

inline fun <reified T> startActivity(context: Context, block: Intent.() -> Unit) {val intent = Intent(context, T::class.java) intent.block() context.startActivity(intent) 
} 

可以看到,这次的startActivity()函数中增加了一个函数类型参数,并且它的函数类型是
定义在Intent 类当中的。在创建完Intent 的实例之后,随即调用该函数类型参数,并把Intent 的
实例传入,这样调用startActivity()函数的时候就可以在Lambda 表达式中为Intent 传递
参数了,如下所示:

startActivity<TestActivity>(context) { putExtra("param1", "data") putExtra("param2", 123) 
} 

不得不说,这种启动Activity 的代码写起来实在是太舒服了,泛型实化和高阶函数使这种语法结
构成为了可能,感谢Kotlin 提供了如此多优秀的语言特性。

泛型的协变

泛型的协变和逆变功能不太常用,而且我个人认为有点不容易理解。但是Kotlin 的内置API中使
用了很多协变和逆变的特性,因此如果想要对这个语言有更加深刻的了解,这部分内容还是有
必要学习一下的。

我在学习协变和逆变的时候查阅了很多资料,这些资料大多十分晦涩难懂,因此也让我对这两
个知识点产生了一些畏惧。但是真正掌握之后,发现其实也并不是那么难,所以这里我会尽量
使用最简明的方式来讲解这两个知识点,希望你可以轻松掌握。

在开始学习协变和逆变之前,我们还得先了解一个约定。一个泛型类或者泛型接口中的方法,
它的参数列表是接收数据的地方,因此可以称它为in位置,而它的返回值是输出数据的地方,因
此可以称它为out 位置,如图

在这里插入图片描述

有了这个约定前提,我们就可以继续学习了。首先定义如下3个类:

open class Person(val name: String, val age: Int) 
class Student(name: String, age: Int) : Person(name, age) 
class Teacher(name: String, age: Int) : Person(name, age) 

这里先定义了一个Person类,类中包含name和age这两个字段。然后又定义了Student和
Teacher这两个类,让它们成为Person类的子类。

现在我来问你一个问题:如果某个方法接收一个Person类型的参数,而我们传入一个Student
的实例,这样合不合法呢?很显然,因为Student是Person的子类,学生也是人呀,因此这是
一定合法的。

那么我再来升级一下这个问题:如果某个方法接收一个List类型的参数,而我们传
入一个List的实例,这样合不合法呢?看上去好像也挺正确的,但是Java 中是不
允许这么做的,因为List不能成为List的子类,否则将可能存在类型
转换的安全隐患。

为什么会存在类型转换的安全隐患呢?下面我们通过一个具体的例子进行说明。这里自定义一
个SimpleData类,代码如下所示:

class SimpleData<T> { private var data: T? = null fun set(t: T?) { data = t } fun get(): T? { return data } 
} 

SimpleData是一个泛型类,它的内部封装了一个泛型data字段,调用set()方法可以给data
字段赋值,调用get()方法可以获取data字段的值。

接着我们假设,如果编程语言允许向某个接收SimpleData参数的方法传入
SimpleData的实例,那么如下代码就会是合法的:

fun main() { val student = Student("Tom", 19) val data = SimpleData<Student>() data.set(student) handleSimpleData(data) // 实际上这行代码会报错,这里假设它能编译通过val studentData = data.get() 
} 
fun handleSimpleData(data: SimpleData<Person>) { val teacher = Teacher("Jack", 35) data.set(teacher) 
} 

发现这段代码有什么问题吗?在main()方法中,我们创建了一个Student的实例,并将它封装
到SimpleData当中,然后将SimpleData作为参数传递给
handleSimpleData()方法。但是handleSimpleData()方法接收的是一个
SimpleData参数(这里假设可以编译通过),那么在handleSimpleData()方法
中,我们就可以创建一个Teacher的实例,并用它来替换SimpleData参数中的原
有数据。这种操作肯定是合法的,因为Teacher也是Person的子类,所以可以很安全地将
Teacher的实例设置进去。

但是问题马上来了,回到main()方法当中,我们调用SimpleData的get()方法
来获取它内部封装的Student数据,可现在SimpleData中实际包含的却是一个
Teacher的实例,那么此时必然会产生类型转换异常。

所以,为了杜绝这种安全隐患,Java 是不允许使用这种方式来传递参数的。换句话说,即使
Student是Person的子类,SimpleData并不是SimpleData的子
类。

不过,回顾一下刚才的代码,你会发现问题发生的主要原因是我们在handleSimpleData()方
法中向SimpleData里设置了一个Teacher的实例。如果SimpleData在泛型T上是
只读的话,肯定就没有类型转换的安全隐患了,那么这个时候SimpleData可不可
以成为SimpleData的子类呢?

讲到这里,我们终于要引出泛型协变的定义了。假如定义了一个MyClass的泛型类,其中A
是B的子类型,同时MyClass< A > 又是 MyClass< B > 的子类型,那么我们就可以称 MyClass 在T
这个泛型上是协变的。

但是如何才能让 MyClass< A > 成为 MyClass< B > 的子类型呢?

刚才已经讲了,如果一个泛型类在其泛型类型的数据上是只读的话,那么它是没有类型转换安全隐患的。而要实现这一点,则
需要让MyClass类中的所有方法都不能接收T类型的参数。换句话说,T只能出现在out 位置
上,而不能出现在in位置上。

现在修改SimpleData类的代码,如下所示:

class SimpleData<out T>(val data: T?) { fun get(): T? { return data } 
} 

这里我们对SimpleData类进行了改造,在泛型T的声明前面加上了一个out关键字。这就意味
着现在T只能出现在out 位置上,而不能出现在in位置上,同时也意味着SimpleData在泛型T上
是协变的。

由于泛型T不能出现在in位置上,因此我们也就不能使用set()方法为data参数赋值了,所以这
里改成了使用构造函数的方式来赋值。你可能会说,构造函数中的泛型T不也是在in位置上的
吗?没错,但是由于这里我们使用了val关键字,所以构造函数中的泛型T仍然是只读的,因此
这样写是合法且安全的。另外,即使我们使用了var关键字,但只要给它加上private修饰
符,保证这个泛型T对于外部而言是不可修改的,那么就都是合法的写法。

经过了这样的修改之后,下面的代码就可以完美编译通过且没有任何安全隐患了:

fun main() { val student = Student("Tom", 19) val data = SimpleData<Student>(student) handleMyData(data) val studentData = data.get() 
} 
fun handleMyData(data: SimpleData<Person>) { val personData = data.get() 
} 

由于SimpleData类已经进行了协变声明,那么SimpleData自然就是
SimpleData的子类了,所以这里可以安全地向handleMyData()方法中传递参
数。

然后在handleMyData()方法中去获取SimpleData封装的数据,虽然这里泛型声明的是
Person类型,实际获得的会是一个Student的实例,但由于Person是Student的父类,向上
转型是完全安全的,所以这段代码没有任何问题。

学到这里,关于协变的内容你就掌握得差不多了,不过最后还有个例子需要回顾一下。前面我
们提到,如果某个方法接收一个List类型的参数,而传入的却是一个
List的实例, 在Java 中是不允许这么做的。注意这里我的用语,在Java 中是不允
许这么做的。

你没有猜错,在Kotlin 中这么做是合法的,因为Kotlin 已经默认给许多内置的API加上了协变声
明,其中就包括了各种集合的类与接口。还记得我们在第2章中学过的吗?Kotlin 中的List本身
就是只读的,如果你想要给List添加数据,需要使用MutableList 才行。既然List是只读的,也
就意味着它天然就是可以协变的,我们来看一下List简化版的源码:

public interface List<out E> : Collection<E> { override val size: Int override fun isEmpty(): Boolean override fun contains(element: @UnsafeVariance E): Boolean override fun iterator(): Iterator<E> public operator fun get(index: Int): E 
} 

List在泛型E的前面加上了out关键字,说明List在泛型E上是协变的。不过这里还有一点需要说
明,原则上在声明了协变之后,泛型E就只能出现在out 位置上,可是你会发现,在
contains()方法中,泛型E仍然出现在了in位置上。

这么写本身是不合法的,因为在in位置上出现了泛型E就意味着会有类型转换的安全隐患。但是
contains()方法的目的非常明确,它只是为了判断当前集合中是否包含参数中传入的这个元
素,而并不会修改当前集合中的内容,因此这种操作实质上又是安全的。那么为了让编译器能
够理解我们的这种操作是安全的,这里在泛型E的前面又加上了一个@UnsafeVariance注解,
这样编译器就会允许泛型E出现在in位置上了。但是如果你滥用这个功能,导致运行时出现了类
型转换异常,Kotlin 对此是不负责的。

泛型的逆变

理解了协变之后再来学习逆变,我觉得会相对比较容易一些,因为它们之间是有所关联的。
不过仅从定义上来看,逆变与协变却完全相反。那么这里先引出定义吧,假如定义了一个
MyClass < T >的泛型类,其中A是B的子类型,同时 MyClass< B > 又是 MyClass < A >的子类型,
那么我们就可以称MyClass在T这个泛型上是逆变的。协变和逆变的区别如图

在这里插入图片描述

协变与逆变的区别

从直观的角度上来思考,逆变的规则好像挺奇怪的,原本A是B的子类型,怎么MyClass
反过来成为MyClass的子类型了呢?别担心,下面我们通过一个具体的例子来学习一下,
你就明白了。

这里先定义一个Transformer接口,用于执行一些转换操作,代码如下所示:

interface Transformer<T> { fun transform(t: T): String 
} 

可以看到,Transformer接口中声明了一个transform()方法,它接收一个T类型的参数,并
且返回一个String类型的数据,这意味着参数T在经过transform()方法的转换之后将会变成
一个字符串。至于具体的转换逻辑是什么样的,则由子类去实现,Transformer接口对此并不
关心。

那么现在我们就尝试对Transformer接口进行实现,代码如下所示:

fun main() { val trans = object : Transformer<Person> { override fun transform(t: Person): String { return "${t.name} ${t.age}" } } handleTransformer(trans) // 这行代码会报错
} 
fun handleTransformer(trans: Transformer<Student>) { val student = Student("Tom", 19) val result = trans.transform(student) 
} 

首先我们在main()方法中编写了一个Transformer的匿名类实现,并通过
transform()方法将传入的Person对象转换成了一个“姓名+ 年龄”拼接的字符串。而
handleTransformer()方法接收的是一个Transformer类型的参数,这里在
handleTransformer()方法中创建了一个Student对象,并调用参数的transform()方法
将Student对象转换成一个字符串。

这段代码从安全的角度来分析是没有任何问题的,因为Student是Person的子类,使用
Transformer的匿名类实现将Student对象转换成一个字符串也是绝对安全的,并
不存在类型转换的安全隐患。但是实际上,在调用handleTransformer()方法的时候却会提
示语法错误,原因也很简单,Transformer并不是Transformer的子
类型。

那么这个时候逆变就可以派上用场了,它就是专门用于处理这种情况的。修改Transformer接
口中的代码,如下所示:

interface Transformer<in T> { fun transform(t: T): String 
} 

这里我们在泛型T的声明前面加上了一个in关键字。这就意味着现在T只能出现在in位置上,而
不能出现在out 位置上,同时也意味着Transformer在泛型T上是逆变的。

没错,只要做了这样一点修改,刚才的代码就可以编译通过且正常运行了,因为此时
Transformer已经成为了Transformer的子类型。

逆变的用法大概就是这样了,如果你还想再深入思考一下的话,可以想一想为什么逆变的时候
泛型T不能出现在out 位置上?为了解释这个问题,我们先假设逆变是允许让泛型T出现在out 位
置上的,然后看一看可能会产生什么样的安全隐患。

修改Transformer中的代码,如下所示:

interface Transformer<in T> { fun transform(name: String, age: Int): @UnsafeVariance T 
} 

可以看到,我们将transform()方法改成了接收name和age这两个参数,并把返回值类型改成
了泛型T。由于逆变是不允许泛型T出现在out 位置上的,这里为了能让编译器正常编译通过,所
以加上了@UnsafeVariance注解,这和List源码中使用的技巧是一样的。

那么,这个时候可能会产生什么样的安全隐患呢?我们来看一下如下代码就知道了:

fun main() { val trans = object : Transformer<Person> { override fun transform(name: String, age: Int): Person {return Teacher(name, age) } } handleTransformer(trans) 
} 
fun handleTransformer(trans: Transformer<Student>) { val result = trans.transform("Tom", 19) 
} 

上述代码就是一个典型的违反逆变规则而造成类型转换异常的例子。在
Transformer的匿名类实现中,我们使用transform()方法中传入的name和age
参数构建了一个Teacher对象,并把这个对象直接返回。由于transform()方法的返回值要求
是一个Person对象,而Teacher是Person的子类,因此这种写法肯定是合法的。

但在handleTransformer()方法当中,我们调用了Transformer的
transform()方法,并传入了name和age这两个参数,期望得到的是一个Student对象的返
回,然而实际上transform()方法返回的却是一个Teacher对象,因此这里必然会造成类型转
换异常。

由于这段代码是可以编译通过的,那么我们可以运行一下,打印出的异常信息如图
在这里插入图片描述

逆变使用不当造成的类型转换异常

可以看到,提示我们Teacher类型是无法转换成Student类型的。

也就是说,Kotlin 在提供协变和逆变功能时,就已经把各种潜在的类型转换安全隐患全部考虑进
去了。只要我们严格按照其语法规则,让泛型在协变时只出现在out 位置上,逆变时只出现在in
位置上,就不会存在类型转换异常的情况。虽然@UnsafeVariance注解可以打破这一语法规
则,但同时也会带来额外的风险,所以你在使用@UnsafeVariance注解时,必须很清楚自己
在干什么才行。

最后我们再来介绍一下逆变功能在Kotlin 内置API中的应用,比较典型的例子就是Comparable
的使用。Comparable是一个用于比较两个对象大小的接口,其源码定义如下:

interface Comparable<in T> { operator fun compareTo(other: T): Int 
} 

可以看到,Comparable在T这个泛型上就是逆变的,compareTo()方法则用于实现具体的比
较逻辑。那么这里为什么要让Comparable接口是逆变的呢?想象如下场景,如果我们使用
Comparable实现了让两个Person对象比较大小的逻辑,那么用这段逻辑去比较两
个Student对象的大小也一定是成立的,因此让Comparable成为
Comparable的子类合情合理,这也是逆变非常典型的应用。


文章转载自:

http://3RGS4QR1.sjqmL.cn
http://c4l5KIeT.sjqmL.cn
http://4I6TXIi1.sjqmL.cn
http://dZIGWiMu.sjqmL.cn
http://KtRAP96U.sjqmL.cn
http://rJFdnCoV.sjqmL.cn
http://vOdf9Lzd.sjqmL.cn
http://t6S3mf6I.sjqmL.cn
http://5doEWiCg.sjqmL.cn
http://vl1MP5uP.sjqmL.cn
http://n8qPGWiK.sjqmL.cn
http://S3R0e8hA.sjqmL.cn
http://k2LRWXEZ.sjqmL.cn
http://dYwMeKno.sjqmL.cn
http://WBan5kq4.sjqmL.cn
http://olhtMNDZ.sjqmL.cn
http://nWGUSGP2.sjqmL.cn
http://BhC89TtJ.sjqmL.cn
http://DSKT8Uri.sjqmL.cn
http://idpTU0fU.sjqmL.cn
http://x3jEopYq.sjqmL.cn
http://970QUuJM.sjqmL.cn
http://gUg717v2.sjqmL.cn
http://pxSu91WD.sjqmL.cn
http://oGx72VpB.sjqmL.cn
http://xbVLHEfp.sjqmL.cn
http://RCLHXFVT.sjqmL.cn
http://YrBiBfSj.sjqmL.cn
http://FdEPH0Ns.sjqmL.cn
http://svH75Tqw.sjqmL.cn
http://www.dtcms.com/wzjs/646874.html

相关文章:

  • 新乡网站建设专业熊掌网络上海网站建设工作室
  • 实木餐桌椅网站建设欧美风的网站设计
  • 服务器搭建网站步骤视频上海国际人才网
  • 网站建设服务公司哪家好常州免费企业网站建设
  • 成都品牌网站建设360广告联盟平台
  • 哪些网站可以做任务挣钱深圳企业信用网
  • 佛山智唯网站建设广告软文范例200字
  • 怎么把网站做10万ippython制作网页的基本步骤
  • 如何做财经网站团队拓展总结
  • 打开网上免费网站吗企业网站推广推广阶段
  • 网站图片最大尺寸小程序定制开发多少钱一个
  • 网站 导出链接百度app下载并安装
  • 做网站优化就是发文章吗国内永久免费crm不实名认证
  • 中职网站建设与维护考试题网站建设协议书怎么写
  • 建网站公司公司名称大全青山网站建设
  • 做数学题好的网站什么是网店推广
  • 苏州晶体公司网站建设做微网站要多少钱
  • 公司网站维护价格表2023怎么做网站赚钱的动漫网站
  • 做商城类网站空间怎么买营口网站制作
  • 网站建设 开题报告自己做的微课上传到哪个网站
  • 相亲网站如何做自我介绍数字短链接生成
  • 洛阳网站建设官网大学生网站建设报告
  • 湖南网站制作公司安徽专业网站制作公司
  • ps做网站效果图制作过程成都企业网站建设哪家好
  • 有源码就可以自己做H5网站吗html怎么做网站
  • 阿里云网站空间购买鲜花销售网站建设策划表
  • 携程网站官网dedecms微电影网站模板
  • sever 2008 网站建设营销的本质
  • 网站设计的可行性分析wordpress汉语插件
  • 网站建网站建设网页wordpress必须翻墙吗