Kotlin 核心编程 (59):面向对象 3.4.3

阅读数:1 2019 年 12 月 28 日 22:25

Kotlin核心编程(59):面向对象 3.4.3

(copy、componentN 与解构)

内容简介
本书不是一本简单介绍 Kotlin 语法应用的图书,而是一部专注于帮助读者深入理解 Kotlin 的设计理念,指导读者实现 Kotlin 高层次开发的实战型著作。书中深入介绍了 Kotlin 的核心语言特性、设计模式、函数式编程、异步开发等内容,并以 Android 和 Web 两个平台为背景,演示了 Kotlin 的实战应用。
全书共 13 章,分为 4 个部分:
热身篇—Kotlin 基础(第 1~2 章),简单介绍了 Kotlin 设计哲学、生态及基础语法,其中包括 Kotlin 与 Scala、Java 之间的关联与对比,以及 Kotlin 的类型声明的特殊性、val 和 var 的使用、高阶函数的使用、面向表达式编程的使用、字符串的定义与操作等内容;
下水篇—Kotlin 核心(第 3~8 章),深入介绍了面向对象、代数数据类型、模式匹配、类型系统、Lambda、集合、多态、扩展、元编程等 Kotlin 开发核心知识,这是本书的重点,其中涉及很多开发者特别关心的问题,比如多继承问题、模式匹配问题、用代数数据类型抽象业务问题、泛型问题、反射问题等。
潜入篇—Kotlin 探索(第 9~11 章),探索 Kotlin 在设计模式、函数式编程、异步和并发等编程领域的应用,其中包括对 4 大类设计模式、Typeclass 实现、函数式通用结构设计、类型替代异常处理、共享资源控制、CQRS 架构等重点内容的深入剖析;
遨游篇—Kotlin 实战(第 12~13 章),着重演示了 Kotlin 在 Android 和 Web 平台的实战案例,其中涉及架构方式、单向数据流模型、解耦视图导航、响应式编程、Spring 5 响应式框架和编程等内容。

我们继续来看上面代码中的一段:

复制代码
public final Bird copy(double weight, int age, @NotNull String color) {
Intrinsics.checkParameterIsNotNull(color, "color");
return new Bird (weight, age, color);
}
public static Bird copy$default(Bird var0, double var1, int var3, String var4, int var5, Object var6) { //var0 代表被 copy 的对象
if ((var5 & 1) != 0) {
var1 = var0.weight; //copy 时若未指定具体属性的值,则使用被 copy 对象的属性值
}
if ((var5 & 2) != 0) {
var3 = var0.age;
}
if ((var5 & 4) != 0) {
var4 = var0.color;
}
return var0.copy(var1, var3, var4);
}

这段代码中的 copy 方法的主要作用就是帮我们从已有的数据类对象中拷贝一个新的数据类对象。当然你可以传入相应参数来生成不同的对象。但同时我们发现,在 copy 的执行过程中,若你未指定具体属性的值,那么新生成的对象的属性值将使用被 copy 对象的属性值,这便是我们平常所说的浅拷贝。我们来看下面这个例子:

复制代码
Bird b1 = new Bird(20.0,1,"blue");
Bird b2 = b1;
b2.setColor("red");
System.out.println(b1.getColor()); //red

类似这样的代码很多人都写过,但这种方式会带来一个问题,明明是对一个新的对象 b2 做了修改,为什么还会影响老的对象 b1 呢?其实这只是一种表象而已。实际上,除了基本数据类型的属性,其他属性还是引用同一个对象,这便是浅拷贝的特点。

实际上 copy 更像是一种语法糖,假如我们的类是不可变的,属性不可以修改,那么我们只能通过 copy 来帮我们基于原有对象生成一个新的对象。比如下面的两个例子:

复制代码
// 声明的 Bird 属性可变
val b1 = Bird(20.0, 1, "blue")
val b2 = b1
b2.age = 2
// 声明的 Bird 属性不可变
val b1 = Bird(20.0, 1, "blue")
val b2 = b1.copy(age = 2) // 只能通过 copy

