深入解析Kotlin 泛型,最新Android架构师成长路线

本文探讨了Kotlin中的泛型、类型擦除、reified和inline关键字,以及与Java在处理类型和泛型方面的异同,特别提到Gson在反序列化时的限制和Kotlin如何通过reified和inline解决。此外,文章还分析了Java和Kotlin的型变概念,包括协变、逆变和通配符的使用。
摘要由CSDN通过智能技术生成

}

编译后的字节码:

public static testGenerics()Ljava/lang/Object;

L0

LINENUMBER 13 L0

ACONST_NULL

ASTORE 0

L1

LINENUMBER 14 L1

ALOAD 0

ARETURN

L2

LOCALVARIABLE t Ljava/lang/Object; L1 L2 0

// signature TT;

// declaration: T

MAXSTACK = 1

MAXLOCALS = 1

我们看到,编译之后 T 变成了 Object,简单来说就相当于:

public static Object testGenerics(){

Object t = null;

return t;

}

这就是传说中的类型擦除了。而 Kotlin 在 JVM 之上,编译之后也是字节码,机制与 Java 是一样的。也正是因为这个原因,我们在使用 Gson 反序列化对象的时候除了制定泛型参数,还需要传入一个 class :

public T fromJson(String json, Class classOfT) throws JsonSyntaxException {

}

显然 Gson 没有办法根据 T 直接去反序列化。

下面我们说一点儿不太一样的。在 Kotlin 当中有一个关键字叫做 reified,还有一个叫做 inline,后者可以将函数定义为内联函数,前者可以将内联函数的泛型参数当做真实类型使用,我们先来看例子:

inline fun Gson.fromJson(json: String): T{

return fromJson(json, T::class.java)

}

这是一个 Gson 的扩展方法,有了这个之后我们就无须在 Kotlin 当中显式的传入一个 class 对象就可以直接反序列化 json 了。

这个会让人感觉到有点儿迷惑,实际上由于是内联的方法调用,T 的类型在编译时就可以确定的:

class Person(var id: Int, var name: String)

fun test(){

val person: Person = Gson().fromJson(“”“{“id”: 0, “name”: “Jack” }”“”)

}

反编译之后:

public static final void test() {

Gson r e c e i v e r receiver receiveriv = new Gson();

String json$iv = “{“id”: 0, “name”: “Jack” }”;

Person person = (Person) r e c e i v e r receiver receiveriv.fromJson(json$iv, Person.class);

}

注意,在这里,inline 是必须的。

2. 型变


2.1 Java 的型变

如果 Parent 是 Child 的父类,那么 List<Parent>List<Child> 的关系是什么呢?对于 Java 来说,没有关系。

也就是说下面的代码是无法编译的:

List numbers = new ArrayList(); //ERROR!

不过 numbers 中可以添加 Number 类型的对象,所以我添加个 Integer 可以不呢?可以的:

numbers.add(1);

那么我要想添加一堆 Integer 呢?用 addAll 是吧?注意看下 addAll 的签名:

boolean addAll(Collection<? extends E> c);

这个泛型参数又是什么鬼?如果我把这个签名写成下面这样:

boolean addAll(Collection c);

我想要在 numbers 当中 addAll 一个 ArrayList<Integer>,那就不可能了,因为我们说过,ArrayList<Number>ArrayList<Integer> 是两个不同的类型,毛关系都没有。

? extends E 其实就是使用点协变,允许传入的参数可以是泛型参数类型为 Number 子类的任意类型。

当然,也有 ? super E 的用法,这表示元素类型为 E 及其父类,这个通常也叫作逆变。

2.2 Kotlin 的型变

型变包括协变、逆变、不变三种。

下面我们看看 Kotlin 是怎么支持这个特性的。Kotlin 支持声明点型变,我们直接看 Collection 接口的定义:

public interface Collection : Iterable {

}

out E 就是型变的定义,表明 Collection 的元素类型是协变的,即 Collection<Number> 也是 Collection<Int> 的父类。

而对于 MutableList 来说,它的元素类型就是不变的:

public interface MutableCollection : Collection, MutableIterable {

public fun addAll(elements: Collection): Boolean

}

换言之,MutableCollection<Number>

MutableCollection<Int> 没有什么关系。

那么请注意看 addAll 的声明,参数是 Collection<E>,而 Collection 是协变的,所以传入的参数可以是任意 E 或者其子类的集合。

逆变的写法也简单一些: Collection<in E>

那么 Kotlin 是否支持使用点型变呢?当然支持。

我们刚才说 MutableCollection 是不变的,那么如果下面的参数改成这样:

public fun addAll(elements: MutableCollection): Boolean

结果就是,当 E 为 Number 时,addAll 无法接类受似 ArrayList<Int> 的参数。而为了接受这样的参数,我们可以修改一下签名:

public fun addAll(elements: MutableCollection): Boolean

这其实就与 Java 的型变完全一致了。

2.3 @UnsafeVariance

型变是一个让人费解的话题,很多人接触这东西的时候一开始都会比较晕,我们来看看下面的例子:

class MyCollection{

fun add(t: T){ // ERROR!

}

}

为什么会报错呢?因为 T 是协变的,所以外部传入的参数类型如果是 T 的话,会出问题,不信你看:

var myList: MyCollection = MyCollection()

myList.add(3.0)

上面的代码毫无疑问可以编译,但运行时就会比较尴尬,因为 MyCollection<Int> 希望接受的是 Int,没想到来了一个 Double。。

对于协变的类型,通常我们是不允许将泛型类型作为传入参数的类型的,或者说,对于协变类型,我们通常是不允许其涉及泛型参数的部分被改变的。这也很容易解释为什么 MutableCollection 是不变的,而 Collection 是协变的,因为在 Kotlin 当中,前者是可被修改的,后者是不可被修改的。

逆变的情形正好相反,即不可以将泛型参数作为方法的返回值。

但实际上有些情况下,我们不得已需要在协变的情况下使用泛型参数类型作为方法参数的类型:

public interface Collection : Iterable {

public operator fun contains(element: @UnsafeVariance E): Boolean

}

比如这种情形,为了让编译器放过一马,我们就可以用 @UnsafeVariance 来告诉编译器:“我知道我在干啥,保证不会出错,你不用担心”。

最后再给大家提一个点,现在你们知道为什么 in 表示逆变,out 表示协变了吗?

3. 通配符


在Java 中,当我们不知道泛型具体类型的时候可以用 ?来代替具体的类型来使用,比如下面的写法:

Class<?> cls = numbers.getClass();

Kotlin 也可以有类似的写法:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

更多学习和讨论,欢迎加入我们的知识星球!

点击这里加入我们吧!

群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

711096174006)]
[外链图片转存中…(img-pW91DF3b-1711096174007)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-ZgevAmmc-1711096174007)]

[外链图片转存中…(img-Vsg3MBuE-1711096174008)]

更多学习和讨论,欢迎加入我们的知识星球!

点击这里加入我们吧!

群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

这里有2000+小伙伴,让你的学习不寂寞~·

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值