Unsafe 创建实例
在java中 创建一个对象 其实主要就是3种方法
通过new 关键字来创建 这种是最常见的
通过反射构造方法来创建对象 这种也不少见。很多框架中都有使用。
Unsafe类来创建实例 ,这种情况非常少见。
这里先讲讲Unsafe创建实例的方法。
我们首先创建一个Pserson类
public class Person { public Person() { System.out.println("Person cons"); } }
再创建一个User类
public class User extends Person { public String getName() { return name; } public void setName(String name) { this.name = name; } private String name; public User() { System.out.println("User cons"); } }
注意看 这2个类的特点是 都有无参的构造方法
然后我们看看 怎么通过Unsafe 来操作 生成一个User的对象
try { Class klass = Unsafe.class; Field field = null; field = klass.getDeclaredField("theUnsafe"); field.setAccessible(true); Unsafe unsafe = (Unsafe) field.get(null); User user=(User) unsafe.allocateInstance(User.class); System.out.println(user.getName()); user.setName("wuyue"); System.out.println(user.getName()); System.out.println("分割线"); User user1=new User(); } catch (NoSuchFieldException | IllegalAccessException | InstantiationException e) { e.printStackTrace(); }
然后我们看下结果:
可以看出来 通过Unsafe构造出来的对象 在使用上和我们用new 关键字 构造出来的对象是一样的。但是有个很大的不同是:UnSafe 创建出来的对象 是没有使用构造方法的,也就是说 构造函数没有走。
大家可以看下日志:
分割线之前 我们用Unsafe的方法创建出来的对象 是没有构造函数的日志的。(很多java boy 看到这里是不是不敢相信?其实Unsafe 这个类下面很多方法 都是这么没有节操。)
这里我们不深究背后的原理(其实是我不知道),大家只要记住这个结论即可
kotlin的非空与不非空
看下这2个函数:
fun one(msg:String){ } fun two(msg:String?){ }
唯一的区别就是函数参数 这里 一个是可空的 一个是不可空的。我们反编译看一下:
所以你看 这里还是挺危险的。如果你这个 one 的函数 是给java的人调用,而他恰好又传了一个null 进来 那么走到one函数里面 就会抛异常了。
例如我们在java代码里面 调用这个one函数 参数写成 第一个小节里的 user.getName() 那就必然会报错抛异常了
kotlin 的 构造函数
这里实际上是非常坑爹的一点。大家都知道 纯java的代码 如果你不写任何构造函数,编译器也会帮你生成一个无参的构造函数。
但是在kotlin中,这个特性被抹掉了
举例说明:
我们首先定义一个kotlin的普通类
class KUser(var name:String)
然后在java的代码中 调用他 试试看
你会发现这样是行不通的, 这种定义类的方式,不会帮你生成默认的无参构造函数。
我们再试试data class 看看行不行
data class KUser2(var name: String)
依旧也是不行的,看来 kotlin中 不管是class 还是data class 都不会自动帮你生成 无参的构造函数。
有没有方法可以避免这种情况。当然是可以的。
比如我们把定义的属性 手动给他指定一个默认值
class KUser(var name:String="") data class KUser2(var name: String="")
或者手动指定一个无参的构造函数
data class KUser2(var name:String ,var age:Int){ constructor():this("123",0) }
总之只要你指定了全部属性 都有默认值 ,那么就肯定会有无参的构造函数的
当kotlin 碰上序列化
有了上面的基础知识,我们再来看下面的 例子就清晰的多了。
首先看下kotlin官网中的 原文:
翻译成人话就是:kotlin 要想和一些序列化或者反序列化框架 工作正常,最好还是提供一下无参构造函数
我们就以gson为例:
gson 将一个json 字符串 序列化为一个javabean的时候 其实遵循的主要逻辑如下:
这个class 有没有 无参构造函数,如果有 就使用 这个无参构造函数 来构造出对象 如果没有 去第二步
第二步 其实就是用 unsafe 来构造出一个 对象。
来看第一个例子:
data class KUser(var name:String,var age:Int) fun main(){ val gson= Gson() val person=gson.fromJson<KUser>(""" {"age":"12"} """,KUser::class.java) println(person.name) }
看下输出结果:
有人觉得奇怪,这里我们json字符串没有 name相关的信息, data class 中 name又定义成了不可空 为啥 没报错
反而输出的结果是null呢?
我们修改一下代码:
data class KUser(var name:String="123",var age:Int)
再运行,看结果:
我擦 我都设置了默认值为123了 为啥还是null?
我不服,我要再改一下,这次我把age的默认值也加上:
data class KUser(var name:String="123",var age:Int)
这下终于正常了。
到这里 我来解释下 为啥会有上述的情况
kotlin中 假设你有n个属性值,那你必须把这n个属性的默认值 都设置了默认值,才会生成默认的无参构造函数,少一个都不会生成无参构造函数
所以 前面的例子,第一个结果和第二个结果 就很好解释了,因为没有无参构造函数,所以gson的反序列化 走了unsafe 直接构造出了对象,绕过了 kotlin的 非空判定,所以不报错,输出null
最后一个结果是因为 我把2个属性 name和age 都设置了默认值,所以有了无参构造函数 从而一切正常。
kotlin 调用 java函数 时 要注意的点
val gson= Gson() val person=gson.fromJson("",KUser::class.java) //不报错 正常展示null println(person) val person2:KUser?=gson.fromJson("",KUser::class.java) // 声明为可空 自然不报错 println(person2) //会崩溃 因为 声明的是不可空,但实际返回了空 所以报错 val person3:KUser=gson.fromJson("",KUser::class.java) println(person3)
主要看下为啥报错,看下反编译:
作者:vivo祁同伟
链接:https://juejin.cn/post/6908986604270927885关注我获取更多知识或者投稿