林小武

code coffee ukulele

  • 主页
  • 安卓
目录 关于我

林小武

code coffee ukulele

  • 主页
  • 安卓

Kotlin in Action 第四章 类、对象和接口

2017-09-18

第四章主要讲了,类和接口,非默认属性的和构造方法。数据类。类委托和使用 object 关键字。

定义类继承接口

Kotlin 中的接口

普通的接口定义和 Java 一样,与 Java 不一样的是,override 成了一个关键字,强制要求加上,否则将无法编译。

1
2
3
4
5
6
7
8
9
10
11
12
13
14

interface Clickable {
fun click()
}

class Button : Clickable {
override fun click() {
println("I was clicked")
}
}

fun main(args: Array<String>) {
Button().click()
}

输出:

I was clicked

不同与 Java ,Kotlin 可以在接口中定义一个带方法体的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Clickable {
fun click()
fun showOff() = println("I'm clickable") //带默认实现方法
}

class Button : Clickable {
override fun click() {
println("I was clicked")
}
}

fun main(args: Array<String>) {
Button().showOff()
}

输出:

I’m clickable

定义另一个实现同样方法的接口,同时让类实现这两个接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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() {
println("I was clicked")
}
}

fun main(args: Array<String>) {
Button().showOff()
}

会出现编译不过,因为编译器强制要求你提供你自己的实现。但同时也可以通过 super<> 去指定调用哪个父类。

例如:

1
2
3
4
5
6
7
8
9
10
11
class Button : Clickable, Focusable {

override fun showOff() {
super<Focusable>.showOff() //super<> 表示想要调用那个父类的 showOff
super<Clickable>.showOff()
}

override fun click() {
println("I was clicked")
}
}

原理,其实 Kotlin 是以 Java 6 设计的。其并不支持接口中的默认方法。因此它会把每个带默认方法的接口编译成一个普通接口和一个将方法体作为静态函数的类结合体。当子类没有实现的时候,其实 Kotlin 在编译的时候帮你实现了,里面调用的就是静态的类的静态方法。可以参考 :面向对象:抽象类和接口

open、final 和 abstract 修饰符:默认为 final

在 《Effective Java》中建议“要么为继承做好设计并记录文档,要么禁止这么做。” 所以 Kotlin 中采用了同样的哲学思想,类和方法默认都是 final 的。

代码如下:

1
2
3
4
5
6
7
8
9
10
open class RichButton : Clickable { //这个类是 open 的,其他类可以继承它

fun disable() {} //这个函数默认是 final 的:不能在子类中重写它

open fun animate() {} // open :可以在子类重写它

final override fun click() { //这里的 final 并没有被删减是因为没有 final 的 override 意味着 open
println("click")
}
}

open 类和智能转换,类默认为 final 带来的一个重要好处就是这使得在大量场景中的智能转化成为可能。

抽象类,基本上和 Java 差不多。代码如下:

1
2
3
4
5
6
7
8
9
abstract class Animated { //抽象类,不能创建实例

abstract fun animate() //抽象方法,子类必须实现

open fun stopAnimating() {}

fun animateTwice() {}

}

可见性修饰符:默认为 public

总的来说 Kotlin 和 Java 的修饰符类似,同样可以使用 public、protected 和 private修饰符。不同的一点是默认的可见性不一样。Kotlin 如果省略掉修饰符,那么声明的就是public的。

Java 默认的可见性 – 包私有。在 Kotlin 中并没有使用。Kotlin 只把包作为在命名空间里组织代码的一种方式使用,并没有将其作用可见性控制。

作为替代方案,Kotlin 提供了一个新的修饰符。internal,表示只在模块内部可见

例子如下:

1
2
3
4
5
6
7
8
9
10
11

internal open class TalkativeButton : Focusable {
private fun yell() = println("Hey")
protected fun whisper() = println("Let's talk")
}


fun TalkativeButton.giveSpeech() { //错误,giveSpeech 是 public 函数。但他暴露了 TalkativeButton,而它是 internal
yell() //错误,不能访问,在TalkativeButton是私有的。
whisper() //不同Java。报错,不能访问,在TalkativeButton是受保护的。
}

