- 本篇参考资料《第一行代码 第三版》 2020.4月出版
- 本篇文章只是本人看书的理解和整理的笔记,更完整的内容还在书上
- 尊重原作者 请购买正版图书
现在许多android项目都是使用的kotlin来做,但同时java项目也不在少数
对于android原生来说,两种语言都需要掌握,由于本书都是kotlin讲解,所以先学kotlin,讲书中内容看完后再想想Java怎么实现
虽然曾今在项目中使用过kotlin,但是是参照着java来使用的,缺少系统性的学习过程,这里补充上
一.变量和函数
1.变量
Kotlin中定义一个变量,只允许在变量前声明两种关键字:val和var。
val(value的简写的简写)用来声明一个不可变的变量,这种变量在初始赋值之后就再也不能重新赋值,对应Java中的final变量。
var(variable的简写的简写)用来声明一个可变的变量,这种变量在初始赋值之后仍然可以再被重新赋值复制,对应Java中的非final变量。
例子
fun main(){
val a=1
var b=2
println(a)
println(b)
}
但是我们在声明变量的时候必须要给它一个初始值或者类型,这样kotlin才能进行推导
//不报错
var b=2
//不报错
var b:Int
b=2
//报错
var b
b=2
java中的每一个基本数据类型对应在kotlin中就是首字母进行大写:
//java
int a;
//kotlin
var a:Int
同理:
2.函数function
Kotlin定义一个函数的语法规则如下:
fun methodName(param1: Int, param2: Int): Int {
return 0
}
fun(function的简写)是定义函数的关键字,无论你定义什么函数,都一定要使用fun来声明。
紧跟在fun后面的是函数名methodName,良好的编程习惯是函数名最好要有一定的意义,能表达这个函数的作用是什么。
函数名后面的一对括号中,可以声明该函数接收什么参数。
括号后面的部分是可选的,用于声明该函数会返回什么类型的数据。如果不需要返回任何数据,这部分可以不写。
两个大括号之间的内容就是函数体了,可以在这里编写函数的具体逻辑。
例:
fun main(){
print(largerNumber(1,2))
}
fun largerNumber(num1:Int,num2: Int):Int{
return max(num1,num2);
}
当一个函数的函数体中只有一行代码时,可以简写(单行代码函数的语法糖):
fun largerNumber(num1:Int,num2: Int) = max(num1,num2)
直接将唯一的一行代码写在函数定义的尾部,中间用等号连接即可
return关键字也可以省略,等号足以表达返回值的意思
Kotlin拥有出色的类型推导机制,可以自动推导出返回值的类型
二.逻辑控制
kotlin 中条件控制语句和java基本上一样
1. if 条件语句
Kotlin中的if语句相比于Java有一个额外的功能:它是可以有返回值的,返回值就是if语句每一个条件中最后一行代码的返回值。
fun largerNumber(num1: Int, num2: Int): Int {
val value = if (num1 > num2) {
num1
} else {
num2
}
return value
}
简写:
fun largerNumber(num1: Int, num2: Int): Int {
return if (num1 > num2) {
num1
} else {
num2
}
}
再简写
fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) {
num1
} else {
num2
}
再再简写
fun largerNumber(num1: Int, num2: Int) = if (num1 > num2) num1 else num2
2.when条件语句
when 是kotlin新增的一个条件判断语句,相当于java中switch的升级版
用法:
when(传入值){
匹配值1->{执行语句}
匹配值2->{执行语句}
匹配值2->{执行语句}
...
else -> {执行语句}
}
例子
fun getScore(name: String) = when (name) {
"Tom" -> 86
"Jim" -> 77
"Jack" -> 95
"Lily" -> 100
else -> 0
}
这里我们可以看到when也是有返回值的,返回值就是对应的执行语句的返回值
除了精确匹配之外,when语句还允许进行类型匹配
fun checkNumber(num: Number) {
when (num) {
is Int -> println("number is Int")
is Double -> println("number is Double")
else -> println("number not support")
}
}
这里的代码中is
就是匹配数据类型的关键字
when还有一种不带参数的用法
例子:叫所有名字为T开头人都是穿的红衣服
fun fun1(name:String)=when{
name.startsWith("T")-> print("红衣服")
name=="Jack"-> print("绿衣服")
name=="Rose"-> print("粉衣服")
else-> print("白衣服")
}
3.for-in循环语句
这里又和java有些许区别
在学习循环语句之前补充一个小知识
我们可以使用如下Kotlin代码来表示一个区间:
val range = 0..10
上述代码表示创建了一个0到10的区间,并且两端都是闭区间,这意味着0到10这两个端点都是包含在区间中的,用数学的方式表达出来就是[0, 10]。
也可以使用until关键字来创建一个左闭右开的区间:
val range = 0 until 10
上述代码表示创建了一个0到10的左闭右开区间,它的数学表达方式是[0, 10)。有了区间之后,我们就可以通过for-in循环来遍历这个区间:
fun main() {
for (i in 0..10) {
print("$i ")
}
}
/*运行结果
0 1 2 3 4 5 6 7 8 9 10
*/
这里用到了美元占位符
${}
在字符串中为变量占位,大括号内是语句则大括号不可省略${list.size}
, 如果是单个变量则可以省略
在for in语句中我们可以使用step
关键字来设置步幅
fun main() {
for (i in 0 until 10 step 2) {
print("$i ")
}
}
/*
运行结果
0 2 4 6 8
*/
之前的..
和until
都是升序的区间,如果想要降序的可以使用downTo
关键字
fun main(){
for (i in 10 downTo 1) {
print("$i ")}
}
运行结果:
10 9 8 7 6 5 4 3 2 1
三.面向对象编程
1.类与对象
可以使用如下代码定义一个类,以及声明它所拥有的字段和函数:
class Person {
var name = ""
var age = 0
fun eat() {
println(name + " is eating. He is " + age + " years old.")
}
}
然后使用如下代码创建对象,并对 对象进行操作:
fun main() {
val p = Person()
p.name = "Jack"
p.age = 19
p.eat()
}
2.继承和构造函数
Kotlin中一个类默认是不可以被继承的,如果想要让一个类可以被继承,需要主动声明open关键字:
open class Person {
…
}
要让另一个类去继承Person类,则需要使用冒号关键字:
class Student : Person() {
var sno = ""
var grade = 0
}
现在Student类中就自动拥有了Person类中的字段和函数,还可以定义自己独有的字段和函数。
这里可以看到,对比java的继承除了将extends改为冒号之外,父类还多了个括号,这里关系到主构造函数和次构造函数登知识
主构造函数是将会是最常用的构造函数,每个类都会有一个默认的不带参数的主构造函数,当然也可也为它指明参数
fun main(){
var student= Student("20162919",89)
student.eat()
}
class Student(var son:String,var grade:Int): Person() {
}
可以看到,主构造函数和java不同,是没有函数体的,直接接在了类名后边,那我们就是想在主构造函数中加入一些逻辑该怎么做呢?可以使用init
关键字
class Student(var son:String,var grade:Int): Person() {
init {
print(son)
print(grade)
}
}
JAVA规定:子类中的构造函数必须调用父类的构造函数
Kotlin也要遵守这个规则,但是Kotlin中主构造函数没有函数体,这该怎么调用呢?
所以我们可以看到以上例子中在父类Person后方加上了括号,来调用父类的构造函数
那比如我们将父类的主构造函数也”重写“一下,加上参数:
open class Person (var name:String,var age:Int){
fun eat() {
println("$name is eating. He is $age years old.")
}
}
在子类中就需要这么写,为父类构造函数传入参数name,age:
class Student(var son:String,var grade:Int, name:String, age:Int): Person(name,age) {
init {
print(son)
print(grade)
}
}
注意:Student的主构造函数中我们可以看到name和age前边没有加val或var,这是因为父类中的同名字段已经存在,会导致冲突
接下来我们可以这样来创建Student实例
var student= Student("20162919",89,"Jack",18)
次构造函数:任何一个类只能有一个主构造函数但可以有多个次构造函数,次构造函数也可以用于创建实例,但是Kotlin中提供了给函数参数设定默认值的功能,可以替代次构造函数,所以基本上用不到
次构造函数使用关键字'constructor'
例1
//这里是有主构造函数的,只是没有加参数
class Student(): Person() {
var son=""
var grade=0
constructor(son:String,grade:Int):this(){
}
}
fun main(){
var s1= Student()
var s2=Student("20162919",2)
}
Kotlin规定,次构造函数必须调用主构造函数,这里通过'this'来调用
例2
fun main(){
var s1= Student("boy")
var s2=Student("20162919",2)
var s3=Student("20162919",2,"boy")
}
class Student(var sex:String): Person() {
var son=""
var grade=0
//次构造函数没有主构造函数的值时需要给一个默认值
constructor(son:String,grade:Int):this(""){
}
//次构造函数也可以包含主构造函数的参数直接赋值
constructor(son:String,grade:Int,sex:String):this(sex){
}
}
接下来看一个特殊情况:一个类只有次构造函数没有主构造函数,则需要在次构造函数中通过super
调用父类的构造函数(没有主动定义次构造函数时,有一个默认的无参主构造函数)
例3
class Student: Person {
var son=""
var grade=0
constructor(son:String,grade:Int):super(){
this.son=son
this.grade=grade
}
}
仔细比较例1和例3的区别,可以看到Person后边的括号没了,这是因为子类没有主构造函数,所以不需要调用父类的构造函数,但是需要在次中调用
3.接口
Kotlin中定义接口的关键字和Java中是相同的,都是使用的interface:
interface Study {
fun readBooks()
fun doHomework()
}
而Kotlin中实现接口的关键字变量了冒号,和继承使用的是同样的关键字:
class Student: Person(),Study {
override fun readBooks() {
println("$name is reading.")
}
override fun doHomework() {
println("$name is doing homework.")
}
}
接下来我们在Kotlin中体会一下面向接口编程,也叫多态:
fun main(){
var student=Student()
student.name="Jack"
doStudy(student)
}
fun doStudy(study:Study){
study.doHomework()
study.readBooks()
}
运行结果:
Jack is doing homework.
Jack is reading.
我们可以在接口中增加默认实现:
interface Study {
fun readBooks()
fun doHomework(){
//接口中的默认实现
println("doHomework default")
}
}
这样我们在Student中删除doHomework也不会报错,这个java JDK1.8也支持
4.函数和变量的修饰符
JAVA中有修饰符(复习一下):
default
(即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
private
: 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
public
: 对所有类可见。使用对象:类、接口、变量、方法
protected
: 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
修饰符 | JAVA | Kotlin |
---|---|---|
public | 所有类可见 | 所有类可见(默认) |
private | 在同一类内可见 | 在同一类内可见 |
protected | 对同一包内的类和所有子类可见 | 当前类可见,子类可见 |
default | 在同一包内可见(默认) | 无 |
internal | 无 | 同一模块的类可见 |
5.数据实体类
我们知道在java中写一个实体类,除了其中的各种属性外,还需要get/set方法,toString方法,hashCode,equals这些重复意义很小的方法往往导致一个实体类有上百行
比如
public class Cellphone{
private String brand;
private double price;
...
属性多的话,会有上百行的get/set 和其他,并且不好修改
...
}
但是在Kotlin中我们只需要一个关键字data
一行代码就搞定,没有其他代码时甚至可以省略大括号
data class Cellphone(var brand:String,var price:Double)
fun main(){
var phone1=Cellphone("Samsung",100.99)
var phone2=Cellphone("Xiaomi",200.99)
print(phone1.equals(phone2))
print(phone1.toString())
}
6.单例模式
单例模式是非常常用的一种设计模式,它要求某个类在全局只有一个对象,避免重复创建对象
在java中实现单例模式需要私有化构造方法,对外部提供一个静态方法来获取实例(如果是第一次调用则创建实例并保存,其他情况则返回第一次调用时创建的实例)
以上虽然简单,但是似乎不怎么优雅,kotlin明显做的更好
kotlin创建单例步骤:右键包名-->new -->Kotlin File/Class 输入类名,选择“Object”
object Singleton {
fun singletonTest(){
print("这里是单例模式内的方法")
}
}
//调用单例模式内的方法
fun main(){
Singleton.singletonTest()
}
五.Lambda编程
Lambda就是一小段可以作为参数传递的代码。正常情况下,我们向某个函数传参时只能传入变量,而借助Lambda却允许传入一小段代码。(本人疑惑 匿名函数?)
Lambda表达式的语法结构:
{参数名1: 参数类型, 参数名2: 参数类型 -> 函数体}
首先最外层是一对大括号,如果有参数传入到Lambda表达式中的话,我们还需要声明参数列表,参数列表的结尾使用一个->符号,表示参数列表的结束以及函数体的开始,函数体中可以编写任意行代码,并且最后一行代码会自动作为Lambda表达式的返回值。
5.1集合的使用
为了学习Lambda,这里补充以下集合的使用
集合通常是指List,Set,Map这些,但这些z在java中都是接口,实际用到的是接口的实现类
List--->ArrayList,LinkedList
Set--->HashSet
Map--->HashMap
这是kotlin创建一个ArrayList的方式:
val list=ArrayList();
list.add("Apple")
list.add("Banana")
list.add("Orange")
list.add("Pear")
list.add("Grape")
list.add("Watermelon")
当然对于这种由初始数据的List,kotlin提供了一个方法listOf
和mutableListOf
两个方法来创建
val list1= listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val list2= mutableListOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
listOf
和mutableListOf
的区别:
listOf
: 创建一个不可变集合,不可改变不能进行add操作,只能读取
mutableListOf
:创建一个可变集合,可以任意改变
List的遍历:
val list2= mutableListOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
for(fluit in list2){
print(fluit)
}
和java中for循环的冒号用法很相似
for(String fluit : list2){...}
Map的用法
kotlin中创建map,当然也可也用java的put/get 添加和读取数据
val map=HashMap()
map.put("Apple",1)
map.put("Banana",2)
map.put("Orange",3)
map.put("Pear",4)
map.put("Grape",5)
但是kotlin更推荐不使用put/get,而是改为以下方式:
//添加
map["Apple"] = 1
//读取
val num=map["Apple"]
kotlin页提供了mapOf
(不可变) 和 mutableMapOf
(可变)
val map1= mapOf("Apple" to 1,"Banana" to 2)
val map2= mutableMapOf("Apple" to 1,"Banana" to 2)
//遍历
for ((fruit,num) in map){
print(fruit+" "+num)
}
5.2 集合函数式API
集合函数时API有很多个,重点是学习用法,比如这里提一个需求找出List中单词最长的水果,我们会很自然的写出如下的代码:
var maxLengthFruit=""
for(fruit in list){
if(fruit.length>maxLengthFruit.length){
maxLengthFruit=fruit
}
}
print("最长水果名$maxLengthFruit")
但是如果使用集合函数API会更加简洁:
val list= mutableListOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
var maxLengthFruit=list.maxBy { it.length }
print("最长水果名$maxLengthFruit")
这个例子比较难懂,以下来分析一下:
这里的函数式API maxBy实际上只是接收了一个Lambda类型的参数,然后在遍历集合的同时,会将每次遍历的结果传递给Lambda表达式,maxBy函数的工作原理就是根据传入的条件来遍历集合,找到该条件下的最大值
因此,结合Lambda表达式的语法结构{参数名1: 参数类型, 参数名2: 参数类型... -> 函数体}
,刚刚的例子我们可以这样写:
val list= mutableListOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val lambda={fruit:String->fruit.length}
var maxLengthFruit=list.maxBy(lambda)
print("最长水果名$maxLengthFruit")
可以看到maxBy实际上就是接收了一个lambda参数,接下来我们可以将2,3行简化一点点:
var maxLengthFruit=list.maxBy({fruit:String->fruit.length})
Kotlin规定当Lambda表达式是函数的最后一个参数时,可以将lambdy移到括号外边(这里maxBy的最后一个参数就是lambdy表达式):
var maxLengthFruit=list.maxBy(){fruit:String->fruit.length}
然后当lambda时,括号可以省略
var maxLengthFruit=list.maxBy{fruit:String->fruit.length}
因为kotlin有出色的类型推导功能,所以不必声明参数的类型,再简化:
var maxLengthFruit=list.maxBy{fruit->fruit.length}
最后,当lambdy只有一个参数时,也不必声明参数,可以使用'it'关键字替代,所以再简化:
var maxLengthFruit=list.maxBy{it.length}
这样就变成我们之前看到的样子了,同时为了保留可读性,不建议将lambdy表达式写的过长
其他常用的集合函数式API:
map()
集合中的map函数是最常用的一种函数式API,它用于将集合中的每个元素都映射成一个另外的值,映射的规则在Lambda表达式中指定,最终生成一个新的集合。比如,这里我们希望让所有的水果名都变成大写模式,就可以这样写:
fun main() {
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val newList = list.map({ fruit: String -> fruit.toUpperCase() })
for (fruit in newList) {
println(fruit)
}
}
map的功能很强大,可以根据任意规则来转换,可以试试转换全小写,取首字母登操作
filter()
filter是根据传入的条件来过滤集合的函数式API,例如我们要取水果名称长度小于五的所有水果:
val list= mutableListOf("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon")
val newlist=list.filter { it.length<5 }
当然我们也可以结合map等其他API一起使用:
//注意:本来filter和map的顺序是不会影响结果的,但是先调用fliter再map可以过滤掉一些元素,提高效率
val newlist=list.filter { it.length<5 }.map{it.toUpperCase()}
any()
和'all()'
返回一个Boolean值,'any'判断集合中是否存在元素满足某条件,'all'判断集合是否所有元素都满足某条件
val myAnyResult=list.any{it.length<5}
val myAllResult=list.any{it.length<5}
六.空指针检查
空指针是一种不受编程语言检查的运行时异常,只能由程序员主动通过逻辑判断来避免,但即使是最出色的程序员,也不可能将所有潜在的空指针异常全部考虑到。
public void doStudy(Study study) {
study.readBooks();
study.doHomework();
}
这段Java代码安全吗?不一定,因为这要取决于调用方传入的参数是什么,如果我们向doStudy()方法传入了一个null参数,那么毫无疑问这里就会发生空指针异常。因此,更加稳妥的做法是在调用参数的方法之前先进行一个判空处理,如下所示:
public void doStudy(Study study) {
if (study != null) {
study.readBooks();
study.doHomework();
}
}
Kotlin中引入了一个可空类型系统的概念,它利用编译时判空检查的机制几乎杜绝了空指针异常。
fun doStudy(study: Study) {
study.readBooks()
study.doHomework()
}
这段代码看上去和刚才的Java版本并没有什么区别,但实际上它是没有空指针风险的,因为Kotlin默认所有的参数和变量都不可为空,所以这里传入的Study参数也一定不会为空,可以放心地调用它的任何函数。
这里强行传入null,编译则不会通过,IDE会报错:
Kotlin提供了另外一套可为空的类型系统,就是在类名的后面加上一个问号。比如,Int表示不可为空的整型,而Int?就表示可为空的整型;String表示不可为空的字符串,而String?就表示可为空的字符串。
比如我这样:
fun doStudy(study:Study?){
if(study!=null){
study.doHomework()
study.readBooks()
}
}
则IDE不会报错:
使用可为空的类型系统时,需要在编译时期就把所有的空指针异常都处理掉才行,就像刚刚的if
判断操作。但是这种方式显得很啰嗦不优雅,为了应对这种情况,Kotlin准备了一系列工具,使开发者能更方便的进行空处理:
?
操作符
1
?
操作符表示当对象不为空时正常调用相应的方法,当对象为空时则什么都不做。比如:
if (a != null) {
a.doSomething()
}
这段代码使用?.操作符就可以简化成:
a?.doSomething()
上边的代码就可以简化:
fun doStudy(study:Study?){
study?.doHomework()
study?.readBooks()
}
2
?
操作符表示如果左边表达式的结果不为空就返回左边表达式的结果,否则就返回右边表达式的结果。比如:
val c = if (a ! = null) {
a
} else {
b
}
这段代码的逻辑使用?:操作符就可以简化成:
val c = a ?: b
比如我们想获取一个字符串的长度,如果为空就输出0,本来该用if判断的
fun getTextLength(text:String?):Int{
if(text!=null){
return text.length
}
return 0
}
但其实简化写法可以这样():
fun getTextLength(text:String?)=text?.length?:0
!!
操作符
在Kotlin中 有时候我们已经确定某个变量不为空,我们已在逻辑上处理了,但是Kotlin不能检测到,这个时候就需要使用!!
操作符,强制告诉系统,这个变量不为空
比如这里定义了一个全局变量content
,然后进行判空,再调用打印大写函数,出现了报错
这个时候我们可以加
!!
来处理:
var content:String?="hello"
fun main(){
if(content!=null){
printUpperCase()
}
}
fun printUpperCase(){
print(content!!.toUpperCase())
}
但这其实是一个不安全的操作方式,我们自己来承担空指针的后果,会为空指针崩溃埋下隐患
那如果是这样:
fun printUpperCase(){
print(content?.toUpperCase())
}
则是空指针安全,工作中尽量少用!!
这种不安全的操作
七 Kotlin的其他语法糖
1.$
美元符号占位符,之前已经讲过了,不再赘述
2.给函数的参数设置默认值
Kotlin允许在定义函数的时候给任意参数设定一个默认值,这样当调用此函数时就不会强制要求调用方为此参数传值,在没有传值的情况下会自动使用参数的默认值。语法格式如下:
fun printParams(num: Int, str: String = "hello") {
println("num is $num , str is $str")
}
这里给printParams()函数的第二个参数设定了一个默认值,这样当调用printParams()函数时,可以选择给第二个参数传值,也可以选择不传,在不传的情况下就会自动使用默认值。
fun main(){
printParams(123)
printParams(123,"world")
}
那如果我们声明函数的时候讲默认值放在第一位呢,这样只传123试试
fun printParams( str: String = "hello",num: Int) {
println("num is $num , str is $str")
}
这样会报错,第一个参数类型不匹配,那么我们该怎么解决这个问题呢?
Kotlin提供了另一种传参方法,就是指定参数名,不考虑参数位置:
现在可以思考一下,之前提到的主构造函数和次构造函数,为什么说我们基本上不会用次构造函数?
因为我们既然在次构造函数中也需要调用主构造函数,并给一个初始值,那么我们完全可以通过给主构造函数参数初始值的方式来替代使用次构造函数
之前的例子
fun main(){
var s1= Student("boy")
var s2=Student("20162919",2)
var s3=Student("20162919",2,"boy")
}
class Student(var sex:String): Person() {
var son=""
var grade=0
//次构造函数没有主构造函数的值时需要给一个默认值
constructor(son:String,grade:Int):this(""){
}
//次构造函数也可以包含主构造函数的参数直接赋值
constructor(son:String,grade:Int,sex:String):this(sex){
}
}
//分别调用了主构造函数和两个次构造函数
var student1a=Student1("boy")
var student1b=Student1("1019",100)
var student1c=Student1("1019",100,"girl")
可以简化为
class Student2(var son:String="",var grade:Int=0,var sex:String="") {
}
//类似于上方的 分别调用了主构造函数和两个次构造函数
var student2a=Student2(sex="boy")//指定字段名
var student2b=Student2("boy",11)//没有默认值的字段必须要赋值
var student2c=Student2("1019",100,"girl")
书上的第二章 Kotlin到这里就讲完了,不得不说郭神真的讲的太细致了,有大腿带着感觉就是不一样
八.各章Kotlin小课堂内容整理
8.1 第三章 具体用法参考P130
8.1.1 标准函数with、run和apply
with
函数接收两个参数:第一个参数可以是一个任意类型的对象,第二个参数是一个Lambda表达式。with函数会在Lambda表达式中提供第一个参数对象的上下文,并使用Lambda表达式中的最后一行代码作为返回值返回。
示例代码如下:
val result = with(obj) {
// 这里是obj的上下文
"value" // with函数的返回值
}
run
函数的用法和使用场景其实和with函数是非常类似的,只是稍微做了一些语法改动而已。首先run函数是不能直接调用的,而是一定要调用某个对象的run函数才行;其次run函数只接收一个Lambda参数,并且会在Lambda表达式中提供调用对象的上下文。其他方面和with函数是一样的,包括也会使用Lambda表达式中的最后一行代码作为返回值返回。
示例代码如下:
val result = obj.run {
// 这里是obj的上下文
"value" // run函数的返回值
}
apply
函数和run函数也是极其类似的,都是要在某个对象上调用,并且只接收一个Lambda参数,也会在Lambda表达式中提供调用对象的上下文,但是apply函数无法指定返回值,而是会自动返回调用对象本身。
val result = obj.apply {
// 这里是obj的上下文
}
// result == obj
8.1.2 定义静态方法
Kotlin没有直接定义静态方法的关键字,但是提供了一些语法特性来支持类似于静态方法调用的写法,如单例类,companion object等,这些语法特性基本可以满足我们平时的开发需求了。
然而如果你确确实实需要定义真正的静态方法, Kotlin仍然提供了两种实现方式:注解和顶层方法。
注解:
单个方法需要定义为静态
class Util {
companion object {//companion object可以让方法类似于静态方法的使用 但不是真的静态
@JvmStatic//加了这个注释就是真正的静态方法了
fun doAction() {
println("do action2")
}
}
}
所有方法需要定义为静态,直接使用单例模式:
object Util {
fun doAction() {
println("do action2")
}
}
顶层方法:不存在于任何类中的方法,在Kotlin中任何位置直接使用
fun doSomething() {
println("do something")
}
8.2 第四章 延迟初始化和密封类
8.2.1 lateinit
延迟初始化
Kotlin定义变量时都必须要初始化
不赋值报错
赋值不报错,哪怕为null
这其实是很优秀的涉及,但是我们就是想要延迟一会再初始化该怎么做?
使用
lateinit
关键字
lateinit var a: String
注意1:延迟初始化需要指定变量类型
注意2:要确保变量在被使用前已经被初始化,否则程序崩溃
例子:
class MainActivity : AppCompatActivity(), View.OnClickListener {
private lateinit var adapter: MsgAdapter
override fun onCreate(savedInstanceState: Bundle?) {
…
adapter = MsgAdapter(msgList)
…
}
override fun onClick(v: View?) {
…
adapter.notifyItemInserted(msgList.size - 1)
…
}
}
8.2.2 使用密封类优化代码 P204
8.3 第五章 扩展函数和运算符重载 P234
8.3.1 扩展函数
扩展函数表示即使在不修改某个类的源码的情况下,仍然可以打开这个类,向该类添加新的函数。
其语法结构非常简单,如下所示:
fun ClassName.methodName(param1: Int, param2: Int): Int {
return 0
}
相比于普通函数,只需要在函数名前加上ClassName.
就可以将函数扩展到其中了
例子,往String
类中加入一个判断字符串有多少个字母的函数:
//在类同名的.kt文件中 方便管理
//创建顶层方法 任何位置都可以调用
fun String.lettersCount():Int{
var count=0
//作为String类的扩展函数,代码自动获得其上下文
//通过this即可调用,不需要再传递参数
for(char in this){
if(char.isLetter()){
count++
}
}
return count
}
用法:
public fun doSomething(){
"12345ABCDEF".lettersCount()
}
注意点1:
扩展函数虽然可以在任何位置创建,但是最好在和类名同名的kt文件中创建顶层函数
这样既方便管理,又在所有地方都可以调用
注意点2:this的使用(代码注释)
注意点3:什么场景需要使用扩展函数?一般是我们无法修改的类,比如String这种内置的类。当然,扩展函数可以作用于任何类,不要被局限了
8.3.2 运算符重载
Kotlin的运算符重载允许我们让任意两个对象
进行相加,或者是进行更多其他的运算操作。
这里以加号运算符为例,如果想要实现让两个对象相加的功能,那么它的语法结构如下:
class Obj {
operator fun plus(obj: Obj): Obj {
// 处理相加的逻辑
}
}
例子:
虽然任意两个对象相加,比如水果对象 香蕉+苹果 可以实现,但是有时候这种相加看起来没有意义。可是如果是钱的对象Money呢。比如我一个钱的对象Money又两个属性一个value表示钱的数目,另一个currency表示币种。
//数据实体 money,包含两个属性
data class Money(var value: Int, var currency: String) {
//定义money加法 使用operator关键字和“plus”方法名
operator fun plus(money: Money): Money {
//定义加法计算逻辑
val sum = value + money.value
//返回计算后的结果 新的money对象
return Money(sum, currency)
}
}
这里边我们定义了plus方法就是money的加法,用法如下:
public fun doSomething(){
var money1= Money(10,"$")
var money2= Money(5,"$")
var money3=money1+money2
print(money3.value)
}
注意点1: operator
关键字不可少
注意点2:我们通过plus方法名定义了加法,但其实不光可以定义加法,还可以通过不同方法名为对象定义任何计算法
算法和方法名对应表如下: