kotlin中data class 与object实现单例原理讲解

1,603 阅读5分钟

data class 与 object 关键字原理讲解

我们日常开发中肯定离不开javaBean类的,一般来讲,都需要定义里面属性,并且有可能还需要实现hashCode和equals函数。比如下面这个例子。

class Human {
    private String name;
    private int age;
    private int sex;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getSex() {
        return sex;
    }

    public void setSex(int sex) {
        this.sex = sex;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Human human = (Human) o;
        return age == human.age && sex == human.sex && Objects.equals(name, human.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, sex);
    }

    @Override
    public String toString() {
        return "Human{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex=" + sex +
                '}';
    }
}

这...无法理解啊!!!为什么是这么一个简单的javabean实现起来要这么多代码?难道就没有更加简单的方式去实现了吗?在java8版本中,确实没有更好的做法了,只能自己乖乖去实现getter/setter等各种方法。但是在kotlin中,kotlin为我们提供了很方便的语法糖。直接使用data class可以。

data class

怎么用kotlin的data 实现和上面一样的功能呢?代码如下

 data class Human(var name:String?,var age:Int?,var sex:Int?)

就一行代码直接搞定。是不是很神奇,觉得很神奇就对了。那么这么神奇的功能是怎么实现的呢?这就需要我们通过反编译来看一下它的java代码了。

 public final class HumanKt {
    @Nullable
    private String name;
    @Nullable
    private Integer age;
    @Nullable
    private Integer sex;
 ​
    @Nullable
    public final String getName() {
       return this.name;
    }
 ​
    public final void setName(@Nullable String var1) {
       this.name = var1;
    }
 ​
    @Nullable
    public final Integer getAge() {
       return this.age;
    }
 ​
    public final void setAge(@Nullable Integer var1) {
       this.age = var1;
    }
 ​
    @Nullable
    public final Integer getSex() {
       return this.sex;
    }
 ​
    public final void setSex(@Nullable Integer var1) {
       this.sex = var1;
    }
 ​
    public HumanKt(@Nullable String name, @Nullable Integer age, @Nullable Integer sex) {
       this.name = name;
       this.age = age;
       this.sex = sex;
    }
 ​
    @Nullable
    public final String component1() {
       return this.name;
    }
 ​
    @Nullable
    public final Integer component2() {
       return this.age;
    }
 ​
    @Nullable
    public final Integer component3() {
       return this.sex;
    }
 ​
    @NotNull
    public final HumanKt copy(@Nullable String name, @Nullable Integer age, @Nullable Integer sex) {
       return new HumanKt(name, age, sex);
    }
 ​
    // $FF: synthetic method
    public static HumanKt copy$default(HumanKt var0, String var1, Integer var2, Integer var3, int var4, Object var5) {
       if ((var4 & 1) != 0) {
          var1 = var0.name;
       }
 ​
       if ((var4 & 2) != 0) {
          var2 = var0.age;
       }
 ​
       if ((var4 & 4) != 0) {
          var3 = var0.sex;
       }
 ​
       return var0.copy(var1, var2, var3);
    }
 ​
    @NotNull
    public String toString() {
       return "HumanKt(name=" + this.name + ", age=" + this.age + ", sex=" + this.sex + ")";
    }
 ​
    public int hashCode() {
       String var10000 = this.name;
       int var1 = (var10000 != null ? var10000.hashCode() : 0) * 31;
       Integer var10001 = this.age;
       var1 = (var1 + (var10001 != null ? var10001.hashCode() : 0)) * 31;
       var10001 = this.sex;
       return var1 + (var10001 != null ? var10001.hashCode() : 0);
    }
 ​
    public boolean equals(@Nullable Object var1) {
       if (this != var1) {
          if (var1 instanceof HumanKt) {
             HumanKt var2 = (HumanKt)var1;
             if (Intrinsics.areEqual(this.name, var2.name) && Intrinsics.areEqual(this.age, var2.age) && Intrinsics.areEqual(this.sex, var2.sex)) {
                return true;
             }
          }
 ​
          return false;
       } else {
          return true;
       }
    }
 }

以上就是它反编译出来的的java代码,除了普通的setter,getter,hashCode,equals函数之外。还另外实现的了几个方法,例如componentN()函数,copy函数。这些函数有什么用呢?在了解这些函数有什么作用之前,先了解data class有什么需要我们注意的。或者说data calss有什么要求。

data class 需要注意的事

data class必须遵循以下规则:

  • 主构造函数需要至少有一个参数
  • 主构造函数的所有参数要标记为var或者val
  • 数据类不能是抽象的,开放的,密封或者是内部的。

此外,成员生成遵循关于成员继承的这些规则:

  • 如果在数据类体中有显式实现 equals()hashCode() 或者 toString(),或者这些函数在父类中有 final 实现,那么不会生成这些函数,而会使用现有函数;
  • 如果超类型具有 opencomponentN() 函数并且返回兼容的类型, 那么会为数据类生成相应的函数,并覆盖超类的实现。如果超类型的这些函数由于签名不兼容或者是 final 而导致无法覆盖,那么会报错;
  • 从一个已具 copy(……) 函数且签名匹配的类型派生一个数据类在 Kotlin 1.2 中已弃用,并且在 Kotlin 1.3 中已禁用。
  • 不允许为 componentN() 以及 copy() 函数提供显式实现。

以上就是data class需要注意的一些知识点,那么生成的componentN()函数有什么用呢?

componentN()的作用

这几个函数是用于解构的,有时把一个对象 解构 成很多变量会很方便,例如:

 val (name, age, sex) = humankt

这种语法称为 解构声明 。一个解构声明同时创建多个变量。 我们已经声明了两个新变量: nameage,并且可以独立使用它们。这有什么用呢?

1、比如一个函数返回两个变量

假设我们需要从一个函数返回两个东西。例如,一个结果对象和一个某种状态。 在 Kotlin 中一个简洁的实现方式是声明一个数据类并返回其实例:

data class Result(val result: Int, val status: Status)
fun function(……): Result {
    // 各种计算
    return Result(result, status)
}

// 现在,使用该函数:
val (result, status) = function(……)

因为数据类自动声明 componentN() 函数,所以这里可以用解构声明。 2、比如遍历

 for ((key, value) in map) {
    // 使用该 key、value 做些事情
 }
 ​
 for ((index,data) in list.withIndex()){}

这就是这个componentN()函数的作用。

copy函数的作用

在很多情况下,我们需要复制一个对象改变它的一些属性,但其余部分保持不变。 copy() 函数就是为此而生成。对于上文的 User 类,其实现会类似下面这样:

 fun copy(name: String = this.name, age: Int = this.age,sex:Int = this.sex) = User(name, age,sex)

这让我们可以写:

 val jack = HumanKt("jack",12,1);
 val olderJack = jack.copy(age = 13)

object 与单例

与data class一样,我们经常使用object来声明一些单例类。如下

 object Utils{}

这就是实现了一个单例类了很简单

 public final class Utils {
    @NotNull
    public static final Utils INSTANCE;
 ​
    private Utils() {
    }
 ​
    static {
       Utils var0 = new Utils();
       INSTANCE = var0;
    }
 }

这个单例是线程安全,也是懒加载。

但是这样实现有一个局限性,就是object修饰的类,不能带有参数的构造函数。如果这时候我需要一个有参数的构造函数,那它就不能满足我们的需求了。这时候,就需要使用双重检测模式去实现单例了。

需要了解的怎么实现一个带参数的单例,请参考:www.jianshu.com/p/2d0f285f6…