一个通用的规则: 类的基础类型和类型参数列表中用到的所有类,或者函数的签名都有与这个类或者函数本身相同的可见性。所以上述代码,有两种解决方案,一是改变基础类为 public 或把函数改成与之对应的internal修饰符(因为默认是 public)

和 Java 还有一个区别是 protected 成员只在和类和它的子类可见。而 Java 是在同一个包内,都能访问一个 protected 成员。

Kotlin 可见性修饰符表:

修饰符 类成员 顶层声明
public(默认) 所有地方可见 所有地方可见
internal 模块中可见 模块中可见
protected 子类中可见 –
private 类中可见 文件中可见

内部类和嵌套类 : 默认是嵌套类

和 Java 一样,都是能在类中嵌套内部类。而区别是 Kotlin 的嵌套类不能访问外部类的实例,除非做出了特别的要求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Kotlin
interface State : Serializable

interface View {
fun getCurrentState(): State
fun restoreState(state: State)
}

// Java
public class Button implements View {

@NotNull
@Override
public State getCurrentState() {
return new ButtonState();
}

@Override
public void restoreState(@NotNull State state) { /*...*/}

private class ButtonState implements State { /*...*/}

}

上述的内部类 ButtonState,在序列会出现异常。因为内部类隐式的持有宿主类的引用。而宿主类并没有现实序列化接口,所以会出现异常。所以修复这个问题,需要将内部类 ButtonState 声明为 static

在 Kotlin 中,默认行为和 Java 刚好相反。

1
2
3
4
5
6
7
8
9
class Button : View {

override fun restoreState(state: State) {/*...*/}

override fun getCurrentState(): State = ButtonState()

class ButtonState : State {/*...*/} //这个类与 Java 中的静态嵌套类类似

}

如果要把变成一个内部类持有外部类的引用的话,需要使用inner修饰符。同时引用实例的语法也和 Java 不同。需要使用 this@Other 从 Inner 去访问 Outher类。

1
2
3
4
5
class Outer {
inner class Inner {
fun getOuterReference(): Outer = this@Outer
}
}

密封类: 定义受限的类继承结构

下面例子中,接口的 Expr 的两个子类分别表示数字的Num,以及表示两个表达式之和的 Sun 用when 表达式处理所有可能固然方便,但是必须提供一个分支来处理没有任何其他分支匹配的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13

interface Expr

class Num(val value: Int) : Expr

class Sum(val left: Expr, val right: Expr) : Expr

fun eval(e: Expr): Int =
when (e) {
is Num -> e.value
is Sum -> eval(e.right) + eval(e.left)
else -> throw IllegalArgumentException("Unknown expression")
}

上述的代码,编译器会强制检查默认选项。在这个例子中,不能返回一个有意义的值。就会直接抛出异常。当我们扩展Expr的子类的时候。忘了改上述代码。就会出现异常报错。编译器不能的检查的出这个潜在的 BUG。

Kotlin 为了这个问题提供了解决方案: sealed 类。为父类添加一个 sealed 修饰符。对可能创建的子类做出严格的显示。所有的直接子类都必须嵌套在父类中。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

sealed class Expr { //标记为密封的

class Num(val value: Int) : Expr() //将所有可能的类作为嵌套类列出。

class Sum(val left: Expr, val right: Expr) : Expr()

}

fun eval(e: Expr): Int =
when (e) {
is Expr.Num -> e.value
is Expr.Sum -> eval(e.right) + eval(e.left) //因为表达式涵盖了所有可能的情况,所以不再需要 else 分支
}

这样,在添加多一个嵌套类的时候,when 这里就会编译报错。需要你实现分支。同时注意,sealed 修饰符隐含着这个类是一个 open 类。不再需要显示的添加。

声明一个带非默认构造方法或者属性的类

在 Java 中,一个类可以声明一个或多个构造方法。Kotlin 也是类是的。只是做了点修改,区分了主构造方法(通常是主要而简洁的初始化类的方法)和从构造方法(在类内部声明)。

初始化类:主构造方法和初始化语句块

声明一个简单的类

1
class User(val nickname: String)

