第一行代码Kotlin编程
第二章:快速入门Kotlin
编程
2.5 面向对象的编程
2.54 数据类与单例类
在一个规范的系统架构中,数据类通常占据非常重要的角色,它们用于将服务器或数据库中的数据映射到内存中,为编程逻辑提供数据模型的支持。
在Kotlin
中,只需要在类的前面加上data
关键词即可
1 | data class Cellphone(val brand: String, val price: Double) |
只要声明了data
关键词,则这个类就是数据类
测试代码
1 | fun main() { |
当一个类中没有任何代码时,还可以将尾部的大括号省略
Kotlin
特有的功能——单例类
在Kotlin
中创建一个单例类只需要将class
关键字改成object
关键字即可
1 | object Singleton { |
此时的函数已经是单例类,我们只需要在函数里编写方法即可
1 | object Singleton { |
调用单例类中的方法代码实现
1 | Singleton.singletonTest() |
2.6 Lamda
编程
2.6.1 集合创建与遍历
集合主要包括List
和Set
,广泛一点就包括Map
这样的键值对数据结构。
List
,Set
和Map
在java中都是接口,List
的主要实现类是ArrayList
和linkedList
,Set
的主要实现方法时HashSet
,Map
的主要实现类是 HashMap
现在提出创建一个包含许多水果名称的集合需求。如果是在java中,会先创建一个ArrayList
实例,然后再一个个添加。
Kotlin
提供一个内置的listOf()
函数来简化初始化集合的写法。如下:
1 | fun main() { |
注意点
listOf()
创建的是一个不可变的集合。不可变集合指的是该集合只能用于读,不能对集合进行添加,修改,或删除操作。
如果需要创建一个可变的集合,可以使用mutableListOf()
函数就可以了。
1 | fun main() { |
Set
集合的用法与List
的一模一样,只是将创建集合的方式换成了setOf()
和mutableSetOf()
函数。
1 | fun main() { |
注意点
Set
集合中是不可以存放重复元素的,如果存放了多个相同的元素,只会保留其中的一份。
在Kotlin
中不推荐使用put()和get()方法来对Map
进行添加和读取数据操作,而是推荐使用一个类似于数组下标的语法结构
添加一条数据
1 | map["apple"] = 1 |
读取一条数据
1 | val number = map["apple"] |
Kotlin
同样提供了mapOf()
和mutableMapOf()
函数继续简化Map
用法
1 | fun main() { |
其实to
不是关键字,而是一个infix
函数。这段代码的只要区别在于,在for-in
循环中,我们将Map
的键值对变量一起声明到了一对括号中,这样当进行循环遍历时,每次遍历的结果就会赋值给这两个键值对变量。
2.6.2 集合的函数式API
重点学习函数式API的语法结构,也就是Lamda
表达式的语法结构
如何在一个水果集合里面找到单词最长的那个水果?
使用集合的函数式API
1 | val list = listOf("Apple","Banana","Orange","Pear","Grape","Watermelon") |
Lamda
就是一小段可以作为参数传递的代码,正常情况下,我们向某个函数传参时只能传入变量,而借助Lamda
允许传入一小段代码
Lamda
的语法结构
1 | {参数名1:参数类型,参数名2:参数类型 -> 函数体} |
如果有参数传入到Lamda
表达式,需要声明参数列表,参数列表结束接函数体。函数体中可以编写任意行代码。最后一行代码会自动作为Lamda
表达式的返回值
没有化简时的代码
1 | val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon") |
不需要额外定义一个lamda
变量
1 | val maxLengthFruit = list.maxBy({ fruit: String -> fruit.length }) |
Kotlin
中规定,当Lamda
参数是函数的最后一个参数时,可以将Lamda
表达式移到函数括号外面。
1 | val maxLengthFruit = list.maxBy(){ fruit: String -> fruit.length } |
Lamda
参数是函数的唯一一个参数时,函数括号可以省略
1 | val maxLengthFruit = list.maxBy{ fruit: String -> fruit.length } |
由于Kotlin
出色的类型推导机制,Lamda
表达式中的参数列表在大多数情况下不必声明参数类型
1 | val maxLengthFruit = list.maxBy { fruit -> fruit.length } |
当Lamda
表达式的参数列表中只有一个参数时,不必声明参数名,直接使用it
关键字代替
1 | val maxLengthFruit = list.maxBy { it.length } |
map
函数
集合中map
函数是最常用的一种函数式API
,用于将集合中的每个元素都映射成一个另外的值,映射的规则在Lamda
表达式中指定,最终生成一个新的集合。
比如希望所有水果名都变成大写模式
1 | fun main() { |
map
函数可以按照我们的需求对集合中的元素进行任意的映射转换
filter
函数
用来过滤集合中的数据,可以单独使用,也可以配合map
函数使用
保留5个字母以内的水果
1 | fun main() { |
先是调用了filter
函数再调用map
函数,如果先调用map
函数再调用filter
函数,效率会差很多
any
和all
函数
any****函数用于判断集合中是否至少存在一个元素满足条件
all****函数用于判断集合中是否所有元素都满足指定条件
1 | fun main() { |
2.6.3 Java函数式API的使用
Java原生API中最常见的但抽象方法接口–Runnable接口
Java代码写法
1 | new Thread(new Runnable(){ |
这里使用匿名内部类的写法。Kotlin
版本
1 | Thread(object : Runnable{ |
Kotlin
完全舍弃了new 关键字,而是使用object关键字。
简化代码
1 | Thread(Runnable { |
因为Runnable类中只要一个待实现的方法,即使这里没有显式地重写run方法,Kotlin
也能自动明白Runable
后面的Lambda表达式就是要在run方法中实现的内容。
如果Java方法的参数列表中有且只有一个Java但抽象方法接口参数,可以直接将Runnable接口名省略
1 | Thread({ |
当Lamda
表达式是方法的最后一个参数时,可以将Lamda
表达式移到方法括号的外面。如果Lamda
还是方法的唯一一个参数,直接将括号省略
1 | Thread { |
Java函数式API使用例子:
Android中常用的点击事件接口OnclickListener
使用Kotlin
函数式API的写法就可以简化成
1 | button.setOnClickListener{} |
2.7空指针检查
2.7.1 可空类型系统
Kotlin
非常科学的解决了这个问题,它利用编译时判空检查的机制几乎杜绝了空指针异常
Kotlin
默认所有的参数和变量都不为空,所以传入参数一定也也不为空。
Kotlin
将空指针异常的检查提前到了编译时期,如果程序存在空指针异常的风险,那么在编译的时候就会直接报错,修正才可以正常运行。
如果需要某个参数或者变量为空,可以在类名后面加上一个问号。
例如,**Int
表示为不为空的整型,而Int
?就表示可为空的整型。**
这时调用参数方法可能出现空指针异常。这里直接进行判断处理就行
2.7.2 判空辅助工具
?. 操作符。这个操作符就是当对象不为空时正常调用相应方法,当对象为空就什么都不做。
1 | if(study!=null) { |
使用操作符
1 | fun doStudy(study: Study?) { |
?: 操作符。这个操作符左右两边都接受一个表达式,如果左边的表达式的结果不为空就返回左边的结果,否则就****返回右边表达式的结果
例子:****没有使用关键字
1 | val c = if(a!=null){ |
使用 ?: 关键字
1 | val c = a?:b |
结合两个关键字使用。
1 | //获得一段文本长度 |
使用操作符简化
1 | fun getTextLength(text: String?) = text?.length ?: 0 |
!! 非空断言工具,确信这里的对象不为空,强行通过编译。
使用场景
1 | fun main(){ |
这里再content.toUpperCase()
编译无法通过,虽然在外部进行非空判断,
但是在函数里面不知道外部做了非空判断。
可以使用非空断言工具,在对象后面加上!!
1 | fun printUpperCase(){ |
这样写就是让编译器不用做空指针检查了,出现问题就直接抛出空指针异常。
let函数。这个函数提供函数式API接口,并将原始调用对象作为参数传递到Lamda
表达式中。
1 | obj.let{obj2-> |
这里调用了obj
的let函数,然后Lamda
表达式的代码会立即执行。并且这个obj
对象本身还会作为参数传递到Lamda
表达式中。let函数属于kotlin
中的标准函数。
下面这段代码其实进行了两次if判断语句。由于?.操作符的限制必须对每个对象进行一次判断
1 | fun doStudy(study: Study?) { |
结合使用?.和let函数来对代码进行优化
1 | fun doStudy(study: Study?) { |
?.操作符表示对象为空时什么都不做,对象不为空就调用let函数,而let函数会将study对象本身作为参数传入Lamda表达式中。此时对象肯定不为空。Lamda
表达式只有一个参数时,不用声明。
1 | fun doStudy(study: Study?) { |
let函数处理全局变量的判空问题,if无法做到这点。
因为全局变量的值随时都有可能被其他线程所修改,即是做了判空处理仍然无法保证if语句中的study变量没有空指针风险。使用let可以正常工作。
原本study是方法参数时,是可以编译通过的。