本文主要介绍kotlin的特性如下:

  • data class

接上篇,正如上篇所说,kotlin 没有重新造轮子,kotlin能做到的,用Java也能做到。所有的编程语言都是工具,目标都是为了实现业务功能。但是工具之间是有分别的:使用者是否用着顺手(是否对程序员友好),完成任务的效率是否高效(开发效率是否高),完成的任务质量是否有保障(运行效率是否高)——这三个维度足够说明一个工具是否优秀。单纯的争论类似“PHP是最好的编程语言”的言论没有任何意义。

5. data class

如果以后AI会写Java代码了,很大概率是先学会写 POJO(Plain old Java object)。

写POJO步骤如下:

  1. 定义字段
  2. 写构造函数
  3. 写setter、getter
  4. 写equals、hashCode
  5. 写toString
  6. 写copy

除了第一步需要人工干预,后面的代码可以利用插件自动生成。既然都可以自动生成了,那为啥还要程序员写出来呢?所以google开源了AutoValue库,只要写一个abstract class,AutoValue会自动实现上述方法。与此类似,kotlin则引入了data class的概念。

声明一个 data class,kotlin会做两件事:

  1. 自动生成equals()/hashCode(), toString(), copy()
  2. 自动生成componentN()
data class User(val login: String,
val avatarUrl: String,
val name: String,
val company: String,
val reposUrl: String,
val blog: String)

转化后生成Java代码

public final class User {
@NotNull
private final String login;
//...
@NotNull
private final String blog;

@NotNull
public final String getLogin() {
return this.login;
}

//....
@NotNull
public final String getBlog() {
return this.blog;
}

public User(@NotNull String login, @NotNull String avatarUrl, @NotNull String name, @NotNull String company, @NotNull String reposUrl, @NotNull String blog) {
//...
super();
this.login = login;
//...
this.blog = blog;
}

@NotNull
public final String component1() {
return this.login;
}

//...

@NotNull
public final String component6() {
return this.blog;
}

@NotNull
public final User copy(@NotNull String login, @NotNull String avatarUrl, @NotNull String name, @NotNull String company, @NotNull String reposUrl, @NotNull String blog) {
return new User(login, avatarUrl, name, company, reposUrl, blog);
}

//...

public String toString() {
return "User(login=" + this.login
+ ", avatarUrl=" + this.avatarUrl
+ ", name=" + this.name
+ ", company=" + this.company
+ ", reposUrl=" + this.reposUrl
+ ", blog=" + this.blog + ")";
}

public int hashCode() {
return (((((this.login != null ? this.login.hashCode() : 0) * 31
+ (this.avatarUrl != null ? this.avatarUrl.hashCode() : 0)) * 31
+ (this.name != null ? this.name.hashCode() : 0)) * 31
+ (this.company != null ? this.company.hashCode() : 0)) * 31
+ (this.reposUrl != null ? this.reposUrl.hashCode() : 0)) * 31
+ (this.blog != null ? this.blog.hashCode() : 0);
}

public boolean equals(Object var1) {
if (this != var1) {
if (var1 instanceof User) {
User var2 = (User) var1;
if (Intrinsics.areEqual(this.login, var2.login)
&& Intrinsics.areEqual(this.avatarUrl, var2.avatarUrl)
&& Intrinsics.areEqual(this.name, var2.name)
&& Intrinsics.areEqual(this.company, var2.company)
&& Intrinsics.areEqual(this.reposUrl, var2.reposUrl)
&& Intrinsics.areEqual(this.blog, var2.blog)) {
return true;
}
}

return false;
} else {
return true;
}
}
}

kotlin 的 data class 虽然很好很强大,但也有许多限制:

  1. 无法使用open修饰 data class

  2. 无法写空构造函数

  3. 所有参数必须有 val 或者 var 修饰

  4. 不能是抽象类,不能是内部类

定下神来,就会发现:这些都是在限制data class的继承功能。

那么,kotlin为什么不允许数据类之间继承呢。
假设 data class 可以实现继承:

Base data class 的实例和继承类的实例之间 equals 如何写?

根据 equals 的定义(不考虑null):

  • 自反 A = A
  • 对称 A = B -> B = A
  • 传递 A = B B = C -> A = C
  • 一致 一次调用A = B成立 -> 多次调用A = B也成立

根据上面代码可知,data class 生成的equals 判断的是 instanceof 比较。

public boolean equals(Object var1) {
if (this != var1) {
if (var1 instanceof User) {
User var2 = (User) var1;
if (Intrinsics.areEqual(this.login, var2.login)
&&
//...
&& Intrinsics.areEqual(this.blog, var2.blog)) {
return true;
}
}

return false;
} else {
return true;
}
}

假设有 Admin 类继承 User 类, 那么下面这个例子就违反了对称性。

Admin a;
User b;
b.equals(a) == true; //Admin a is instanceof(User)
a.equals(b) == false; //User b is not instanceof(Admin)s

情况复杂起来了,是不是?

另外,子类如果修改了构造函数的参数顺序,生成的 componentN 方法会发生冲突。

比如下面这个例子

data class Derived(val c: C, b: B, a: A) : Base(a, b)

Derived 类继承的component1(): A会和生成的component1(): C冲突

所以在遇到复杂的需要人工干预equals、hashCode的情况时,kotlin直接限制了data class 相关的能力。