前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Kotlin泛型的型变之路

Kotlin泛型的型变之路

作者头像
用户1907613
发布于 2023-03-01 08:10:06
发布于 2023-03-01 08:10:06
1.3K00
代码可运行
举报
文章被收录于专栏:Android群英传Android群英传
运行总次数:0
代码可运行

之前就写过一篇泛型的文章,但是总觉得写得不够系统,所以最近对泛型又作了些研究,算是对这篇文章的补充了。

kotlin修炼指南7之泛型

泛型,是为了让「类」、「接口」、「方法」具有更加通用的使用范围而诞生的,举个例子,假如我们不使用泛型,那么一个List中可以装得下任何对象,这么做的问题就在于,在使用时,需要对类型进行检查,不然就会转换异常。所以,我们需要将这种检查前置到编译期,这样在编写代码时,就可以安全的使用不同类型,例如List,我们一看就知道是一个String类型的list,不能放其他类型的元素。在Java中,由于历史原因,它并不存在真泛型,Java所有的泛型都是伪泛型,因为Java在编译期,会执行「泛型擦除」,从而导致在Java字节码中,不存在类型信息(但是类型会被保存在其它地方,这个后面讲)。

❝正是由于泛型擦除的问题,你甚至可以通过反射绕开泛型的限制,传递一个非当前泛型限制的对象。 ❞

泛型类型在Java中,通常以一个大写字母来进行标识,我们并不是一定要写「T」来表示泛型,但这是一个约定成俗的表示,类似的约束还有下面这些。

  • 通用泛型类型:T,S,U,V
  • 集合元素泛型类型:E
  • 映射键-值泛型类型:K,V
  • 数值泛型类型:N

要理解Kotlin的泛型,我们最好首先从Java的泛型来学习,毕竟Kotlin的语法糖太多了,Java会更加白话文一点。首先,Java中的泛型具有「不变性」,也就是说,编译器会认为List和List是两个完全不同的类型,当然,不仅仅是List,比如下面这个例子。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
open class A
class B : A()

那么Test<A>和Test<B>是不是一个类型呢?必须不是,虽然A和B是父子关系,但Test<A>和Test<B>就不是了,为什么呢?我们站在编译器的角度来想想,假如它们是同一个类型,那么在Test类中get出来的实例,到底是A还是B呢?所以编译器为了避免这种不确定性,就否定了Test<A>和Test<B>是一种类型的推断。但是这种处理在我们处理泛型业务时,会有很多限制,所以,泛型提供了「型变」来拓展泛型的使用。

协变

协变指的是,当参数具有父子关系时,子类可以作为参数传递,而泛型的上界就是其父类。协变通过上界通配符<? extends 父类型>来实现。实例化时可确定为「父类型的未知类型」,所以它「只能读不能写」,因为编译器不确定到底是哪个子类。例如下面的代码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List<Button> buttons = new ArrayList<Button>();
List<? extends TextView> textViews = buttons;

由于Button是TextView的子类,所以上面的代码可以正确运行。我们来解释下上面的代码。

  • 「?」通配符表示这是一个未知类型
  • 「extends」上界通配符表示这个类型只能是其子类或者本身
  • 这里不仅可以是类,也可以适用于接口

❝上界通配符还有一个特例,那就是「?」,例如List<?>,实际上就是List<? extends Object>的缩写。在Kotlin中,使用的是「*」,即List<*>,实际上就是List<out Any> ❞

简而言之,协变就是——如果A是B的子类,那么Generic<A>就是Generic<? extends B>的子类型。

协变的限制

我们来看下面的代码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List<? extends TextView> textViews = new ArrayList<TextView>();
TextView textView = textViews.get(0);
// Error
textViews.add(textView);

我们来解释下上面的代码,首先,我们定义了一个具有泛型上界的list,然后,我们从list中读取一个元素,这时候,这个元素的返回类型是什么呢?编译器并不知道,但由于泛型上限的存在,所以它一定是TextView及其子类,所以定义为TextView类型,也完全没有问题。接下来我们来实现写入,这时候,就报错了。看上去好像没错啊,add进去的元素是TextView类型,符合泛型上界的定义啊,但是,这个List的类型定义是<? extends TextView>,编译器并不知道具体是什么类型,所以它就认为,最好的办法就是什么都不让加,多做就是错,那不如不做。所以,经过协变之后的泛型,就失去了写入的能力,它只能用于向外提供数据,也就是「数据生产者Producer」。

