《Kotlin 实战》第4章笔记

Kotlin 实战笔记

接口

使用 interface 关键字来声明一个接口。可以包含抽象方法和非抽象方法(带默认实现的方法):

1
2
3
4
5
6
interface Clickable {
// 抽象方法
fun click()
// 非抽象方法(带默认实现的方法),子类可以不重写
fun showOff() = println("I`m clickable!")
}

实现类可以使用类似 Java 的 super 关键字来调用接口的非抽象方法;但是,如果实现的多个接口中有相同名字的非抽象方法,此时实现类必须重写该方法,并且对它们的调用也有所不同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface Focusable {
// 两个非抽象方法
fun setFocus(b: Boolean) =
println("I ${if (b) "got" else "lost"} focus")

fun showOff() = println("I`m focusable!")
}

class Button : Clickable, Focusable {
override fun click() {
// 调用 Focusable 接口的 setFocus() 方法
super.setFocus(true)
}

// 此时必须重写该方法
override fun showOff() {
// 调用 Clickable 接口的 showOff() 方法
super<Clickable>.showOff()
// 调用 Focusable 接口的 showOff() 方法
super<Focusable>.showOff()
}
}

final、open

Kotlin 中默认情况下类、方法、属性都是是 finalpublic 的,即不可被继承(不可重写),需要显式的用 open 关键字修饰才可被继承(可重写):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
open class Button : Clickable, Focusable {
// 子类不可重写
val enable: Boolean = true
// 子类可重写
open val text: String = "Button"
// 子类不可重写
fun disable() {}
// 子类可重写
open fun animate() {}
...
}

// 子类可通过关键字 super 调用父类非 private 的属性和方法
class RichButton : Button() {
// 重写子类属性
override val text: String
get() = super.text + super.enable

// 重写父类方法
override fun animate() {
super.animate()
super.disable()
}
}

abstract

使用 abstract 关键字来声明抽象类,抽象方法默认是 open 的,而非抽象方法并不是默认为 open 的:

1
2
3
4
5
6
7
abstract class Animated {
// 抽象方法默认 open
abstract fun animate()

// 非抽象方法可以使用 open,否则不能被重写
open fun stopAnimating() {}
}

内部类、嵌套类

嵌套类类似于 Java 中的静态内部类,不持有外部类的引用;内部类类似于 Java 中的普通内部类,持有外部类引用:

1
2
3
4
5
6
7
8
9
10
11
class Outer {
val test: Int = 0
// 嵌套类,类似于 Java 的静态内部类,不持有外部类引用
class nest {}

// 内部类,持有外部类引用
inner class Inner {
// 使用 this@外部类名字(比如 this@Outer)访问外部类
fun test() = this@Outer.test
}
}

初始化类

constructor 关键字用来构造方法的声明;init 关键字用来引入初始化语句块,类似于 Java 构造方法中的初始化工作。

在声明类时用括号包裹起来的就是主构造方法,它的目的有两个:表明构造方法的参数、声明属性并将参数赋值给属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
// (val nickname: String) 就是主构造方法
// 表明了构造方法参数、声明了一个 nickname 的属性并将参数赋值给属性
class User(val nickname: String)

// 如果主构造方法中没有注解或可见性修饰符,可以省略 constructor
class User constructor(nickname: String) {
val nickname: String

// 初始化语句块
init {
this.nickname = nickname;
}
}

上面两个 User 的声明是等价的。构造方法参数可以和普通方法参数一样使用默认值。

如果想要确保类不被其他代码实例化,需要使用 private 关键字修饰构造方法:

1
class Secretive private constructor() {}

构造方法

除了主构造方法外还可以声明多个从构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
class View {
// 从构造方法
constructor(ctx: Context) {}
constructor(ctx: Context, attr: AttributeSet)
}

class Button: View {
// 调用自己的另一个构造方法
constructor(ctx: Context): this(ctx, null)
// 调用父类构造方法
constructor(ctx: Context, attr: AttributeSet): super(ctx, attr)
}

数据类

使用 data 关键字可以声明类为数据类。

1
data class Client(val name: String, val postalCode: Int)

数据类自动生成了一些常用的方法:

  • equals:用来比较实例
  • hashCode:用来作为例如 HashMap 这种基于哈希容器的键
  • toString:用来为类生成按声明顺序排列的所有字段的字符串表达形式
  • copy:用来创建对象副本

object 关键字

object 关键字定义一个类并同时创建一个实例(对象),使用场景:

  • 对象声明:是定义单例的一种方式
  • 伴生对象:可以持有工厂方法和其他与这个类相关
  • 对象表达式:用来替代 Java 的匿名内部类

对象声明:单例

一个对象声明可以包含属性、方法、初始化语句块,但是不允许声明构造方法(包括主构造方法和从构造方法),同时也可以继承类和接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// object 将类的声明和对象的创建放在了一起
// 内部是通过静态代码块创建了一个对象
object Singleton {
val list= arrayListOf(1, 2, 3)

fun singleton() {
for (i in list) {
print(i)
}
}
}

fun main(args: Array<String>) {
Singleton.list.add(4)
Singleton.singleton()
}

对象声明也可以在类中,这样的对象同样只有一个单一实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Test {
private val test = 0
object Singleton {
val list= arrayListOf(1, 2, 3)

fun singleton() {
// 可以访问外部类 private 成员
println(Test().test)

for (i in list) {
print(i)
}
}
}
}

fun main(args: Array<String>) {
Test.Singleton.list.add(4)
Test.Singleton.singleton()
}

看看编译后的 Java 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static final class Singleton {
@NotNull
private static final ArrayList list;
public static final Test.Singleton INSTANCE;

@NotNull
public final ArrayList getList() {
return list;
}

public final void singleton() {
Iterator var2 = list.iterator();
while(var2.hasNext()) {
Integer i = (Integer)var2.next();
Intrinsics.checkExpressionValueIsNotNull(i, "i");
int var3 = i;
System.out.print(var3);
}
}

static {
Test.Singleton var0 = new Test.Singleton();
INSTANCE = var0;
list = CollectionsKt.arrayListOf(new Integer[]{1, 2, 3});
}
}

伴生对象

伴生对象就是在对象声明的基础上使用了 companion 关键字,但是伴生对象必须声明在类里面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Factory {
private val abc = 0
private fun aaa() {}

// Test 可以省略
companion object Test {
fun p() {
// 可以访问外部类的 private 成员
Factory().abc
Factory().aaa()
}
}
}

fun main(args: Array<String>) {
Factory.p()
}

伴生对象和对象声明很像,但是它们编译后的 Java 代码却有区别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public final class Factory {
private final int abc;
public static final Factory.Test Test = new Factory.Test((DefaultConstructorMarker)null);

private final void aaa() {}

public static final class Test {
public final void p() {
(new Factory()).abc;
(new Factory()).aaa();
}

private Test() {}

public Test(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}

对象表达式:匿名对象

匿名对象替代了 Java 中的匿名内部类的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Java
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "onClick: ");
}
});

// Kotlin
button.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
Log.d(TAG, "onClick")
}
})
坚持原创技术分享,您的支持将鼓励我继续创作!