括号围起来的语句块就叫做主构造方法。它表明了构造函数的参数。以及定义参数的初始化的属性。
这是一种简写。明确的代码如下:

1
2
3
4
5
6
7
class User constructor(_nickname: String) { //带一个参数的主构造函数
val nickname: String

init { //初始化语句块
nickname = _nickname
}
}

上述代码有这么两个关键字。

  1. constructor 关键字用来开始一个主构造方法或从构造方法的声明。
  2. init 关键字着用来引入一个初始化语句块。包含了在类被创建时执行的代码,并会与主构造方法一起使用。

如果主构造方法没有注解或可见性修饰符,同样也可以去掉 constructor 关键字。

_nickname 用来消除歧义。同时也可以用this来消除歧义。

如果类具有父类。主构造方法同样需要初始化父类。可以通过在基类列表的父类引用中提供父类构造方法的参数的方式来做到这一点。

1
class TwitterUser(_nickname: String) : User(_nickname)

如果确保类不被其他代码实例化。必须把构造方法标记为 private

1
class Secretive private constructor() //这个类有一个 private

构造方法:用不同的方式来初始化父类

例如 Android 的 View 。多个构造函数来创建 View

代码如下:

1
2
3
4
5
6
7
8
open class View {
constructor(ctx: Context){ //从构造方法
//some code
}
constructor(ctx: Context, attr: AttributeSet){
//some code
}
}

如果想要扩展这个类,可以声明同样的构造方法。

1
2
3
4
5
6
7
8
9
class MyButton : View {
constructor(ctx: Context) : super(ctx) { //super 调用父类构造方法
//some code
}

constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) {
//some code
}
}

和 Java 一样。也可以使用 this 关键字。从一个构造方法中调用你自己类的另一个构造方法。如下:

1
2
3
4
5
6
7
8
9
10
class MyButton : View {

constructor(ctx: Context) : this(ctx, MY_STYLE) { //委托给这个类的另一个构造方法。
//some code
}

constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) {
//some code
}
}

实现在接口中声明的属性

在 Kotlin 接口可以包含抽象属性的声明。如下:

1
2
3
interface User {
val nickname: String
}

可以以不同的方式去实现接口属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Privateuser(override val nickname: String) : User //主构造方法属性

class SubscribingUser(val email: String) : User {
override val nickname: String
get() = email.substringBefore("@")
}

class FaceBookUser(val accountId: Int) : User {
override val nickname = getFaceBookName(accountId)

private fun getFaceBookName(accountId: Int): String {
return "face $accountId"
}
}

除了抽象属性声明外。接口还可以包含具有 getter 和 setter 的属性。

1
2
3
4
5
6

interface User {
val email:String
val nickname: String
get() = email.substringBefore('@') //属性没有支持字段,结果值在每次访问时通过计算得到。
}

通过 getter 或 setter 访问支持字段

结合存储的属性和具有自定义访问器在每次访问时计算值的属性。需要支持这个情况,需要能够从属性的访问器中访问它的支持字段。

例子如下:

1
2
3
4
5
6
7
8
9
10
11
class User(val name: String) {
var address: String = "unspecified"
set(value: String) {
println("""
Address was changed for $name:
"$field" -> "$value".""".trimIndent() //读取支持字段的值
)

field = value //更新字段的值
}
}

输出

Address was changed for Alice:
“unspecified” -> “Elsenheimerstrasse 47,80687 Muenchen”.

在 setter 的函数中,使用了特殊的标识符 field 来访问支持字段的值。

修改访问器的可见性。

访问器的可见性默认与属性的可见性相同。但可以通过修饰符方式来修改它。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class LengthCounter {
var counter: Int = 0
private set //不能在类外部修改这个属性。

fun addWord(word: String) {
counter += word.length
}
}

//调用
val lengthCounter = LengthCounter()
lengthCounter.addWord("Hi!")
println(lengthCounter.counter)

输出:

3

编译器生成的方法:数据类和类委托

Java 平台定义了一些需要在许多类中呈现的方法,并通过是以一种机械的方式实现。如equals、hashCode及 toString

通用对象方法

先简单的创建一个客户类

1
class Client(val name: String, val postalCode: Int)
字符串表示:toString

