本文主要介绍kotlin的特性如下:
接上篇,正如上篇所说,kotlin 没有重新造轮子,kotlin能做到的,用Java也能做到。所有的编程语言都是工具,目标都是为了实现业务功能。但是工具之间是有分别的:使用者是否用着顺手(是否对程序员友好),完成任务的效率是否高效(开发效率是否高),完成的任务质量是否有保障(运行效率是否高)——这三个维度足够说明一个工具是否优秀。单纯的争论类似“PHP是最好的编程语言”的言论没有任何意义。
5. data class
如果以后AI会写Java代码了,很大概率是先学会写 POJO(Plain old Java object)。
写POJO步骤如下:
- 定义字段
- 写构造函数
- 写setter、getter
- 写equals、hashCode
- 写toString
- 写copy
除了第一步需要人工干预,后面的代码可以利用插件自动生成。既然都可以自动生成了,那为啥还要程序员写出来呢?所以google开源了AutoValue库,只要写一个abstract class,AutoValue会自动实现上述方法。与此类似,kotlin则引入了data class的概念。
声明一个 data class,kotlin会做两件事:
- 自动生成
equals()/hashCode()
, toString()
, copy()
- 自动生成
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 虽然很好很强大,但也有许多限制:
-
无法使用open
修饰 data class
-
无法写空构造函数
-
所有参数必须有 val 或者 var 修饰
-
不能是抽象类,不能是内部类
定下神来,就会发现:这些都是在限制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; a.equals(b) == false;
|
情况复杂起来了,是不是?
另外,子类如果修改了构造函数的参数顺序,生成的 componentN
方法会发生冲突。
比如下面这个例子
data class Derived(val c: C, b: B, a: A) : Base(a, b)
|
Derived 类继承的component1(): A
会和生成的component1(): C
冲突
所以在遇到复杂的需要人工干预equals、hashCode
的情况时,kotlin直接限制了data class 相关的能力。