0%

Data classes 不是魔法(译)

Data classes 是一种替代Java中传统POJOs的简洁方式,每当有人向Java开发者推广Kotlin是救世主的时候,data classes 一定是前三的理由之一。

别误解我,data classes 很棒, 但是从每个人谈论它们(也包括使用,我打赌)的样子看,许多人并不懂 data 关键词真正对一个类做了什么。所以,让我们搞清楚!

传统类(Regular classes)

这是一个非常简单的没有 data 关键词的数据模型类:

1
class Person(val name: String, var age: Int)

这个类实际上已经和我们叫做 POJO 的类是一样的了。在 Java 里,它有两个字段,合适的 getters 和 setters,然后还有一个有两个参数的构造器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public final class Person {
private final String name;
private int age;

public final String getName() {
return this.name;
}

public final int getAge() {
return this.age;
}

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

public Person(String name, int age) {
this.name = name;
this.age = age;
}
}

数据修改器(The data modifier)

现在我们把这个类变成一个 data class

1
data class Person(val name: String, var age: Int)

所做的是给我们添加了一些额外生成的方法,从 Java 代码的角度看像下面这样(简化后的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public final class Person {
/* ... 所有的属性和方法 ... */

public String toString() {
return "Person(name=" + this.name + ", age=" + this.age + ")";
}

public int hashCode() {
return this.name.hashCode() * 31 + this.age;
}

public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Person)) return false;
Person p = (Person) obj;
return this.name.equals(p.name) && this.age == p.age;
}

public final String component1() {
return this.name;
}

public final int component2() {
return this.age;
}

/* 极度简化! */
public final Person copy(String name, int age) {
return new Person(name, age);
}
}

前面三个方法具有自我解释性,它们只是 Any(或者 Object)类的方法的合理实现。

注意到生成的 equals 和 hashCode 方法始终并只使用主构造器中的属性。如果需要不一样的行为,你就得自己去实现这些方法。这可能是你想不使用 data class 的一个地方。

然而,优势是这些方法是在编译期生成的,意味着他们始终是最新的,能够用到主构造器中的每个属性。如果你在自己的代码里维护这些方法的话,没当你添加、删除或者改变一个属性的时候,都要自己去维护这些方法!

Data class 中其余的方法是 Kotlin 特有的,你应该永远都不要从 Java 代码中进行调用。

componentN 风格的方法是支持解构声明(destructuring declarations)的一种约定。在下面的例子中,它允许我们把类分解成多个变量:

1
2
val natalie = Person("Natalie", 43)
val (name, age) = natalie

copy 方法允许我们创造一个我们类的新实例,新实例默认每个属性的值都保持一样:

1
val nat = natalie.copy() // Person(name=Natalie, age=43)

Data class的每个属性还有一个可选的参数(有默认值)。你能够通过使用命名参数挨个按照自己的期望去改变。

1
val will = natalie.copy(name = "Will") // Person(name=Will, age=43)

如你所见,任何没有被提供的参数都能在新的实例里保留默认值。

优点和缺点

所以,选择这个或者另一个的优点是什么?(缺点的列表本质上是相同的,只是反过来)

常规类的优点:

  • 对许多使用场景来说足够了
  • 生成更少的方法:在 Android 上仍然是一个需要关心的地方
  • 在继承上没有任何限制:data classes 在继承上有一些痛点
  • 可以有非属性的构造函数参数:data classes 要求所有的主构造器参数都是属性

Data classes 的优点:

  • 合理地实现了 Any 的方法:使得调试、比较和在 Map 中当做 key 使用变得更容易
  • 支持解构
  • copy 方法:对不可变类尤其有用
  • 真的、真的很流行

结论

Data classes 非常好,带来很多特性的功能。但是 Kotlin 的常规类似乎没有得到应有的关注度,它们比大多数人想象得更有用处!

所以下次你准备创建一个 data class 的时候,想想你是否真的需要它的功能,如果你只是想创建有几个属性的简单类的话。