# Kotlin
变量和函数
变量
-
如果代码要定义一个变量,需要在变量前面声明这个变量的类型,比如
-
int a = 1;String b = "杜金亮";
-
-
而在中定义一个变量,只允许在变量前声明两种关键字:
val
和var
-
val
(value的简写)用来声明一个不可变的量,这种变量在初始赋值之后就再也不能重新赋值,对应Java中
final
变量 -
var
(variable的简写)用来声明一个可变的变量,这种变量在初始赋值之后仍然可以再被重新赋值,对
Java中的非final
变量 -
val a = 10 var b = "杨浔"
-
可以看到在声明变量时,并没有为了限定类型,那么他的类型时怎么确定的呢?
- 这是由于Kotlin的类型推导机制完成的
-
但是这种类型推导机制并不是万能的,在一些延迟赋值的情况下,Kotlin就无法自动推导他的类型了
这时候就需要显式的声明变量类型才行,语法如下 -
val a : Int = 100
- 可以看到,此时的是首字母大写的,而Java中的首字母是小写的,这是因为Kotlin已经完全抛弃了Java
中的基本数据类型,全部使用了对象数据类型,如变成了一个类,拥有自己的方法和继承结构
- 可以看到,此时的是首字母大写的,而Java中的首字母是小写的,这是因为Kotlin已经完全抛弃了Java
-
Java和Kotlin数据类型对照表
-
Java基本数据类型 Kotlin对象数据类型 数据类型说明 int Int 整型 long Long 长整型 short Short 短整型 float Float 单精度浮点型 double Double 双精度浮点型 boolean Boolean 布尔型 char Char 字符型 byte Byte 字节型
-
-
为什么要使用
val
关键字? -
为了解决Java中final关键字没有被合理使用的问题
- 在Java中,除非你在主动在变量前声明了final关键字,否则这个变量就是可变的。然而这并不
一件好事,当项目变得越来越复杂,参与开发的人越来越多时,你永远不知道一个可变的便令
在什么时候被谁给修改了,即使他原本不应该被修改。这就经常会导致出现一些很难排查的
题。因此,一个好的变成习惯是,除非一个变量名取而允许被修改,否则都应该给他加上final
键字
- 在Java中,除非你在主动在变量前声明了final关键字,否则这个变量就是可变的。然而这并不
-
什么使用使用什么时候使用?
- 永远优先使用声明一个变量,而当无法满足你的需求时再使用
-
函数
-
语法
-
fun methodName(param1 : Int, param2 : Int) : Int{ return 0 }
-
-
语法糖,当一个函数只有一行时,Kotlin允许我们不必写函数体,可以直接将唯一的一行代码写在函数定义
尾部,中间用等号连接即可-
fun largerNum(num1 : Int, num2 : Int) : Int { return max(param1, num2) } ====> fun largerNum(num1 : Int, num2 : Int) : Int = max(num1, num2)
-
使用这种语法,return关键字也可以省略了,等号足以表达返回值的意思,另外,因为Kotlin优秀的
型推导机制,所以该函数可以再化简为 -
fun largerNum(num1 : Int, num2 : Int) = max(num1, num2)
-
程序的逻辑控制
if条件控制
-
Kotlin中的
if
语句和Java中的if
语句几乎没有任何区别-
fun largerNum(num1 : Int, num2 : Int) : Int{ var temp = 0 if(num1 > num2){ temp = num1 }else{ temp = num2 } return temp }
-
-
Kotlin中的
if
语句相比于Java有一个额外功能——他是可以有返回值的,返回值就是语句每一个条件中最后
行代码的返回值,因此上面的代码可以简化为-
fun largerNum(num1 : Int, num2 : Int) : Int{ val temp = if(num1 > num2){ num1 }else{ num2 } return temp }
-
-
可以看到temp是一个多余的变量,故可以再次修改
-
fun largerNum(num1 : Int, num2 : Int) : Int{ return if(num1 > num2){ num1 }else{ num2 } }
-
-
这样看的话,return后面可以看作是一句话,故可以再次修改
-
fun largerNum(num1 : Int, num2 : Int) = if(num1 > num2){ num1 }else{ num2 } 周知,大括号里有一句以不写大括号=============> fun largerNum(num1 : Int, num2 : Int) = if(num1 > num2) num1 else num2
-
when条件语句
-
when
语句有点类似Java中的switch
语句,但他又远比语句强大得多switch
有着种种限制,比如他只能传入整型或短于整型或者字符串变量作为条件,其次每一个case条件都要
最后主动加上一个break,否则执行完当前case之后会一次执行下面的case
-
下面看一个实例
-
fun getScore(name : String) = if(name == "Tom"){ 86 }else if(name == "Jim"){ 77 }else if(name == "Jack"){ 100 }else{ 0 }
-
-
当判断条件很多时,使用
if
就会显得代码很冗余,这时可以使用when
-
fun getScore(name : String) = when(name){ "Tom" -> 86 "Jim" -> 77 "Jack" -> 100 else -> 0 }
-
-
因为和
if
语句一样,也是可以有返回值的,所以仍旧可以使用单行代码函数的语法糖 -
语句允许传入一个任意类型的参数,然后可以在when的结构提中定义一系列的条件,格式是
-
匹配值 -> {执行逻辑}
-
当代码只有一行时,{}可以省略
-
-
除了精确匹配之外,语句还允许进行类型匹配
-
fun checkNumber(num : Number){ when(num){ is Int -> println("number is Int") is Double -> println("number is Double") lse -> println("number not support") } }
-
上述代码中,关键字是类型匹配的核心,它相当于Java中的
instanceof
关键字,由于checkNumber()
函数接受一个Number
类型的参数,这是Kotlin
内置的一个抽象类,像Int、Float、Double
等与数字相关的类都是他的子类,所以这里就可以使用了类型匹配来判断传入的参数到底属于什么类型
-
-
when
语句的基本用法就这些,但其实when
语句还有一种不带参数的用法-
fun getScore(name : String) = when{ name == "Tom" -> 86 name == "Jack" -> 100 else -> 0 }
-
这种写法可能会觉得比较冗余,但有些情况必须使用这种写法才能实现,如所有Tom开头的人都是86分:
-
fun getScore(name : String) = when{ name.startWith("Tom") -> 86 name == "Jack" -> 100 else -> 100 }
-
-
循环语句
-
熟悉Java的人都晓得,Java中主要有两种循环语句:
while
循环和for
循环。Kotlin中也提供了while
和for
循环,其中while
循环不论是语法还是使用技巧上都和Java一样,故不讨论 -
Kotlin在
for
循环方面做了很大幅度的修改,Java中最常用的for-i
循环在Kotlin直接被舍弃,而Java中另一种for-each
循环则被Kotlin进行了大幅度的增强,变成了for-in
循环 -
在此之前,先普及一个区间的概念
-
val range = 0..10
-
上述代码表示创建了一个0到10的区间,并且两端都是闭区间,这意味着0到10这两个端点都是包含在区间中的,用数学的方式表示就是
[0,10]
-
其中,
..
是创建两端闭区间的关键字,在..
两边置顶区间的左右端点就可以创建一个区间了
-
-
有了区间后就可以通过
for-in
循环来遍历这个区间-
for(i in 0..10){ println(i) }
-
-
在很多情况下,双端闭区间却不如单端闭区间好用,比如数组的下标是从0开始,一个长度为10的数组,他的下标区间范围是0到9,因此左闭右开的区间更加常用,Kotlin中可以使用
until
关键字来创建一个左闭右开的区间-
val range = 0 until 10
-
for(i in 0 until 10){ println(i) }
-
-
默认情况下,
for-in
循环每次执行循环时会在区间回味内递增1,相当于Javafor-i
循环中i++
的效果,而如果你想要跳过其中的一些元素,可以使用step
关键字:-
for(i in 0 until 10 step 2){ println(i) }
-
相当于
i+=2
的效果
-
-
上述两种都是区间左端必须小于区间右端,也就是这两种关键字创建的都是一个升序的区间,如果想创建一个降序的区间,可以使用
downTo
关键字-
for(i in 10 downTo 1){ println(i) }
-
相当于
[10, 1]
-
面向对象编程
- 什么是面向对象编程?
- 先将实物封装成具体的类,然后将事物所有的属性和能力分别定义成类中的字段和函数,接下来对类进行实例化,再根据具体的变成需求调用类中的字段和方法即可
类与对象
-
class Person{ var name = "" var age = 0 fun eat(){ println(name + "is eating. He is " + age + "years old.") } } val p = Person() ==================================== fun main(){ val p = Person() p.name = "Jack" p.age = 21 p.eat() }
-
相对于Java,省去了关键字
new
继承与构造函数
-
class Student{ var id = "" var grade = 0 }
-
想要让Student类继承Person类需要做两件事
-
第一,使Person类可以被继承
-
在Kotlin中,任何一个非抽象类默认都是不可以被继承的,相当于Java中给类声明了
final
关键字,之所以这样设计和val
关键字的原因是差不多的,因为类和变量一样,最好都是不可变的,而一个类允许被继承的话,他无法预知子类会如何实现,因此可能就会存在一些未知的风险。 -
open class Person{ .... }
-
-
第二,让Student类继承Person类
-
与Java不同,继承使用的关键字是
:
而不是extends
-
class Student : Person(){ var id = "" var grade = 0 }
-
-
-
为什么继承的Person后面有括号?
-
在Java的继承中,我们知道在子类的构造函数中是要调用父类的构造函数的,但是在Kotlin中,构造函数分为主构造函数和次构造函数,最常用的是主构造函数
-
主构造函数的特点是没有函数体,直接定义在类名的后面即可
-
class Student(val id : String, val grade : Int) : Person(){ }
-
因为主构造函数没有函数体,所以如果想在主构造函数中编写一些逻辑,可以在
init
结构体中书写-
class Student(val id : String, val grade : Int) : Person(){ init { println("id is " + id) println("grade is " + grade) } }
-
-
根据继承特性的规定,子类的构造函数必须调用父类的构造函数,可是主构造函数并没有函数体,我们怎样去调用父类的构造函数呢?你可能会说,在
init
结构体中去调用不就好了。这或许是一种办法,但是在绝大多数场景下,我们是不需要编写init
结构体的。在Kotlin中子类的主构造函数调用父类中的哪个构造函数,在继承的时候通过括号指定-
class Student(val id : String, val grade : Int) : Person(){ }
-
-
上述为父类无参数的构造函数,而如果对其进行修改
-
class Person(val name : String, val age : Int) { }
-
这时子类就会出错,需修改为
-
class Student(val id : String, val grade : Int, name :String, age : Int) : Person(name, age)
-
-
需要注意的是,在Student类的主构造函数中增加name和age这两个字段时,不能再将他们声明成
val
,因为在主构造函数中声明成val
或var
的参数将自动成为该类的字段,,这就会导致和父类同名的name和age字段造成冲突。因此这里的name和age参数前面我们不用加任何关键字,让他的作用于仅限定在主构造函数中即可
-
-
-
次构造函数
-
任何一个类只能有一个主构造函数,但是可以有多个次构造函数,次构造函数都必须调用主构造函数(包括间接调用)
-
class Student(val id : String, val grade : Int, name : String, age : Int) : Person(name, age){ constructor(name : String, age : Int) : this("", 0, name, age){ } constructor() : this("", 0){ } } ================================== val student1 = Student() val student2 = Student("Jack", 19) val student3 = Student("a123", 5, "Jack", 18)
-
-
还有一种特殊的情况:类中只有次构造函数,没有主构造函数,即当一个类没有显式定义主构造函数且定义了次构造函数时,他就是没有主构造函数的
-
class Student : Person{ constructor(name : String, age : Int) : super(name, age){ } }
-
可以看到这里的Person没有后面的括号了,这是为什么呢?
- 回顾之前有主构造函数时为什么需要加括号,因为主构造函数没有函数体,所以在主构造函数初始化时,调用父类的哪个构造函数由类名后的括号来指定,而这里没有主构造函,只有次构造函数,而又因为次构造函数必须调用主构造函数,所以此时他需要使用
super
关键字调用父类的构造函数,所以在类名后面就可以不用指定调用哪个构造函数了,因为在次构造函数那里已经指定过了
- 回顾之前有主构造函数时为什么需要加括号,因为主构造函数没有函数体,所以在主构造函数初始化时,调用父类的哪个构造函数由类名后的括号来指定,而这里没有主构造函,只有次构造函数,而又因为次构造函数必须调用主构造函数,所以此时他需要使用
-
-
-
接口
都一样,随缘
可见性修饰
修饰符 | Java | Kotlin |
---|---|---|
public | 所有类可见 | 所有类可见(默认) |
private | 当前类可见 | 当前类可见 |
protected | 当前类、子类、同一包路径下的类可见 | 当前类、子类可见 |
default | 同一包路径下的类可见(默认) | 无 |
internal | 无 | 同一模块中的类可见 |
数据类与单例类
-
数据类,就是持久化类、领域类
-
Kotlin只需要在类前加
data
关键字即可,其他的都不用写 -
data class User(val username : String, val password : String)
-
当一个类没有任何代码时,可以将大括号省略
-
-
单例类,某个类在全局只能有一个实例,私有一个静态实例,写一个get方法,实例化的时候判断实例是否为空,不为空就返回,为空赋值返回
-
Kotlin只需要把
class
换为object
即可 -
调用类似Java的静态方法的调用,实际上Kotlin在背后自动创建了一个Singleton的实例,并且保证全局只会存在一个Singleton实例
-
Object Singleton{ fun singleton(){ println("singleton is called.") } }
-
Lambda编程
-
集合的创建和遍历
-
List
-
# 不可变集合 val list = listOf("Apple", "Banana", "Orange") for(fruit in list){ println(fruit) } #可变集合 val list = mutableListOf("Apple", "Banana", "Orange") list.add( "Pear") for(fruit in list){ println(fruit) }
-
-
Set
#不可变集合 val set = setOf("Apple", "Banana", "Orange") for(fruit in set){ println(fruit) } #可变集合 val set = mutableListOf("Apple", "Banana", "Orange") list.add( "Pear") for(fruit in set){ println(fruit) }
-
Map
-
#存数据 map["Apple"] = 1 #取数据 val number = map["Apple"] #不可变集合 val map = mapOf("Apple" to 1, "Banana" to 2) #可变集合 val map = mutableMapOf("Apple" to 1, "Banana" to 2) map["Pear"] = 3 for((fruit, number) in map){ println("fruit is" + fruit + ",number is " + number) }
-
-
-
集合的函数式API
-
Lambda的语法结构
-
{参数名1 : 参数类型, 参数名2 : 参数类型 -> 函数体}
-
-
集合的函数式API实质上就是接受了一个Lambda参数
-
#按给定条件查询最大值 val maxLengthFruit = list.maxBy{ it.length } #按给定条件将集合中的每个元素映射成另外一个值,最终生成一个新的集合 val newList = list.map{ it.toUppercase } #按给定条件过滤,最终生成一个新的集合 val newList = list.filter{ it.length > 4} #判断集合中是否至少存一个元素满足条件 val flag = list.any{ it.length > 5 } #判断集合中所有元素是否都满足条件 val flag = list.all{ it.length > 5 }
-
-
Lambda简化条件
- 当Lambda参数是函数最后一个参数时,可以将Lambda表达式移到函数括号外面
- 当Lambda参数是函数的唯一一个参数的话,还可以将函数的括号省略
- 因为Kotlin的类型推导机制,Lambda的参数列表在大多数情况下不必声明参数类型
- 当Lambda表达式的参数列表只有一个参数时,也不必声明参数名,而是可以使用
it
关键字来代替
-
-
Java函数式API的使用
- 函数式编程,自己看去
空指针检查
-
Kotlin通过编译时判空检查的机制几乎杜绝了空指针异常
-
Kotlin默认所有的参数和变量都不可为空
- 一旦传入一个null参数则会报错
-
如果需要设定某个参数可以接受空呢?
-
可以使用
?
-
fun doStudy(study : Study?){ if(study != null){ study.readBooks() } }
-
但是需要注意的是,一旦参数可以为空,那么下方的调用则必须判断是否为空,不为空才可以调用方法,否则会报错
-
-
如果每个方法都要写判断语句的话,会很繁琐,所以Kotlin提供了一系列的辅助工具
-
?.
-
就是当对象不为空时正常调用,为空则什么都不做
-
fun doStudy(study : Study?){ study?.readBooks() }
-
-
?:
-
就是当左边表达式不为空就返回左边表达式的结果,反则就返回右边表达式的结果
-
fun getTextLength(text : String?) = text?.length ?: 0
-
-
!!
-
就是非空断言,表示你非常确信这里的对象不会为空,但是在使用断言时,最好还是问下自己还有没有更好的方案
-
在这里虽然在main方法判空了,但是在调用toUpperCase()时还会认为这里存在风险,编译不通过
-
var content : String? = "hello" fun main(){ if(content != null){ printUpperCase() } } fun printUpperCase(){ val upperCase = content.toUpperCase() println(upperCase) }
-
-
let
-
obj.let{obj2 -> //编写具体的业务逻辑 }
-
调用了obj的let函数,然后Lambda表达式中的代码就会立即执行,并且这个obj对象本身还会作为参数传递到Lambda表达式中,不过为了防止变量重名,这里将参数改成了obj2,但实际上他们是一个对象,这就是let的作用
-
重新审视下这个代码
-
fun doStudy(study : Study?){ study?.readBooks() study?.sleep() }
-
-
变化成原始代码就是这个样子
-
fun doStudy(study : Study?){ if(study != null){ study.readBooks() } if(study != null){ study.sleep() } }
-
-
可以看到对对象的判断出现了两次,这无疑是冗余的,使用
let
修改-
fun doStudy(study : Study?){ study?.let{ stu -> stu.readBook() stu.sleep() } }
-
-
因为若表达式的参数列表只有一个参数时,可以不用声明参数名,用
it
替换-
fun doStudy(study : Study?){ study?.let{ it.readBook() it.sleep() } }
-
-
需要补充的一点是,对于全局变量的判空问题,可以使用
let
,但是使用if
时仍旧会出错,这是因为全局变量的值随时都有可能被其他线程修改,即使做了判空处理,仍然无法保证if
与剧中的study变量没有空指针风险
-
-
补充
字符串内嵌表达式
-
val name = "杜金亮" println("hello world" + name) println("hello world ${name}") //当表达式内只有一个变量时,大括号可以省略 println("hello world $name")
函数的参数默认值
fun printParams(num : Int, str : String = "hello")
printParams(123)
fun printParams(num : Int = 100, str : String = "hello")
//若想要使num使用默认值,可以通过键值的方式赋值
printParams(str = "world")
进阶
标准函数
let(见上)
with
-
with
函数接受两个参数:第一个参数可以是一个任意类型的对象,第二个参数是一个Lambda表达式,with
函数会在Lambda表达式中提供一个参数对象的上下文,并使用Lambda表达式的最后一行代码作为返回值返回 -
普通代码举例
-
val list = listOf<String>("Apple", "Banada", "barrery") val builder = StringBuilder() builder.append("Start eating fruits : \n") list.forEach { builder.append(it+"\n") } builder.append("Ate All fruit") val result = builder.toString() println(result)
-
-
可以看到连续调用了很多次
StringBuilder
对象的方法,可以使用with
函数进行修改-
val list = listOf<String>("Apple", "Banada", "barrery") val result = with(StringBuilder()){ append("Start eating fruits:\n") list.forEach { append(it+"\n") } append("Ate All fruits") toString() } println(result)
-
run
-
和
with
函数类似,只是稍微做了一些语法的修改,首先run
函数通常不会直接调用,而是要在某个对象的基础上调用;其次run
函数只接受一个Lambda参数,斌企鹅会在Lambda表达式中提供调用对象的上下文,其他方面和with
函数一样,包括也会使用Lambda表达式中的最后一行代码作为返回值返回 -
对上面的代码进行修改得到:
-
val result = StringBuilder().run { append("Start eating fruits:\n") list.forEach{ append(it + "\n") } append("Ate All fruits\n") toString() } println(result)
-
apply
-
apply
函数和run
函数也是机器类似的,都要在某个对象上调用,并且只接受一个Lambda参数,也会在Lambda表达式中提供调用对象的上下文,但是apply
函数无法指定返回值,而是会自动返回调用对象本身 -
对上面的代码进行修改得到:
-
val list = listOf<String>("Apple", "Banada", "barrery") val builder = StringBuilder().apply { append("Start eating fruits\n") list.forEach { append(it + "\n") } append("Ate All fruits\n") } println(builder.toString())
-
定义静态方法
-
在
Kotlin
中极度弱化了静态方法这个概念,因为Kotlin
提供了比静态方法更好的语法特性:单例类-
object Util{ fun doAction(){ println("doSomething") } }
-
-
不过使用单例类的写法会将整个类中的所有方法全部变成类似静态方法的调用方式,而如果我们只是希望让类中的某一个方法变成静态方法的调用方式与应该怎么办呢?这个时候就可以使用``companion object`,示例如下:
-
class Util{ fun doAction(){ print;n("doSomething") } companion object{ fun doAction2(){ println("do Something") } } }
`
-
-
不过,
doAction2
方法其实也并不是静态方法,companion object
这个关键字实际上会在Util类的内部创建一个伴生类,而doAction2
方法就是定义在这个伴生类里面的实例方法,只是Kotlin会保证Util类始终只会存在一个伴生类对象,因此调用Util.doAction2()
方法实际上就是调用了Util类中伴生对象的doAction2
方法 -
由此可以看出,Kotlin确实没有直接定义静态方法的关键字,但是提供了一些语法特性来支持类似静态方法调用的写法,这些语法特性基本可以满足我们平时的开发需求了
-
但是如果,你确确实实想要定义真正的静态方法,Kotlin仍然提供了两种实现方式:注解和顶层方法:
-
先看注解,前面使用单例类和
companion object
都只是在语法的形式上模仿了静态方法的调用方式,实际上他们都不是真正的静态方法,因此如果你在Java代码中以静态方法的形式去调用的话,你会发现这些方法并不存在,而如果我们给单例类或companion object
中的方法加上@JvmStatic
注解,那么Kotlin编译器就会讲这些方法编译成真正的静态方法:-
class Util{ fun doAction(){ print;n("doSomething") } companion object{ @JvmStatic fun doAction2(){ println("do Something") } } }
-
-
再来看顶层方法,顶层方法指的是那些没有定义在任何类中的方法,Kotlin编译器会将所有的顶层方法全部编译成静态方法,因此只要你定义了一个顶层方法,那么他就一定是静态方法
-
怎么建立呢?就是新建,选kotlin文件,选那个file就行了
-
fun doSomething(){ println("do something") }
-
-
对变量延迟初始化
-
当你的类中存在很多全局变量实例,为了保证他们能够满足
Kotlin
的空指针检查语法标准,你不得不做许多的非空判断才可以,即使你非常确定他们不会为空 -
延迟初始化使用的关键字是
lateinit
关键字,他可以告诉Kotlin
编辑器,我会在晚些时候对这个变量进行初始化,这样就不用一开始的时候将他赋值为null
了.-
class MainActivity : AppCompatActivity(), View.OnClinkListener{ private lateinit var adapter : MsgAdapter override fun onCreate(saveInstance : Bundle?){ ... adapter = MsgAdapter(msgList) ... } }
-
使用密封类优化代码
-
首先来了解下密封类的作用,这里我们来一个简单的例子,新建一个kotlin文件,文件名就叫Result.kt好了,然后在这个文件中编写如下代码:
-
interface Result class Success(val msg : String) : Result class Fail(val msg : String) : Result
-
-
这里定义了一个Result接口,用于表示某个操作的结果, 接口中不用编写任何内容,然后定义了两个类去实现Result接口,一个Success类表示用于成功时的结果,一个Fail类表示用于失败时的结果
-
再定义一个getResultMsg()方法,用于获取最终结果的信息
-
fun getResultMsg(result : Result) = when(result){ is Success -> result.msg is Fail -> result.msg else -> throw IllegaArgumentException() }
-
-
这样写的话是有一个潜在风险得到
- 如果我们现在新增一个Unknown类并实现Result接口,用于表示未知的执行结果,但是忘记在getResultMsg中添加相应的条件分支,编译器在这种情况下是不会提示我们的,而是会在运行的时候进入else条件,从而抛出异常并导致程序崩溃
-
解决办法是,可以使用密封类,密封类的关键字是
sealed class
他的用法非常简单,我们可以轻松的将Result接口改造成密封类的写法:-
sealed class Result class Success(val msg : String) : Result() class Fail(val msg : String) : Result()
-
-
此时,getResultMsg中的else已经不再需要了
-
fun getResultMsg(result : Result) = when(result){ is Success -> result.msg is Fail -> result.msg }
-
-
而一旦我们手动添加了新的类继承自Result类,那么在getResultMsg处一定会报错
-
注意:
- 密封类及其所有子类只能定义在同一个文件的顶层位置,不能嵌套在其他类中,这是被密封类的底层实现机制所限制的
拓展函数
-
拓展函数表示即使在不修改某个类的源码的情况下,仍然可以打开这个类,向该类添加新的函数
-
语法结构
-
fun className.methodName(param1 : Int, param2 : Int) : Int{ }
-
-
示例
-
fun String.letterCount() : Int{ var count = 0 for (char in this){ if(char.isLetter){ count++ } } return count }
-
重载运算符
-
class Obj{ operator fun plus(obj : Obj) : Obj { //处理相加的逻辑 } }
-
语法糖表达式和实际调用函数对照表
高阶函数
-
基本规则
-
(String , Int) -> Unit
-
其中
Unit
相当于Java中的void
表示空的返回值类型
-
-
示例
-
fun num1AndNum2(num1 : Int, num2 : Int, operation : (Int, Int) —> Int) : Int { val result = operation(num1, num2) return result } fun main(){ val num1 = 100 val num2 = 100 val result1 = num1AndNum2(num1, num2) { n1, n2 -> n1 + n2 } val result2 = num1AndNum2(num1, num2) { n1, n2 -> n1 - n2 } }
-
内联函数
- 上述的高阶函数对应到Java中是什么样子的呢,其实是高阶函数的位置是一个匿名对象,内部重写了方法,这样的效果,就说明当我们大量使用高阶函数时,会有大量的对象的创建和内存的占用,为了解决这种现象我们可以使用内联函数,关键字是
inline
- 使用了内联函数的话,由
kotlin
编辑器的作用,会将高阶函数在编译时完成对调用地方的替换,则避免了无用对象的创建 - 当我们参数里使用了一个之多个高阶函数时,内联函数都会在编译时完成调用时代码的替换,但是如果我们想要某一个参数不去替换呢,就可以使用
noinline
关键字,为什么会有这种想法呢,是因为内联函数可以return
而非内联函数不能return
但可以局部return
即return@printString
,需要注意内联函数的return
其实是外部的return
即替换位置的return
,而局部return
则是匿名对象函数内部的return
- 但是当我们在一个内联函数的内部调用
Lambda
匿名类时如果使用内联函数会出现错误,这是因为内联函数允许return
而匿名类不允许return
,出现了矛盾,就可以使用crossinline
关键字,他是一个契约,用于保证在内联函数的Lambda
表达式中一定不会使用return
关键字,这样矛盾就不存在了