美文网首页
一个Kotlin泛型反射问题

一个Kotlin泛型反射问题

作者: Winter_F | 来源:发表于2018-10-11 11:10 被阅读21次

前言

Kotlin成为Android的官方开发语言已经有一段时间了,在开发中遇到的坑大都能找到填坑方案,而今天我要给大家展示一段Kotlin官方给我们挖的大坑。本文需要你对泛型和反射有相对深入的了解,如果不了解也无可奉告了哈哈哈。

有坑自远方来

话说呀,我们有一个很简单的需求,就是为很多个类添加一个 description 方法,这个方法的返回值就是这个类的属性名以及值,例如下面这个类:

class Person(val name: String, val age: Int)

它的 description 方法的返回值应该是这样:

age: 30;name: Benny

这个东西很通用,于是我们决定用扩展方法加反射的方式来输出,于是:

inline fun <reified T : Any> T.description()
        = this::class.memberProperties
        .map {
            "${it.name}: ${it.get(this@description)}"
        }
        .joinToString(separator = ";")

看上去很美是吧,不过呢,这段代码是编译不过的!为什么?it.get(this@description) 这一句看上去很合理,it 是一个属性的反射引用,通过 get 传入调用者 this 来获取当前属性的值,很正常嘛,我们在 Java 中都是这么干的呀。然而,并没有什么用,这里就是报错。

坑亦有理

那我们就来分析下吧,报错的原因也很简单,编译器说 this@description 的类型是 Person,而 it.get 需要的参数类型是 out Person,什么鬼,这里居然还有协变的事儿??

本着息事宁人的态度,类型不匹配我强转下不就得啦:

it.get(this@description as out Person) // 错误的!!

可问题是你老人家仔细瞅瞅,协变类型强转的事儿,真是没听说过..

这就有意思了,我明明用的是 Person 的实例,怎么后面的属性的泛型参数是 out Person?我们看下下面的属性的反射引用类型的定义(也就是 it 的类型):

public interface KProperty1<T, out R> : KProperty<R>, (T) -> R { 

    public fun get(receiver: T): R

    ...
}

T 的类型从哪儿来的呢?当然是从获取反射引用的 KClass 对象来的,也就是 this::class 这个对象了,这个对象难道不应该是 KClass<Person> 吗?No,是 KClass<out Person>!看上去有点儿不可思议,不过仔细想想,这样是否有道理呢?

val person: Any = Person("Benny", 30)

对于这样的情况,person::class 如果返回的是 KClass<Any>,那么在后续的反射访问属性的操作中,我们将什么都得不到,毕竟 Any 什么属性都没有。可这不对呀,person::class 不应该拿到的是对象真实的类型吗?没错,为了照顾到这一点,又不让类型系统出错, Kotlin 选择将 person::class 的类型置为 KClass<out Any> 来解决问题。

其实 Java 也有类似的操作,请看文章:Java中getClass方法的返回值类型

person::class 相当于 Java 的 person.getClass(),尽管这个方法的签名是这样的:

public final Class<?> getClass()

但这个返回值实际上是协变的:

Class<? extends String> c = "".getClass();

不过 Java 毕竟与 Kotlin 不一样,它的反射传参要求非常简单,没有严格的类型限制,只要是 Object 就照单全收:

Method.java

public Object get(Object obj)

总结下,Java 和 Kotlin 对于 person.getClass() (Java 当中)或者 person::class(Kotlin 当中)的处理方式是一致的,返回值都是协变的,但对于反射来说,Java 对参数类型要求几乎没有,而 Kotlin 则非常严格,这样会导致的问题就是 Kotlin 的反射使用起来有些难受。

对于这一点,官方论坛中也有人提出了类似的质疑:Kotlin and Reflection,说 Kotlin 怎么能酱紫搞呢,这么完美的代码居然给我报了个协变的错误,真是不可思议!

Error:(18, 16) Kotlin: Out-projected type 'KMutableProperty1' prohibits the use of 'public abstract fun set(receiver: T, value: R): Unit defined in kotlin.reflect.KMutableProperty1'

反射嘛,本来就是黑科技,类型整那么复杂真的有必要吗?

遇坑填坑

有坑不填,不是好码农啊。

前面抛出这么个大坑,说实在的,不给出解决方案我都不好意思写这篇文章。

类型强转方案

谁说类型强转不行了?谁说的??既然 get 不好使,我们给他来个类似 Java 反射的版本,我们对参数类型不做任何限制:

