Kotlin 调用Java写的方法,参数Class<T&g

作者: IMSk | 来源:发表于2019-01-03 13:11 被阅读6次

    看调用问题

    先来看几段代码

    1. Java 写的 loadResource 方法,参数需要传入 Class<T>
        public <T> T loadResource(String resource, CCMMPParse<T> parse, Class<T> clazz){
            HJKitConfigAssembledResourceModel model =  new HJKitConfigAssembledResourceModel();
            model.setContent(resource);
            T result = parse.parse(model);
            if (clazz.isInstance(result)){
                return clazz.cast(result);
            }   
            return  null;
        }
    
    1. Kotlin 写的 Parse 实现
    class EmoticonMMPConfigParser : CCMMPParse<Boolean> {
    
        override fun parse(model: BaseAssembledResourceModel?): Boolean {
            return false
        }
    }
    
    1. kotlin 尝试调用 Java 写的 loadResource 方法
    val result = CCMMPManager.loadResource("true", EmoticonMMPConfigParser(), Boolean::class.java)
    

    此时大家,大胆的猜测一下 result 运行结果是什么?

     result = null 
    

    是的你没看错,运行结果并不是我们 Parse 里面返回的 false,而是返回了 null。 为什么啊???

    单独跟踪一下,会发现问题出在这里:

    if (clazz.isInstance(result)){
        //这里在判断类型的时候,出错了的,并没有走到这个case中
                return clazz.cast(result);
            }   
    //走到这里了的,所以返回 null
            return  null;
    

    那问题来了的,为什么 clazz.isInstance(result) 判断的结果是 false 呢,我们调用的是时候明明传入的Class<T>是Kotlin的 Boolean::class.java, 解析的 parse 也是用 Kotlin 写的方法,返回的也是 Boolean啊, 输入输出的应该是同一个类型,为什么判断结果却是告诉我们不是一个类型呢???

    一开始怀疑是,kotlin 和 Java 相互调用的时候,之间发生了什么未知的坑,好那么试试纯用kotlin 写一代吗验证一下。

        init {
            val b = getB()
            Log.d("tag",".isInstance: ${ Boolean::class.java.isInstance(b)}")
    
        }
    
        fun getB():Boolean {
            return true
        }
    

    你猜结果如何???

    isInstance: false
    

    Why ???

    先说如何解决这个问题: How to reference Boolean java class from kotlin?

    即 换成 javaObjectType.isInstance() :

             Log.d("tag","isInstance: ${ Boolean::class.java.isInstance(b)}")
            Log.d("tag","isInstance: ${ Boolean::class.javaObjectType.isInstance(b)}")
    

    你猜结果如何???

    isInstance: false
    isInstance: true
    

    parse 也换换?

    val result = CCMMPManager.loadResource("true", EmoticonMMPConfigParser(), Boolean::class.javaObjectType)
    

    result 运行结果是什么?

     result = false 
    

    Good.

    好,现在来分析一下,问题到底出在哪里?

    首先来罗列一下代码

    Boolean::class.java 指向的是 kotlin 标准库里面定义的Boolean.kt类:

    Boolean.kt

    Boolean::class.javaObjectType 指向的是 kotlin 标准库中 JvmClassMappingKt 中的一个方法:

    javaObjectType

    返回的是JavaLangBoolean::class.java, 即JDK中的 Boolean.java 类:

    Boolean.java

    而我们用 kotlin 写的parse,在运行时返回的 false是JVM给我们的基本类型(你可以理解是拆箱之后的数值),也就是对应的是 JavaLangBoolean::class.java, 而非我们以为的kotlin中写的 Boolean.kt

    延伸 '编译阶段的语法糖' 拆箱和装箱

    类似的其他的kotlin基本类型,也有这样的问题:
    A confusion about Int::class.java.isInstance() in Kotlin

     The issue is that Int::class refers to the primitive int type, but the value is the boxed Integer type.
    
    A workaround is to use Integer::class instead of Int::class, as this will refer to the boxed Java type.
    

    经常听到有人说: 所有的kotlin基本类型都是包装类型, 但这句话其实是错误的。。。在编译成字节码的时候,会优化转换成符合 JVM规范的字节码,比如

        val b:Int = 5
        val b1:Int? = b
        val b11:Int? = b
        println(b1 === b11) // 注意这里打印的是 'true'
     
        val a: Int = 10000
        val a1: Int? = a
        val a11: Int? = a
        println (a1 === a11 ) //注意这里打印的是 'false'
    
    

    原因是第一段代码其实都是int, 没有装箱的操作, 所以是true,而第二段10000超过了JVM的优化策略范围,会自动装箱变成了Integer,所以是false。借用别人的一句话: 长话短说,JVM把[-128,127]的所有int数字全部缓存了,任何指向这个范围的对象,都不可能被另外"创建",又何谈“装箱” 。看一下装箱Integer. valueOf的源码即可:

     public static Integer valueOf(int i) {
            if (i >= IntegerCache.low && i <= IntegerCache.high)
                return IntegerCache.cache[i + (-IntegerCache.low)];
            return new Integer(i);
        }
    

    你可以看一下这篇文章:《java-integer-cache》

    写在最后

    我对JVM,其实没有研究过,虽然以前上学的时候学过的,但可惜的是早就忘在脑后了的,所以如果哪里写的不对的,请见谅并希望指出来告诉我。

    写这篇文章,只是为了记录这个神坑。并且加深了之前自己的理解(也许是错误的),一切基于JVM的语言,最终都逃不过JVM的规范,换言之,你也可以创造一门新的JVM语言。

    也是我在之前写过一篇文章中提到的《一张图,三分钟,掌握 Swift & Kotlin》

    聊到这里,不知道大家有没有同样的感受。我们的圈子里面有两个关键字:
    语言: 到目前为止,有那么多语言,每个语言都在不同平台上展示着自己的优势。
    工程师: 擅长不同的语言,在不同平台上去编码。
    所以,楼主坚信语言仅仅是工程师的一把工具而已,用来在不同的平台上砍瓜切菜。楼主个人认为,你我都有学不完的语言,也很难做到拍着胸脯说自己完全掌握了什么什么语言,但作为拿工具的我们,可以做到的是,掌握拿工具的姿势,在不同平台环境下只是选择更加合适的工具,去拿刀(Swift)还是拿斧头(Kotlin)而已。语言是用来驾驭的,千万不要被语言牵着鼻子走。(所以前言里面说的 这篇文章适合已经学过其中一门语言,不仅仅局限与Swift & Kotlin)

    相关文章

      网友评论

        本文标题:Kotlin 调用Java写的方法,参数Class<T&g

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