逆变

逆变指的是,父类可以作为参数传递,但子类必须是其下界。逆变通过下界通配符<? super 子类型>来实现。实例化时可确定为「子类型的未知类型」,所以「只能写不能读」。

❝不能读指的是不能读取为指定的类型,而不是不能调用读的方法。 ❞

例如下面的代码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List<? super Button> buttons = new ArrayList<TextView>();

同样我们来分析下上面的代码。

  • 「?」通配符表示这是一个未知类型
  • 「super」下界通配符表示后面的这个类型,只能是它子类或者本身
  • 这里不仅可以是类,也可以适用于接口

其实这整个就是协变的反向操作。一个是约束上界,另一个是约束下界,所以对比着,其实很好理解。简而言之,逆变就是——如果A是B的子类,那么Generic<B>就是Generic<? super A>的子类型。

逆变的限制

类似的,我们再来看下逆变的限制。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
List<? super Button> buttons = new ArrayList<TextView>();
Button button = new Button(context);
buttons.add(button);
Object object = buttons.get(0);

上面的代码,创建了一个list,它的元素类型的下界是Button,也就是说,这个list里面都是放的Button的父类类型。所以,当我们创建一个Button,并写入的时候,是完全可以的,因为它符合我们定义下界的约束。再来看看读取呢?当我们从list中读取一个元素时,由于编译器只知道它是Button的父类,但是具体是什么类型,它也不知道,所以,编译器不如将它作为Object这个万物基类了。所以说,逆变之后的泛型,失去了读的能力(因为读出来都是Object),所以逆变泛型通常都作为「数据消费者Consumer」。

Kotlin型变

泛型让我们有了可以支持多种类型的能力,型变让我们有了修改泛型的能力,总结来说:

  • 泛型通配符<? extends x>可以使泛型支持协变,但是「只能读不能写」,这里的写,指的是对泛型集合添加元素,如果是remove(int index)或者是clear这种删除,则不受影响。
  • 泛型通配符<? super x>可以使泛型支持逆变,但是「只能写不能读」,这里的读,指的是不能按照泛型类型读,但如果按照Object读出来再强转具体类型,则是可以的。

在学习了Java泛型之后,我们再来看下Kotlin的泛型,这时候你再看,就没那么复杂了,核心就两条。

  • 使用关键字 out 来支持协变,等同于 Java 中的上界通配符 ? extends
  • 使用关键字 in 来支持逆变,等同于 Java 中的下界通配符 ? super

其实在理解了逆变和协变之后,你会发现out和in这两个关键字真的是「言简意赅」,out表示输出,即协变只用于输出数据,in表示输入,即逆变只用于写入数据。Kotlin官网上有个著名的——Consumer in, Producer out,说的就是这个意思。

Kotlin泛型的优化

我们通过这个例子来看下Kotlin对Java泛型的改进。

申明处型变

我们通过下面这个例子来看下Kotlin申明处型变的好处,这是一个生产者与消费者的例子,代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 生产者
class Producer<T> {
    fun produce(): T {}
}
val producer: Producer<out TextView> = Producer<Button>()
val textView: TextView = producer.produce()

首先我们来看生产者,对于T类型的Producer,我们要创建它的子类时,就需要使用协变,即Producer,否则它就只能生产Button类型的数据。所以,在Java中,每次获取数据的时候,都要声明一次协变,所以Kotlin对其进行了优化,可以在申明处进行协变,代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 生产者
class Producer<out T> {
    fun produce(): T {}
}
val producer1: Producer<TextView> = Producer<Button>()
val producer2: Producer<out TextView> = Producer<Button>()

Kotlin约定,当泛型参数T只会用来输出时,可以在申明类的时候,直接使用协变约束,这样在调用的时候,就不用额外使用协变了,当然写了也不会错。与此类似的,消费者也是如此。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 消费者
class Consumer<T> {
    fun consume(t: T) {}
}
val consumer: Consumer<in Button> = Consumer<TextView>()
consumer.consume(Button(context))

