kotlin入门3 | 青训营笔记

118 阅读9分钟

这是我参与「第四届青训营 」笔记创作活动的第5天

kotlin语言

接口

接口是用于实现多态编程的重要组成部分。Kotlin是单继承结构的语言,任何一个类最多只能继承一个父类,但是却可以实现任意多个接口。

我们可以在接口中定义一系列的抽象行为,然后由具体的类去实现。 下面介绍具体例子,首先创建一个Study接口,并在其中定义几个学习行为。然后在Study接口中添加几个学习相关的函数,注意接口中的函数不要求有函数体,代码如下 所示:

interface Study {
	fun readBooks()
	fun doHomework()
}
class Student(name: String, age: Int) : Person(name, age), Study {
	override fun readBooks() {
		println(name + " is reading.")
	}
	override fun doHomework() {
		println(name + " is doing homework.")
	}
}

Kotlin中统一使用冒号实现继承和接口,中间用逗号进行分隔。上述代码就表示Student类 继承了Person类,同时还实现了Study接口。另外接口的后面不用加上括号,因为它没有构造函数可以去调用。 Study接口中定义了readBooks()和doHomework()这两个待实现函数,因此Student类必须实现这两个函数。Kotlin中使用override关键字来重写父类或者实现接口中的函数,这里我们只是简单地在实现的函数中打印了一行日志。

fun main() {
	val student = Student("Jack", 19)
	doStudy(student)
}
fun doStudy(study: Study) {
	study.readBooks()
	study.doHomework()
}

主要为了展示多态编程的特性。

首先创建了一个 Student类的实例,将它传入到了doStudy()函数中。doStudy()函数接收一个Study类 型的参数,由于Student类实现了Study接口,因此Student类的实例是可以传递给 doStudy()函数的,接下来我们调用了Study接口的readBooks()和doHomework()函数, 这种就叫作面向接口编程,也可以称为多态。

为了让接口的功能更加灵活,Kotlin还增加了一个额外的功能:允许对接口中定义的函数进行默认实现。其实Java在JDK 1.8之后也开始支持这个功能了,因此总体来说,Kotlin和Java在接口方面的功能仍然是一模一样的。

interface Study {
	fun readBooks()
	fun doHomework() {
		println("do homework default implementation.")
	}
}

这里给doHomework()函数加上了函数体,并且在里面打印了一行日志。如果接口中的一个函数拥有了函数体,这个函数体中的内容就是它的默认实现。现在当一个类去实现Study接口时,只会强制要求实现readBooks()函数,而doHomework()函数则可以自由选择 实现或者不实现,不实现时就会自动使用默认的实现逻辑。 现在回到Student类当中,你会发现如果我们删除了doHomework()函数,代码是不会提示错 误的,而删除readBooks()函数则不行。

函数的可见性修饰符

修饰符JavaKotlin
public所有类可见所有类可见(默认)
private当前类可见当前类可见
protected当前类、子类、同一包路径下的类可见前类、子类可见
default同一包路径下的类可见(默认)
internal同一模块中的类可见

Java中有public、private、protected和default(什么都不 写)这4种函数可见性修饰符。Kotlin中也有4种,分别是public、private、protected和 internal,需要使用哪种修饰符时,直接定义在fun关键字的前面即可。下面我详细介绍一下Java和Kotlin中这些函数可见性修饰符的异同。 首先private修饰符在两种语言中的作用是一模一样的,都表示只对当前类内部可见。public 修饰符的作用虽然也是一致的,表示对所有类都可见,但是在Kotlin中public修饰符是默认 项,而在Java中default才是默认项。前面我们定义了那么多的函数,都没有加任何的修饰 符,所以它们默认都是public的。protected关键字在Java中表示对当前类、子类和同一包 路径下的类可见,在Kotlin中则表示只对当前类和子类可见。Kotlin抛弃了Java中的default可 见性(同一包路径下的类可见),引入了一种新的可见性概念,只对同一模块中的类可见,使 用的是internal修饰符。比如我们开发了一个模块给别人使用,但是有一些函数只允许在模块 内部调用,不想暴露给外部,就可以将这些函数声明成internal。

数据类与单例类

数据类简介

在一个规范的系统架构中,数据类通常占据着非常重要的角色,它们用于将服务器端或数据库中的数据映射到内存中,为编程逻辑提供数据模型的支持。数据类通常需要重写equals()、hashCode()、toString()这几个方法。其中,equals() 方法用于判断两个数据类是否相等。hashCode()方法作为equals()的配套方法,也需要一起重写,否则会导致HashMap、HashSet等hash相关的系统类无法正常工作。toString()方法用于提供更清晰的输入日志,否则一个数据类默认打印出来的就是一行内存地址。

