《Kotlin核心编程》 ——3 面 向 对 象
下水篇
Kotlin核心
第3章 面向对象
第4章 代数数据类型和模式匹配
第5章 类型系统
第6章 Lambda和集合
第7章 多态和扩展
第8章 元编程
第3章
面 向 对 象
通过对上一章的阅读,相信你对Kotlin的基础语法已经有了一定的了解,本章我们会开启Kotlin中面向对象的大门。在Java中,也许你已经厌烦了重载多个构造方法去初始化一个类,或者又因设计了错误的继承关系而导致结构混乱。另外,你也肯定见识过Java中各种模板化的代码,这让程序变得臃肿。
很庆幸,在Kotlin中你将没有这些烦恼,它用合理的语言设计帮我们处理了可能会遇到的麻烦,比如方法默认参数、更严格的限制修饰符等。最后,你还将接触到Kotlin中编译器生成的更多样化的密封类、数据类,这为Kotlin从面向对象到函数式架起了另一条桥梁。我们会在下一章中进一步介绍它们的高级应用。
3.1 类和构造方法
Java是一门假设只用面向对象进行程序设计的编程语言,在Kotlin中对象思想同样非常重要(虽然它是多范式语言)。本节我们会从对象这个概念入手,结合一个鸟的例子,来学习在Kotlin中如何简洁地声明一个类和接口。
3.1.1 Kotlin中的类及接口
对象是什么?我们肯定再熟悉不过了,任何可以描述的事物都可以看作对象。我们以鸟为例,来分析它的组成。
状态:形状、颜色等部件可以看作鸟的静态属性,大小、年龄等可以看作鸟的动态属性,对象的状态就是由这些属性来表现的。
行为:飞行、进食、鸣叫等动作可以看作鸟的行为。
1. Kotlin中的类
对象是由状态和行为组成的,我们可以通过它们描述一个事物。下面我们就用Kotlin来抽象一个Bird类:
// Kotlin中的一个类
class Bird {
val weight: Double = 500.0
val color: String = "blue"
val age: Int = 1
fun fly() {} // 全局可见
}
是不是一点也不陌生?我们依然可以使用熟悉的class结构体来声明一个类。但是,Kotlin中的类显然也存在很多不同。作为对照,我们把上述代码反编译成Java的版本,然后分析它们具体的差异。
public final class Bird {
private final double weight = 500.0D;
@NotNull
private final String color = "blue";
private final int age = 1;
public final double getWeight() {
return this.weight;
}
@NotNull
public final String getColor() {
return this.color;
}
public final int getAge() {
return this.age;
}
public final void fly() {
}
}
可以看出,虽然Kotlin中类声明的语法非常近似Java,但也存在很多不同:
1)不可变属性成员。正如我们在第2章介绍过,Kotlin支持用val在类中声明引用不可变的属性成员,这是利用Java中的final修饰符来实现的,使用var声明的属性则反之引用可变。
2)属性默认值。因为Java的属性都有默认值,比如int类型的默认值为0,引用类型的默认值为null,所以在声明属性的时候我们不需要指定默认值。而在Kotlin中,除非显式地声明延迟初始化,不然就需要指定属性的默认值。
3)不同的可访问修饰符。Kotlin类中的成员默认是全局可见,而Java的默认可见域是包作用域,因此在Java版本中,我们必须采用public修饰才能达相同的效果。我们会在下一节讲解Kotlin中不同的访问控制。
2.可带有属性和默认方法的接口
在看过类对比之后,我们继续来看看Kotlin和Java中接口的差异。这一次,我们先来看一个Java 8版本的接口:
// Java 8中的接口
public interface Flyer {
public String kind();
default public void fly() {
System.out.println("I can fly");
}
}
众所周知,Java 8引入了一个新特性—接口方法支持默认实现。这使得我们在向接口中新增方法时候,之前继承过该接口的类则可以不需要实现这个新方法。接下来再来看看在Kotlin中如何声明一个接口:
// Kotlin中的接口
interface Flyer {
val speed: Int
fun kind()
fun fly() {
println("I can fly")
}
}
同样,我们也可以用Kotlin定义一个带有方法实现的接口。同时,它还支持抽象属性(如上面的speed)。然而,你可能知道,Kotlin是基于Java 6的,那么它是如何支持这种行为的呢?我们将上面Kotlin声明的接口转换为Java代码,提取其中关键的代码:
public interface Flyer {
int getSpeed();
void kind();
void fly();
public static final class DefaultImpls {
public static void fly(Flyer $this) {
String var1 = "I can fly";
System.out.println(var1);
}
}
}
我们发现Kotlin编译器是通过定义了一个静态内部类DefaultImpls来提供fly方法的默认实现的。同时,虽然Kotlin接口支持属性声明,然而它在Java源码中是通过一个get方法来实现的。在接口中的属性并不能像Java接口那样,被直接赋值一个常量。如以下这样做是错误的:
interface Flyer {
val height = 1000 //error Property initializers are not allowed in interfaces
}
Kotlin提供了另外一种方式来实现这种效果:
interface Flyer {
val height
get() = 1000
}
可能你会对这种语法感到不习惯,但这与Kotlin实现该机制的背景有关。我们说过,Kotlin接口中的属性背后其实是用方法来实现的,所以说如果我们要为变量赋值常量,那么就需要编译器原生就支持方法默认实现。但Kotlin是基于Java 6的,当时并不支持这种特性,所以我们并不能像Java那样给一个接口的属性直接赋值一个常量。我们再来回味下在Kotlin接口中如何定义一个普通属性:
interface Flyer {
val height: Long
}
它同方法一样,若没有指定默认行为,则在实现该接口的类中必须对该属性进行初始化。
总的来说,Kotlin的类与接口的声明和Java很相似,但它的语法整体上要显得更加简洁。好了,现在我们定义好了Bird类,接下来再来看看如何用它创建一个对象吧。
- 点赞
- 收藏
- 关注作者
评论(0)