我们在使用的时候,也是必须使用逆变,借助Kotlin,同样可以在申明处进行逆变。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 消费者
class Consumer<in T> {
    fun consume(t: T) {}
}
val consumer1: Consumer<Button> = Consumer<TextView>()
val consumer2: Consumer<in Button> = Consumer<TextView>()

这样在调用的时候,就不用额外使用逆变了,当然写了也不会错。

reified

由于在Java会进行泛型擦除,所以编译器无法在运行时知道一个确切的泛型类型,也就是说,我们无法在运行时,判断一个对象是否为一个泛型T的实例,例如下面的代码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if (item instanceof T) {
    System.out.println(item);
}

同样的,在Kotlin里面也是不行的,毕竟一母同胞。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if (item is T) {
    println(item)
}

为了解决这个问题,在Java或者Kotlin中,我们通常会多传入一个Class类型的参数,然后通过Class.isInstance来判断类型是否匹配。但是由于Kotlin支持了内联函数,所以它提供了一个更加方便的方式来处理这种场景,那就是「reified」配合「inline」来实现。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
inline fun <reified T> checkType(item: Any) {
    if (item is T) {
        println(item)
    }
}

不是说好了不能直接对泛型来做类型判断吗,为什么这里却可以呢?这其实就是内联的作用,虽然这里是对T做判断,但实际上在编译时,这里已经被替换成了具体的类型,而不再是泛型T了,所以当然可以使用is来进行类型判断了。

支持协变的List

在Kotlin中,有两种List,一种是可变的,一种是不可变的,即MutableList和List,其中List的申明如下,它已经实现的协变,所以Kotlin中的List只能读而不能写。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface List<out E> : Collection<E>

获取泛型的具体类型

reified

通过reified和inline配合,我们可以在运行时获取泛型的具体类型,这是Kotlin的特性,具体的使用方式,上面的文章已经讲了一个例子。下面我们再看看几个比较典型的例子。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fun reifiedClass() {
    // normal
    val serviceImpl1 = ServiceLoader.load(Service::class.java)
    // reified
    val serviceImpl2 = loadService<Service>()
}

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

interface Service {
    fun work()
}

再看一个简化startActivity的方式。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
inline fun <reified T : Activity> Activity.startActivity(bundle: Bundle? = null) {
    val intent = Intent(this, T::class.java)
    bundle?.let {
        intent.putExtras(it)
    }
    startActivity(intent)
}

startActivity<SampleActivity>()

传入指定Class

通过传入具体的Class类型,我们也可以在运行时获取泛型类型,这个方法是Java和Kotlin都支持的,这个在前面的文章中也提到了。

匿名内部类

匿名内部类会在运行时实例化,这个时候,就可以拿到泛型的具体类型了,示例代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
open class Test<T>

fun main() {
    val innerClass = object : Test<String>() {}
    val genericType: Type? = innerClass.javaClass.genericSuperclass
    if (genericType is ParameterizedType) {
        val type = genericType.actualTypeArguments[0]
        // class java.lang.String
    }
}

Class类提供了一个方法getGenericSuperclass ,通过它可以获取到带泛型信息的父类Type(Java的Class文件会保留继承的父类或者接口的泛型信息)。通过对获取的genericType来判断是否实现ParameterizedType接口,是说明支持泛型,从而获取出对应的泛型列表(因为泛型可能有多个)。这个方式是一个很巧妙的获取泛型类型的方法,在Gson中,就是通过它来获取类型的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
val content = Gson().toJson("xxx", object : TypeToken<String>() {}.type)

在使用Gson时,我们需要创建一个继承自TypeToken的匿名内部类, 并实例化泛型参数TypeToken,这样我们就可以通过getGenericSuperclass来获取父类的Type,也就是上面例子中的TypeToken了。

反射

反射自然是可以拿到运行时的具体类型了,代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
open class Test<T>

class NewTest : Test<String>() {
    private val genericType: Type? = javaClass.genericSuperclass
    fun test() {
        if (genericType is ParameterizedType) {
            val type = genericType.actualTypeArguments[0]
            // class java.lang.String
        }
    }
}

