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 实现,那么不会生成这些函数,而会使用现有函数; - 如果超类型具有 open 的
componentN()
函数并且返回兼容的类型, 那么会为数据类生成相应的函数,并覆盖超类的实现。如果超类型的这些函数由于签名不兼容或者是 final 而导致无法覆盖,那么会报错; - 从一个已具
copy(……)
函数且签名匹配的类型派生一个数据类在 Kotlin 1.2 中已弃用,并且在 Kotlin 1.3 中已禁用。 - 不允许为
componentN()
以及copy()
函数提供显式实现。
以上就是data class需要注意的一些知识点,那么生成的componentN()函数有什么用呢?
componentN()的作用
这几个函数是用于解构的,有时把一个对象 解构 成很多变量会很方便,例如:
val (name, age, sex) = humankt
复制代码
这种语法称为 解构声明 。一个解构声明同时创建多个变量。 我们已经声明了两个新变量: name
和 age
,并且可以独立使用它们。这有什么用呢?
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…