重写 toString方法。

1
2
3
4
5
6
7
8
class Client(val name: String, val postalCode: Int) {
override fun toString(): String = "Client(name=$name,postalCode=$postalCode)"
}

//调用

val client = Client("xiaowu", 111)
println(client.toString())

输出:

Client(name=xiaowu,postalCode111)

对象相等性:equals

重写 equals方法。

1
2
3
4
5
6
7
8
9
10
11
12
override fun equals(other: Any?): Boolean { //"Any" 是 java.lang.Object 的模拟。Kotlin 中所有类的父类
if (other == null || other !is Client)
return false
return name == other.name &&
postalCode == other.postalCode
}

//调用

val client2 = Client("xiaowu", 111)
println(client.equals(client2))
println(client == client2) //在 Kotlin == 也是和 equals 作用一致。

输出:

true
true

Hash 容器: hashCode

hashCode 通常和 equals 一起重写。假设创建了有一个元素的set。如果传入相同数据的Clint 期望检查是true 但是实际上返回的是 false

1
2
3

val processed = hashSetOf(Client("Alice", 3412))
print(processed.contains(Client("Alice", 3412)))

输出:

false

添加上 hashCode 的实现

1
2
3
4
class Client(val name: String, val postalCode: Int) {
...
override fun hashCode(): Int = name.hashCode() * 31 + postalCode
}

这时再调用上述processed.contains 方法。返回的就是 true 了。

数据类: 自动生成通用方法的实现

如果想要的类是一个方便的数据容器,你需要重新写toString,equals,hashCode方法。虽然说 IDE 能帮助生成这些代码,但是也是重复的工作量,Kotlin 添加了data 修饰符。通过这个修饰符。编译的时候就自动帮你生成那些需要重写的方法。

代码如下:

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

再调用toString,equals,hashCode 这些方法。效果和自己重写的一模一样。

数据类和不可变性:copy() 方法

虽然数据类,没有强制用val,同样也可以用var 但是强烈建议用val。因为用var的话。当实例被加入到 HashMap 或类似容器的键。作为键的对象加入容器后被修改。这时候容器可能会进入一种无效的状态。

为了使用不可变对象的数据类变得更容易,Kotlin 编译器为它们生成了一个方法;一个允许 copy 类的实例方法,并在 copy 的同时修改某些属性值。创建副本通常是修改实例的好选择:副本有着单独的生命周期而且不会影响代码中引用原始的位置。

手动实现 copy 方法后看起来的样子。

1
2
3
4
5
6
7
8
class Client(val name: String, val postalCode: Int) {
...
fun copy(name: String = this.name, postalCode: Int = this.postalCode) = Client(name, postalCode)
}

//调用
val bob = Client("Bob", 9527)
print(bob.copy(postalCode = 222))

输出

Client(name=Bob, postalCode=222)

类委托类:使用”by”关键字

当我们需要向一些类添加一些行为,即便它并没有被设计为可扩展的。一个常用的方式以装饰器模式。这个模式的本质是创建一个新类,实现与原始类一样的接口。并将原来的类实例作为一个字段保存。与原始类拥有同样行为的方法不用被修改,只需要转发到原始类的实例。

这个方式的一个缺点就是需要相当多的模板代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

class DelegatingCollection<T> : Collection<T> {

private val innerList = arrayListOf<T>()

override val size: Int get() = innerList.size

override fun contains(element: T): Boolean = innerList.contains(element)

override fun containsAll(elements: Collection<T>): Boolean = innerList.containsAll(elements)

override fun isEmpty(): Boolean = innerList.isEmpty()

override fun iterator(): Iterator<T> = innerList.iterator()

}

kotlin 将委托作为一个语言级别的功能做了头等支持。无论什么时候实现一个接口,你都可以使用 by 关键字将接口的实现委托到另一个对象。

下面用这种方法重写上述代码

1
2
3
4

class DelegatingCollection<T>(
innerList: Collection<T> = ArrayList<T>()
) : Collection<T> by innerList {}

类中所有方法实现都消失了,编译器会生成它们。因为代码中没有太多有意思的内容,所以当编译器能够自动为你做同样的事情时候,没必要手写这些代码。