data class Cellphone(val brand: String, val price: Double)

这里我们新构建一个手机数据类,只有品牌和价格这两个字段。非常简洁。

神奇的地方就在于data这个关键字,当在一个类前面声明了data关键字时,就表明你希望这个类是一个数据类,Kotlin会根据主构造函数中的参 数帮你将equals()hashCode()toString()等固定且无实际逻辑意义的方法自动生成, 从而大大减少了开发的工作量。 另外,当一个类中没有任何代码时,还可以将尾部的大括号省略。

单例类简介

单例模式是最常用、最基础的设计模式之一,它可以用于避免创建重复的对象。比如我们希望某个类在全局最多只能拥有一个实例,这时就可以使用单例模式。 Kotlin将一些固定的、重复的逻辑实现隐藏了起来,只暴露给我们最简单方便的用法。 在Kotlin中创建一个单例类的方式极其简单,只需要将class关键字改成object关键字即可。

object Singleton {
	fun singletonTest() {
		println("singletonTest is called.")
	}
}

在Kotlin中我们不需要私有化构造函数,也不需要提供getInstance()这样的静态方法,只需要把class关键字改成object关键字,一个单例类就创建完成了。而调用单例类中的函数也很简单,比较类似于Java中静态方法的调用方式:

Singleton.singletonTest()

这种写法虽然看上去像是静态方法的调用,但其实Kotlin在背后自动帮我们创建了一个 Singleton类的实例,并且保证全局只会存在一个Singleton实例。

Lambda编程

此Kotlin专门提供了一个内置的listOf()函数来简化 初始化集合的写法,如下所示:

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")

不过需要注意的是,listOf()函数创建的是一个不可变的集合。使用mutableListOf()函数就可以创建可变集合。 Set集合的用法几乎与集合一模一样,只是将创建 集合的方式换成了setOf()和mutableSetOf()函数而已。不再赘述。

Map是一种键值对形式的数据结构,因此在用法上和List、 Set集合有较大的不同。传统的Map用法是先创建一个HashMap的实例,然后将一个个键值对数据添加到Map中。在Kotlin中并不建议使用put()和get()方法来对Map进行添加和读取数据操作,而是更加推荐 使用一种类似于数组下标的语法结构,比如向Map中添加一条数据就可以这么写:

map["Apple"] = 1

而从Map中读取一条数据就可以这么写:

val number = map["Apple"]

下面给出一个例子:

val map = HashMap<String, Int>()
map["Apple"] = 1
map["Banana"] = 2
map["Orange"] = 3
map["Pear"] = 4
map["Grape"] = 5

Kotlin毫无疑问地提供了一对mapOf()和 mutableMapOf()函数来继续简化Map的用法。

val map = mapOf("Apple" to 1, "Banana" to 2, "Orange" to 3, "Pear" to 4, "Grape" to 5)

这里的键值对组合看上去好像是使用to这个关键字来进行关联的,但其实to并不是关键字,而 是一个infix函数。

Lambda

首先来看一下Lambda的定义,如果用最直白的语言来阐述的话,Lambda就是一小段可以作为参数传递的代码。从定义上看,这个功能就很厉害了,因为正常情况下,我们向某个函数传参时只能传入变量,而借助Lambda却允许传入一小段代码。这里两次使用了“一小段代码”这种描述,那么到底多少代码才算一小段代码呢?Kotlin对此并没有进行限制,但是通常不建议在 Lambda表达式中编写太长的代码,否则可能会影响代码的可读性。 接着我们来看一下Lambda表达式的语法结构:

{参数名1: 参数类型, 参数名2: 参数类型 -> 函数体}

这是Lambda表达式最完整的语法结构定义。首先最外层是一对大括号,如果有参数传入到 Lambda表达式中的话,我们还需要声明参数列表,参数列表的结尾使用一个->符号,表示参数列表的结束以及函数体的开始,函数体中可以编写任意行代码(虽然不建议编写太长的代 码),并且最后一行代码会自动作为Lambda表达式的返回值。

一些简化

  • 我们不需要专门定义一个lambda变量,而是可以直接将lambda表达式传入maxBy函数当中。
  • Kotlin规定,当Lambda参数是函数的最后一个参数时,可以将Lambda表达式移到函数括号的外面
  • 如果Lambda参数是函数的唯一一个参数的话,还可以将函数的括号省略
  • 由于Kotlin拥有出色的类型 推导机制,Lambda表达式中的参数列表其实在大多数情况下不必声明参数类型
  • 当Lambda表达式的参数列表中只有一个参数时,也不必声明参数名,而是可以使用it 关键字来代替