copy 更像提供了一种简洁的方式帮我们复制一个对象,但它是一种浅拷贝的方式。所以在使用 copy 的时候要注意使用场景,因为数据类的属性可以被修饰为 var,这便不能保证不会出现引用修改问题。

接下来我们来看看 componentN 方法。简单来说,componentN 可以理解为类属性的值,其中 N 代表属性的顺序,比如 component1 代表第 1 个属性的值,component3 代表第 3 个属性的值。那么,这样设计到底有什么用呢?我们来思考一个问题,我们或多或少地知道怎么将属性绑定到类上,但是对如何将类的属性绑定到相应变量上却不是很熟悉。比如:

复制代码
val b1 = Bird(20.0, 1, "blue")
// 通常方式
val weight = b1.weight
val age = b1.age
val color = b1.color
//kotlin 进阶
val (weight, age, color) = b1

看到 Kotlin 的语法相信你一定会感到兴奋,因为你可能写过类似下面的代码:

复制代码
String birdInfo = "20.0,1,bule";
String[] temps = birdInfo.split(",");
double weight = Double.valueOf(temps[0]);
int age = Integer.valueOf(temps[1]);
String color = temps[2];

这样代码有时真的很烦琐,我们明明知道值的情况,却要分好几步来给变量赋值。很幸运,Kotlin 提供了更优雅的做法:

复制代码
val (weight, age, color) = birdInfo.split(",");

这个语法很简洁也很直观。那么这到底是一种什么魔法呢?其实原理也很简单,就是解构,通过编译器的约定实现解构。

当然 Kotlin 对于数组的解构也有一定限制,在数组中它默认最多允许赋值 5 个变量,因为若是变量过多,效果反而会适得其反,因为到后期你都搞不清楚哪个值要赋给哪个变量了。所以一定要合理使用这一特性。

在数据类中,你除了可以利用编译器帮你自动生成 componentN 方法以外,甚至还可以自己实现对应属性的 componentN 方法。比如:

复制代码
data class Bird(var weight: Double, var age: Int, var color: String) {
var sex = 1
operator fun component4(): Int { //operator 关键字
return this.sex
}
constructor(weight: Double, age: Int, color: String, sex: Int) : this(weight, age, color) {
this.sex = sex
}
}
fun main(args: Array<String>) {
val b1 = Bird(20.0, 1, "blue", 0)
val (weight, age, color, sex) = b1
}

除了数组支持解构外,Kotlin 也提供了其他常用的数据类,让使用者不必主动声明这些数据类,它们分别是 Pair 和 Triple。其中 Pair 是二元组,可以理解为这个数据类中有两个属性;Triple 是三元组,对应的则是 3 个属性。我们先来看一下它们的源码:

复制代码
//Pair
public data class Pair<out A, out B>(
public val first: A,
public val second: B)
//Triple
public data class Triple<out A, out B, out C>(
public val first: A,
public val second: B,
public val third: C)

可以发现 Pair 和 Triple 都是数据类,它们的属性可以是任意类型,我们可以按照属性的顺序来获取对应属性的值。因此,我们可以这么使用它们:

复制代码
val pair = Pair(20.0, 1)
val triple = Triple(20.0, 1, "blue")
// 利用属性顺序获取值
val weightP = pair.first
val ageP = pair.second
val weightT = triple.first
val ageT = triple.second
val colorT = triple.third
// 当然我们也可以利用解构
val (weightP, ageP) = Pair(20.0, 1)
val (weightT, ageT, colorT) = Triple(20.0, 1, "blue")

数据类中的解构基于 componentN 函数,如果自己不声明 componentN 函数,那么就会默认根据主构造函数参数来生成具体个数的 componentN 函数,与从构造函数中的参数无关。

Kotlin核心编程(59):面向对象 3.4.3

购书地址 https://item.jd.com/12519581.html?dist=jd

评论

发布