可以部分不使用委托,提供一个不同的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class CountingSet<T>(
val innerSet: MutableCollection<T> = HashSet<T>()
) : MutableCollection<T> by innerSet { //将 MutableCollection 的实例委托给 innerSet

var objectsAdded = 0

override fun add(element: T): Boolean { //不使用委托,提供一个不同的实现。
objectsAdded++
return innerSet.add(element)
}

override fun addAll(elements: Collection<T>): Boolean {
objectsAdded += elements.size
return innerSet.addAll(elements)
}
}

//调用

val cset = CountingSet<Int>()
cset.addAll(listOf(1, 1, 3))
println("${cset.objectsAdded} objects were added,${cset.size} remain")

输出

3 objects were added,2 remain

object 关键字:将声明一个类与创建一个实例结合起来。

核心理念。这个关键字定义一个类并同事创建一个实例(对象)。下面是一些使用场景

  • 对象声明是定义单例的一种方式。
  • 伴生对象可以持有工厂方法和其他与这个类相关,但在调用时并不依赖类实例对象。他们成员可以通过类名来访问。
  • 对象表达式用替代 Java 的匿名内部类。

对象声明:创建单例易如反掌

在 Java 常用单例模式,定义一个使用 private 构造方法并用静态字段来持有这个类仅有的实例。而 Kotlin 通过使用对象声明功能来实现这个。

例如一个忽略大小写的文件路径比较器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
object CaseInsensititiveFileComparator : Comparator<File> {
override fun compare(file1: File, file2: File): Int {
return file1.path.compareTo(file2.path, ignoreCase = true)
}
}

//调用
println(CaseInsensititiveFileComparator.compare(
File("/User"),File("/user")
))

//调用
val files = listOf( File("/Z"),File("/a"))
println(files.sortedWith(CaseInsensititiveFileComparator)) //sortedWith 它返回一个根据特定的比较器排序过的列表。

输出:

0
[/a, /Z]

同样可以在类中声明对象。这样的对象同样只有一个单例实例:它在每个容器类的实例并不具有不同的实例。

例如嵌套类实现 Comparator

1
2
3
4
5
6
7
8
9
10
11
data class Person(val name: String) {
object NameComparator : Comparator<Person> {
override fun compare(p1: Person, p2: Person): Int =
p1.name.compareTo(p2.name)

}
}

//调用
val persons = listOf(Person("Bob"),Person("Alice"))
print(persons.sortedWith(Person.NameComparator))

输出:

[Person(name=Alice), Person(name=Bob)]

Kotlin 中的对象声明被编译成了通过静态字段来持有它的单一实例的类,这个字段名字始终都 INSTANCE

代码如下

1
2
/* Java */
CaseInsensititiveFileComparator.INSTANCE.compare(file1,file2);

伴生对象:工厂方法和静态成员的地盘

一般来说,可以用顶层函数来做为静态工具方法。但是它并不能访问到类的 private 成员。因此需要在没有类实例的情况下访问类内部的函数。可以将其写成那个类中的对象声明成员。这种函数的一个例子就是工厂方法。

在类中定义的对象之一可以使用一个特殊关键字来标记 companion

代码如下:

1
2
3
4
5
6
7
8
class A {
companion object {
fun bar() = println("companion object call")
}
}

//调用
A.bar()

输出

companion object call

定义一个拥有多个构造方法的类

1
2
3
4
5
6
7
8
9
10
11
12
class User {
val nickname: String

constructor(email: String) {
nickname = email.substringBefore("@")
}

constructor(facebookAccounyId: Int) {
nickname = getFaceBookName(facebookAccounyId)
}

}

使用工厂方法来替代从构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

class User private constructor(val nickname: String) { //将主构造方法标记为私有的。

companion object {
fun newSubscribingUser(email: String) =
User(nickname = email.substringBefore("@")) //使用工厂方法创建

fun newFaceBookUser(accountId:Int) =
User(getFaceBookName(accountId))
}

}

//调用
val subscribingUser = User.newSubscribingUser("bob@gmail.com")
println(subscribingUser.nickname)

输出:

bob

作为普通对象使用伴生对象

