数据类

数据类有点类似java中的实体类,kotlin中可以关键字data class来声明数据类,完成类似java中lombok的功能。

data class User(val name: String, val age: Int)

对于数据类,kotlin会为primary constructor的参数(也是属性)自动生成下面的方法:

  • equals()/hashCode()
  • toString()
  • componentN()
  • copy()
    下面反编译上面定义的User类,看看kotlin编译后的数据类长啥样:
> javap com.morris.kotlin.dataclass.User
Compiled from "DataClass1.kt"
public final class com.morris.kotlin.dataclass.User {
  public final java.lang.String getName();
  public final int getAge();
  public com.morris.kotlin.dataclass.User(java.lang.String, int);
  public final java.lang.String component1();
  public final int component2();
  public final com.morris.kotlin.dataclass.User copy(java.lang.String, int);
  public static com.morris.kotlin.dataclass.User copy$default(com.morris.kotlin.dataclass.User, java.lang.String, int, int, java.lang.Object);
  public java.lang.String toString();
  public int hashCode();
  public boolean equals(java.lang.Object);
}

数据类声明的要点:

  • primary constructor至少有一个参数
  • 所有primary constructor都需要声明为val or var,使其成为属性
  • 数据类不能是abstract, open, sealed or inner

数据类继承的要点:

  • 如果数据类中显示的定义了equals(), hashCode() or toString(),或者在数据类的父类将这些方法声明为final,那么这些方法就不会再生成,转而使用已有的。
  • 如果父类拥有componentN()方法并且是open的以及兼容的返回类型,那么编译器就会生成对应的componentN()方法并重写父类的方法,如果父类中的方法是不兼容的返回类型或者是final,那么编译器就会报错。
  • 在数据类中显示的提供componentN() and copy()是不被允许的。

当数据类中的属性都有默认值时,kotlin会为数据类提供无参的构造方法:

data class User2(val name: String = "", val age: Int = 0)

生成的数据类反编译后的内容如下:

> javap com.morris.kotlin.dataclass.User2
Compiled from "DataClass1.kt"
public final class com.morris.kotlin.dataclass.User2 {
  public final java.lang.String getName();
  public final int getAge();
  public com.morris.kotlin.dataclass.User2(java.lang.String, int);
  public com.morris.kotlin.dataclass.User2(java.lang.String, int, int, kotlin.jvm.internal.DefaultConstructorMarker);
  public com.morris.kotlin.dataclass.User2();
  public final java.lang.String component1();
  public final int component2();
  public final com.morris.kotlin.dataclass.User2 copy(java.lang.String, int);
  public static com.morris.kotlin.dataclass.User2 copy$default(com.morris.kotlin.dataclass.User2, java.lang.String, int, int, java.lang.Object);
  public java.lang.String toString();
  public int hashCode();
  public boolean equals(java.lang.Object);
}

声明在类中的属性

只有声明在构造方法中的属性才会在toString(), equals(), hashCode(), and copy()方法有所体现,而直接声明在类中的属性不会。

data class Person(val name: String) {
    var age: Int = 0
}

生成的数据类反编译后的内容如下:

> javap com.morris.kotlin.dataclass.Person
Compiled from "DataClass1.kt"
public final class com.morris.kotlin.dataclass.Person {
  public final int getAge();
  public final void setAge(int);
  public final java.lang.String getName();
  public com.morris.kotlin.dataclass.Person(java.lang.String);
  public final java.lang.String component1();
  public final com.morris.kotlin.dataclass.Person copy(java.lang.String);
  public static com.morris.kotlin.dataclass.Person copy$default(com.morris.kotlin.dataclass.Person, java.lang.String, int, java.lang.Object);
  public java.lang.String toString();
  public int hashCode();
  public boolean equals(java.lang.Object);
}

也就意味着下面代码中的两个对象是相等的:

fun main() {
    val person1 = Person("John")
    val person2 = Person("John")
    person1.age = 10
    person2.age = 20
    assert(person1 == person2) // true
}

拷贝

数据类生成的copy()方法用来拷贝对象的属性,是一个浅拷贝。

User类的copy()方法声明如下:

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

copy()方法的使用:

val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
println(olderJack) // User(name=Jack, age=2)

注意:copy()方法是一个浅拷贝,下面的例子证明了这一点:

data class User3(var name : String)

data class Address(var user: User3, var city: String)

fun main() {

    var userJack = User3(name="Jack")
    var address = Address(user = userJack, city = "London")
    var addressCopy = address.copy()

    addressCopy.city = "New York"
    addressCopy.user.name = "John"  // Propagates to `address.user` because they both point to userJack.

    println("address.city is ${address.city}")  // Prints "London"
    println("address.user.name is ${address.user.name}")  // Prints "John"
}

解构声明

在主构造方法中有多少个参数,就会有多少个componentN()方法,这些方法的返回值就是对应的属性,是用来实现解构声明的。

val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // prints "Jane, 35 years of age"
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