阅读 1584

深入理解Kotlin无参构造函数

Unsafe 创建实例

在java中 创建一个对象 其实主要就是3种方法

    1. 通过new 关键字来创建 这种是最常见的
    1. 通过反射构造方法来创建对象 这种也不少见。很多框架中都有使用。
    1. 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的时候 其实遵循的主要逻辑如下:

  1. 这个class 有没有 无参构造函数,如果有 就使用 这个无参构造函数 来构造出对象 如果没有 去第二步
  2. 第二步 其实就是用 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)
复制代码

主要看下为啥报错,看下反编译:

文章分类
Android
文章标签