声明一个命名伴生对象。例如命名为 Loader

代码如下:

1
2
3
4
5
6
7
8
9
class Person(val name: String) {
companion object Loader{
fun fromJSON(jsonText:String):Person = ...
}
}

//调用
Person.Loader.fromJSON("{name:'Dmitry'}")
Person.fromJSON("{name:'Dmitry'}")

这样就可以通过 Loader. 也可以直接通过方法名。

在伴生对象实现接口

伴生对象也能实现接口。代码如下:

1
2
3
4
5
6
7
8
9
interface JSONFactory<T> {
fun fromJSON(jsonText: String): T
}

class Person(val name: String) {
companion object : JSONFactory<Person> {
fun fromJSON(jsonText: String): Person = ...
}
}

Kotlin 的伴生对象和静态成员。

类的伴生对象会同样被编译成常规对象:类中的一个引用了它的实例的静态字段。如果这个伴生对象没有给命名,在 Java 中它可以通过 Companion 引用来访问。

1
Person.Companion.fromJSON("...");
伴生对象扩展
1
2
3
4
5
6
7
8
9
10
11
12
13
14

class Person(val firstName: String, val lastName: String) {
companion object { //定义一个空的伴生对象。

}
}

fun Person.Companion.fromJson(json: String): Person ={
//...
}


//调用
Person.fromJson("{name:'Dmitry'}")

对象表达式:改变写法的匿名内部类

object 不仅仅用来声明单例的对象。还能用来声明匿名对象。

用匿名对象来实现事件监听

1
2
3
4
5
6
7
8
9
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
//重写方法。
}

override fun mouseEntered(e: MouseEvent) {
//重写方法。
}
})

如果需要给对象分配一个名字。可以将其存储到一个变量中

1
2
3
4
val listener = object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { }
override fun mouseEntered(e: MouseEvent) {}
}

和 Java 匿名内部类只能扩展一个类或实现一个接口不同。Kotlin 的匿名对象可以实现多个接口或者不实现接口。

与 Java 匿名类一样。在对象表达式中的代码可以访问创建它的函数中的变量。但与 Java 不同。访问并没有被限制在 final 变量,还可以在对象表达式中国年修改变量值。

如下:

1
2
3
4
5
6
7
8
9
fun countClick(window: Window) {
var clickCount = 0

window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
clickCount++
}
})
}

总结

  • Kotlin 的接口与 Java 的相识,但可以包含默认实现。
  • 所有的声明默认都是 final 和 public 的。
  • 要想使得声明不是 final 的,标记为 open
  • internal 声明在同一模块可见
  • 嵌套类默认不是内部类,使用 inner 关键字来存储外部类的引用
  • sealed 类(密封类)的子类只能嵌套在自身的声明中(1.1 允许将子类放置在同一文件的任意地方)
  • 初始化语句块(init)和从构造方法(constructor)为初始化类实例提供了灵活性
  • 使用 field 标识符在访问器方法体中引用属性的支持字段
  • 数据类提供了编译器生成的 equals、hashCode、toString、copy和其他方法
  • 类委托帮助避免在代码中出现许多相似的委托方法
  • 对象声明(object)是 Kotlin 中定义单例类的方法。
  • 伴生对象 companion(与包级别函数和属性一起)替代了 Java 静态方法和字段的定义
  • 伴生对象和其他对象一样,可以实现接口,也可以拥有扩展函数和属性
  • 对象表达式是 Kotlin 中针对 Java 匿名内部类的替代品,并增加了诸如实现多个接口的能力和修改在创建对象的作用域中定义的变量的能力等功能
  • Android开发
  • kotlin

扫一扫,分享到微信

微信分享二维码
Kotlin in Action 第五章 Lambda 编程
Kotlin in Action 第三章 函数的定义与调用
© 2020 林小武
Hexo Theme Yilia by Litten
  • 目录
  • 关于我

tag:

  • Android开发
  • 随笔
  • 咖啡
  • RxJava
  • 音乐
  • TED
  • git
  • kotlin
  • java
  • 自动化
  • 算法
  • ukulele
  • 基础
  • Python

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

Android工程师
咖啡爱好者
会弹点尤克里里