fun <T, R> KProperty1<T, R>.getUnsafed(receiver: Any): R {
    return get(receiver as T)
}

那么我们的 T.description 扩展就可以稍作修改啦:

inline fun <reified T : Any> T.description()
        = this::class.memberProperties
        .map {
            "${it.name}: ${it.getUnsafed(this@description)}" //注意这里的修改
        }
        .joinToString(separator = ";")

没毛病。

Java 反射方案

Kotlin 反射不能用?不用还不行了么,打不起还躲不起吗,什么世道。。

inline fun <reified T : Any> T.description()
        = this.javaClass.declaredFields
        .map {
            //注意我们访问的 Kotlin 属性对于 Java 来说是 private 的,getter 是 public 的
            it.isAccessible = true
            "${it.name}: ${it.get(this@description)}"
        }
        .joinToString(separator = ";")

我和我的小伙伴们都惊呆了的方案

这。。什么鬼。。

不知道大家怎么看这件事儿,kotlin 对象获取 KClass 实例的方法其实不止 person::class 这样一种,还有一种叫做:this.javaClass.kotlin ,这货的类型是 KClass<Person>,没有 out,repeat,没有 out !

所以我们的代码还可以改成这样:

inline fun <reified T : Any> T.description()
        = this.javaClass.kotlin.memberProperties
        .map {
            "${it.name}: ${it.get(this@description)}"
        }
        .joinToString(separator = ";")

哇靠,Kotlin 官方的开发者,你们太不厚道了。。

我当时就惊呆了,还以为这两种获取 KClass 的方式有什么重大差别呢,结果跟了下源码,是的,this::class 这种写法在调试的时候也是可以强制跳入调用栈的(反编译看字节码也可以),二位居然都是调用了下面的方法来获取 KClass 实例的:

public class Reflection {

    public static KClass getOrCreateKotlinClass(Class javaClass) {
        return factory.getOrCreateKotlinClass(javaClass);
    }

    ...
}

嗯,人家哥们直接用了 Java 的 raw 类型,在 Kotlin 调用处做了一次类型强转,一个强转成了 KClass<out Person>,一个强转成了 KClass<Person> 。。。

我就问一句,你们这么搞,良心不痛吗?

小结

除了上面说到的这个坑,还有一篇别人归纳的Kotlin 反射你敢用吗?可以看一下,看完发现坑还真不少。反正反射这技术不用是不可能不用的了,最后只能说一句,真香。

相关文章

  • 一个Kotlin泛型反射问题

    前言 Kotlin成为Android的官方开发语言已经有一段时间了,在开发中遇到的坑大都能找到填坑方案,而今天我要...

  • 为 Kotlin 中的 Boolean 类扩展一套流式 API

    近日拜读了大佬 mikyou 一篇讲 Kotlin 中泛型的 文章,里面说到 Kotlin 中泛型应用场景的问题时...

  • 泛型

    Kotlin 泛型详解 声明一个泛型类 声明一个泛型方法 泛型约束 List 和 List 是...

  • 泛型

    与Java泛型相同,Kotlin同样提供了泛型支持。对于简单的泛型类、泛型函数的定义,Kotlin 与 Java ...

  • Kotlin---泛型

    Kotlin不变型泛型 Kotlin的不变型泛型和Java一样,通过声明泛型类型来使用泛型类。而该种泛型声明后,则...

  • Kotlin 泛型 VS Java 泛型

    建议先阅读我的上一篇文章 -- Java 泛型 和 Java 泛型一样,Kotlin 泛型也是 Kotlin 语言...

  • 泛型 & 注解 & Log4J日志组件

    掌握的知识 : 基本用法、泛型擦除、泛型类/泛型方法/泛型接口、泛型关键字、反射泛型(案例) 泛型 概述 : 泛型...

  • Kotlin for android学习六:泛型

    前言 kotlin官网和kotlin教程学习教程的笔记。 1. 声明泛型 2. 泛型约束 : 对泛型的类型上限进行...

  • Kotlin泛型-你可能需要知道这些

    本博文主要讲解一些Kotlin泛型的问题,中间会对比穿插Java泛型。 1. 泛型类型参数 1.1 形式 我们使用...

  • Kotlin 泛型

    Kotlin 泛型 1. 泛型类 定义一个泛型类 使用 在继承中 使用 2. 泛型函数 使用 3. 泛型的擦除 无...

网友评论

      本文标题:一个Kotlin泛型反射问题

      本文链接:https://www.haomeiwen.com/subject/rfzlnftx.html