通过反射来获取实际类型,是很大开源库中都在使用的方式,例如Retrofit,它在内部就是通过method.genericReturnType来获取泛型的返回类型,通过method.genericParameterTypes来获取泛型的参数类型。不过这里大家要好奇了,在文章的一开始,我们就说了,Java的伪泛型,会在编译时进行泛型擦除,那么反射又是怎么拿到这些泛型信息的呢?其实,编译器还是留了一手,申明处的泛型信息,实际上会以Signature的形式,保存到Class文件的Constant pool中,这样通过反射,就可以拿到具体的泛型类型了。

❝要注意的是,这里能保留的是申明处的泛型,如果是调用处的泛型,例如方法的传参,这种就不会被保存了。 ❞

PESC

PESC是泛型型变中的一个指导性原则,意为「Producer Extend Consumer Super」,当然在Kotlin中,这句话要改为「Consumer in, Producer out」。这个原则是从集合的角度出发的,其目的是为了实现集合的多态。

  • 如果只是从集合中读取数据,那么它就是个生产者,可以使用extend
  • 如果只是往集合中增加数据,那么它就是个消费者,可以使用super
  • 如果往集合中既存又取,那么你不应该用extend或者super

还是举一个例子来说明,我们可以认为Kotlin是Java的子类,但是List和List却是两个无关的类,它们之间没有继承关系,而使用List<? extends Java>后,相当于List和List之间也有了继承关系,从而可以读取List中不同类型的数据,List就是通过这种方式来实现了集合的多态。

协变和逆变的使用场景

我们来看这样一段代码,我们创建了一个copyAll的方法,传入to和from两个列表,代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fun <T> copyAll(to: MutableList<T>, from: MutableList<T>) {
    to.addAll(from)
}

fun main() {
    val numberList = mutableListOf<Number>() // to
    val intList = mutableListOf(1, 2, 3, 4) // from

    copyAll(numberList, intList)// Error
}

但是这段代码是不能编译通过的,原因在于to是一个List,而from是一个List,所以类型转换异常,不能编译。但实际上,我们知道Int是可以转换为Number的,但是编译器不知道,所以它只能报错,编译器需要的,就是我们告诉它,这样做是安全的,得到了我们的保证,编译器才能执行编译。这个保证是从两方面来说的,首先我们来看from。from是一个List,完全可以当做List,所以,要保证「from取出来的元素可以转为Number类型,而且from不能再有其它写入」,否则你向一个List中插入了一条Number类型的元素,那就不乱套了。所以,我们可以对from做协变,让它只读不写,代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fun <T> copyAll(to: MutableList<T>, from: MutableList<out T>) {
    to.addAll(from)
}

这样就表示from,只接受T或者T的子类型,也就是说,from只能是Number或者Number的子类型,而此时from是Int类型,所以编译通过了。上面是从from的角度做的保证,那么从to方面呢?对于to来说,我们需要保证「to只能写入,而不能读取」。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fun <T> copyAll(to: MutableList<in T>, from: MutableList<T>) {
    to.addAll(from)
}

这样就表示to,只接受T或者T的父类型,也就是说,to只能是Int或者Int的父类型,而此时to是Number类型,所以编译通过了。

❝另外,我们将from的签名改为List,也是可以编译的,其原因就是Kotlin中的List已经支持协变了。 ❞

相信大家通过这个例子,大概能理解协变和逆变的使用方式了。那么我们在实际的代码中,要在哪些场景使用协变和逆变呢?通常来说,泛型参数协变后则表示——「这个参数在当前类中,只能作为函数的返回值,或者是只读属性」。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
abstract class TestOut<out T> {
    abstract val num: T// 只读属性
    abstract fun getItem(): T// 函数的返回值

    abstract var num1 : T// Error 用于可变属性
    abstract fun addItem(t: T)// Error 用于函数的参数
}

而逆变,表示这个参数「只能作为函数的参数,或者修饰可变属性」。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
abstract class TestIn<in T> {
    abstract val num: T//Error 只读属性
    abstract fun getItem(): T//Error 函数的返回值

    abstract fun addItem(t: T)// 用于函数的参数
}

本文原创公众号:群英传,授权转载请联系微信,授权后,请在原创发表24小时后转载。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-12-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 群英传 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
kotlin入门之泛型
为什么List<TextView> textViews=buttons;会报错呢?这是因为Java的泛型本身 具有不可变性。Java里面会认为List<TextView> 和List<Button>类型不一致, 也就是说,子类的泛型(List<Button>)不属于泛型(List<TextView> )的子类。
易帜
2022/02/09
1.3K0
kotlin修炼指南7之泛型
Kotlin在Java的基础上,同样对泛型语法进行了拓展,所以很多Kotlin开发者,看着源码中的一堆in、out和*,感觉非常不知所措。其实,只要了解了Java泛型,那么Kotlin泛型就迎刃而解了。
用户1907613
2022/05/17
6860
Kotlin | 9. 泛型
本章内容包括: 声明泛型函数和类 类型擦除和实化类型参数 声明点变型和使用点变型 9.1 泛型类型参数 // 如果要创建一个空的列表,必须显示的指定,有值的话可以被推导出来 val readers: MutableList<String> = mutableListOf() val readers1 = mutableListOf<String>() val reader2 = listOf("jingbin", "jinbeen")
Jingbin
2021/03/02
1.9K0
一文了解 Java/Kotlin 中的泛型
在 Java/Kotlin 中,子类对象是可以赋值给一个父类类型的,但是父类对象不可以赋值给子类类型,例如:
GeeJoe
2022/03/25
1.2K1
一文了解 Java/Kotlin 中的泛型
【Kotlin】泛型 ③ ( 泛型 out 协变 | 泛型 in 逆变 | 泛型 invariant 不变 | 泛型逆变协变代码示例 | 使用 reified 关键字检查泛型参数类型 )
本章总结 : 使用了 泛型 out 协变 和 泛型 in 逆变 极大的提高了程序的扩展性 ;
韩曙亮
2023/03/30
2K0
【Kotlin】泛型 ③ ( 泛型 out 协变 | 泛型 in 逆变 | 泛型 invariant 不变 | 泛型逆变协变代码示例 | 使用 reified 关键字检查泛型参数类型 )
Effective Kotlin 译文:Chapter3-Item24-泛型的型变
以下文章翻译自《Effective Kotlin: Best practices》 中的 *Chapter 3 - Item24 -
GeeJoe
2022/03/26
7740
Effective Kotlin 译文:Chapter3-Item24-泛型的型变
第8章 泛型第8章 泛型
通常情况的类和函数,我们只需要使用具体的类型即可:要么是基本类型,要么是自定义的类。但是在集合类的场景下,我们通常需要编写可以应用于多种类型的代码,我们最简单原始的做法是,针对每一种类型,写一套刻板的代码。这样做,代码复用率会很低,抽象也没有做好。我们能不能把“类型”也抽象成参数呢?是的,当然可以。
一个会写诗的程序员
2018/08/17
2K0
第8章 泛型第8章 泛型
Kotlin学习笔记(七)-泛型
这节我们说下Kotlin的泛型。首先默认大家对Java泛型有个基本的认识,如果 不熟悉Java的泛型,可以阅读文章,或是看下Java《Java核心技术卷一基础知识第10版》中关于泛型章节的知识,讲述的也很详细。其实Kotlin的泛型和Java很相似。他们都是伪泛型,所谓伪泛型就是我们们是无法获取到泛型的具体的类型的。以为Java存在类型擦除和转换。本篇还是和反射一样,从实际代码编写角度,说下Kotlin的泛型
g小志
2019/12/19
6840
Kotlin 范型之协变、逆变
如果 Dog 是 Animal 的子类,但 List<Dog> 并不是 List<Animal> 的子类。 下面的代码会在编译时报错:
fengzhizi715
2019/06/24
1.4K0
Kotlin 范型之协变、逆变
【Kotlin】泛型总结 ★ ( 泛型类 | 泛型参数 | 泛型函数 | 多泛型参数 | 泛型类型约束 | 可变参数结合泛型 | out 协变 | in 逆变 | reified 检查泛型参数类型 )
将 泛型参数 T 放在 尖括号 <T> 中 , 该泛型参数放在 类名后 , 主构造函数之前 , 该泛型参数 T 是 类型占位符 ,
韩曙亮
2023/03/30
4.5K0
【Kotlin】泛型总结 ★ ( 泛型类 | 泛型参数 | 泛型函数 | 多泛型参数 | 泛型类型约束 | 可变参数结合泛型 | out 协变 | in 逆变 | reified 检查泛型参数类型 )
From Java To Kotlin 2:Kotlin 类型系统与泛型终于懂了
上期主要分享了 From Java To Kotlin 1 :空安全、扩展、函数、Lambda。
Seachal
2023/06/06
5310
From Java To Kotlin 2:Kotlin 类型系统与泛型终于懂了
《Kotlin 极简教程 》第6章 泛型
通常情况的类和函数,我们只需要使用具体的类型即可:要么是基本类型,要么是自定义的类。
一个会写诗的程序员
2018/08/17
1.8K0
Kotlin入门潜修之类和对象篇—泛型及其原理
如果我们了解java中的泛型,那么本篇文章提到的kotlin泛型我们也不会陌生。但是如果之前没有接触过泛型或者没有真正理解泛型,本篇文章理解起来可能有些困难,不过我会尽量阐述的通俗易懂。
Android架构
2019/06/24
9920
Kotlin 泛型详解
0. 引子 Kotlin 100% 与 Java 兼容,所以抛开语言表面上面的种种特质之外,背后的语言逻辑或者说“灵魂”与 Java 总是想通的。本文只涉及 Kotlin Jvm,Kotlin Js、Kotlin Native 的具体实现可能有差异。 最近一段时间在网上发了一套 Kotlin 的入门视频,涵盖了基础语法、面向对象、高阶函数、DSL、协程等比较有特色的知识点,不过有朋友提出了疑问:这门课为什么不专门讲讲泛型、反射和注解呢? 我最早听到这个问题的时候,反应比较懵逼,因为我居然没有感觉到 Kotl
腾讯Bugly
2023/04/02
1.3K0
Kotlin 泛型详解
Kotlin---泛型
Kotlin的不变型泛型和Java一样,通过声明泛型类型来使用泛型类。而该种泛型声明后,则无法使用父类方法与属性。在编译时候,会将泛型擦除。
None_Ling
2019/03/15
9900
带着问题高效学Android:关于Java与Kotlin泛型你应该知道的知识点
image.png 前言 带着问题学习可以让我们在学习的过程中更加有目的性与条理。 例如在读源码的过程中,我们如果从头开始读,往往千头万绪,抓不住要领。 而如果在开始读之前先带着几个问题,则可以让我们
Android技术干货分享
2020/11/17
1.2K0
带着问题高效学Android:关于Java与Kotlin泛型你应该知道的知识点
Kotlin基础之泛型
泛型 与Java一样,Koltin的类也有类型参数。例如: class Box<T>(t: T){ var value = t } 常规来说,创建这样的类,需要提供具体的类型。例如: val box: Box<Int> = Box<Int>(1) 当类型可以从构造参数或其他上下文中推断出时,可以忽略类型参数。上面的代码可以简化为: val box = Box(1) 型变 Java类型系统中最复杂的其中一个部分就是通配符类型(Java泛型FAQ)。而Kotlin没有任何的通配符类型,它使用声明处变型和
xiangzhihong
2018/02/08
1K0
【JavaSE】Java进阶知识一(泛型详解,包括泛型方法,协变,逆变,擦除机制)
一般用<T>作为占位符 ,表示当前类是一个泛型类。Java中的泛型参数只能是引用类型,不能是基本类型,这与Java的泛型擦出机制有关。
小皮侠
2024/04/08
2400
【JavaSE】Java进阶知识一(泛型详解,包括泛型方法,协变,逆变,擦除机制)
Android面试题之Java 泛型和Kotlin泛型
比如没有ArrayList<int>,只有ArrayList<Integer>,当泛型擦除后,ArrayList的原始类中的类型变量T替换成了Object,但Object不能存放基本数据类型
AntDream
2024/06/13
1120
Android面试题之Java 泛型和Kotlin泛型
深入理解 Java 泛型
泛型最开始是在 C++ 中提出的,实现为模块方法和模板类,主要为了解决与类型相关的算法的重用问题,比如对栈的描述:
BUG弄潮儿
2021/10/08
6190
推荐阅读
相关推荐
kotlin入门之泛型
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档