函数
1.1 函数的定义
fun
是定义函数的关键字- 参数声明格式为"
参数名:参数类型
" - Kotlin函数语法糖:函数**
只有一行代码时
可以不写函数体!
直接用等号连接在函数定义的尾部**
fun largeNumber(num1:Int,num2:Int) = max(num1,num2)
fun 函数名(参数列表): 返回值类型{
函数内容
return 返回值
}
private fun doSomething (age:Int, flag:Boolean):String
可见性修饰符 函数声明关键字 函数名 函数参数 返回类型
fun main() {
println(doSomething(5, false))
}
//声明函数 默认修饰符是public
private fun doSomething(age:Int, flag:Boolean):String{
return "result"
}
1.2 函数参数
默认值参
如果不打算传入值参 可以预先给参数指定默认值、
优点:避免创建重载的函数
。
fun main() {
fix("scout", 3) //age为缺损值 不传的话用默认值
}
//定义缺损值的时候必须在后面 public定义函数时可以去掉 默认就是public
//函数没有返回值时后面可以不跟东西
fun fix(name: String, age: Int = 2){
println(name + age)
}
具名函数参数
如果使用命名值参 就可以不用管值参的顺序
fun main() {
fix(age = 10, name = "jiejie") //具名的函数参数传参时可以不用管顺序 但是一定要指名
}
//定义缺损值的时候必须在后面 public定义函数时可以去掉 默认就是public
//函数没有返回值时后面可以不跟东西
fun fix(name: String, age: Int = 2){
println(name + age)
}
1.3 Unit函数
在Kotlin中,针对不需要返回具体数据的函数,我们可以声明返回类型为Unit。就如同java中没有数据返回的函数声明为void一样。
而针对Unit函数,我们通常情况下是可以省略掉的。我们如果不定义的话,kotlin会自动编译为Unit。
我们可以默认省略掉Unit 和函数中的return。所以,在Kotlin中unit代表的就是没有实际意义的数据。
Kotlin中没有返回值的函数叫Unit函数,也就是说返回类型是Unit
在Kotlin之前没有返回值的函数用void表示 意思是没有返回类型 也就是说如果函数不返回任何东西 就忽略类型 但是这种解决方案无法解释泛型
fun main() {
println(fix("scout", 3)) //kotlin.Unit
}
fun fix(name: String, age: Int = 2){
println(name + age)
}
1.4 Nothing函数
在Kotlin中有一个特殊的数据类型Nothing。它只能用于函数返回类型声明,不能定义为变量声明。相较于而言,Unit可以定义为变量声明。
Nothing声明的函数永远不会返回正常值,只会抛出异常。
通常在单元测试框架执行测试失败的时候,可以通过定义Nothing返回类型的函数来抛出异常。
我们如果进行sdk开发,在用户使用时没有按照规范调用时我们也可以定义检测函数,并使用Nothing返回类型来抛出异常终止程序运行等。
Kotlin中内置TODO函数, 该函数的任务就是抛出异常 就是永远别指望它运行成功,返回Nothing类型
fun main() {
TODO("nothing")
println("aaa")
}
TODO函数
@kotlin.internal.InlineOnly
public inline fun TODO(reason: String): Nothing = throw NotImplementedError("An operation is not implemented: $reason")
1.5 反引号中的函数名
kotlin可以使用空格和特殊字符对函数命名
,不过函数名要用一对反引号括起来。
为了支持Kotlin和Java互操作,而Kotlin和java各自却有着不同的保留关键字,不能作为函数名,使用反引号括住函数名就能避免任何冲突
fun main() {
MyJava.`is`() //is在Kotlin中是关键字 可以用反引号引起来避免冲突
}
fun `**~special function with weird name~**`(){
}
public class MyJava {
public static void is() {
System.out.println("is");
}
}
1.6 匿名函数
定义时不取名字的函数,匿名函数通常整体传递给其他函数,或者从其他函数返回。
匿名函数对Kotlin来说很重要,有了它,我们就能根据需要制定特殊的规则,轻松定制标准库里的内置函数
fun main() {
//使用匿名函数给标准函数制定规则
val total = "Mississippi".count()
//第一个letter是变量 下一行是函数体
val totalS = "Mississippi".count({ letter ->
letter == 's'
})
println(total)
println(totalS)
}
//第二种
fun main() {
//使用匿名函数给标准函数制定规则
val total = "Mississippi".count()
//第一个letter是变量 下一行是函数体
val totalS = "Mississippi".count {
it == 's'
}
println(total)
println(totalS)
}
//第三种
fun main() {
//使用匿名函数给标准函数制定规则
val total = "Mississippi".count()
//第一个letter是变量 下一行是函数体
val totalS = "Mississippi".count{ letter ->
letter == 's'
}
println(total)
println(totalS)
}
1.7 函数类型与隐式返回
匿名函数也有类型,可以当作变量赋值给函数类型的变量,就像其他变量一样。匿名函数就可以在代码里传递了。变量有类型,变量可以等于函数,函数也有类型。函数的类型,由传入的参数和返回值类型决定
和具名函数不同,除了极少数情况外,匿名函数不需要return关键字来返回数据,匿名函数会隐式或自动返回函数体最后一行语句的结果。
//变量的类型是一个匿名函数 无参 返回字符串类型的匿名函数
val blessingFunction: () -> String = {
val holiday = "New Year."
"Happy $holiday"
//一般情况下把最后一句代码返回
}
println(blessingFunction) //() -> kotlin.String
println(blessingFunction()) //Happy New Year.
1.8 函数参数
和具名函数一样,匿名函数可以不带参数,也可以带一个或多个任何类型的参数,需要带参数时,参数的类型放在匿名函数的类型定义中,参数名则放在函数定义中
val blessingFunction2:(String) -> String = { name -> //name为传入的参数
val holiday = "New Year."
"$name and $holiday"
//一般情况下把最后一句代码返回
}
println(blessingFunction2("scout")) //scout and New Year.
1.9 it关键字
定义只有一个参数的匿名函数时,可以使用it关键字来表示参数名。当你需要传入两个值参,it关键字就不能用啦
//it关键字
val blessingFunction3:(String) -> String = {
val holiday = "New Year."
"$it and $holiday"
//一般情况下把最后一句代码返回
}
1.10 类型推断
定义一个变量时,如果已把匿名函数作为变量赋值给它,就不需要显示指明变量类型了。
//类型推断 不是代码块 是匿名函数 推断返回值类型
val blessingFunction4 = {
val holiday = "New Year."
" and $holiday"
//一般情况下把最后一句代码返回
}
类型推断也支持带参数的匿名函数,匿名函数的参数名和参数类型必须有
使用类型推断以及不使用的区别
//不使用类型推断
val blessingFunc:(String, Int) -> String = {name, year ->
val holiday = "New Year"
"$name ,Happy $holiday $year"
}
//使用类型推断时 需要在传参的时候标清参数类型 参数类型推断
val blessingFunc1 = {name:String, year:Int ->
val holiday = "New Year"
"$name ,Happy $holiday $year"
}
1.11 lambda
将匿名函数称为lambda,将它的定义称为lambda表达式,它返回的数据称为lambda结果
1.12 定义参数是函数的函数
函数的参数是另一个函数
//定义一个函数是另一个函数的参数
fun main() {
val getWords = {goodName:String, hour:Int ->
val currentYear = 2002
"$currentYear 年" //中间有空格 没有空格就会将后面的字符当作变量名
"${currentYear}年, ${goodName} ${hour}" //带括号后可以不用空格
}
showOnBoard("xxx", getWords)
}
//具名函数
fun showOnBoard(goodName: String, getWords:(String, Int) -> String) {
val hour:Int = (1..24).shuffled().last()
println(getWords(goodName, hour))
}
简略写法
如果一个函数的lambda参数排在最后,或者是唯一的参数,
那么括住lambda值参的一对圆括号就可以省略。
val total = "Mississippi".count({ it == 's' })
val totalS = "Mississippi".count { it == 's' }
//定义一个函数是另一个函数的参数
fun main() {
val getWords = {goodName:String, hour:Int ->
val currentYear = 2002
"$currentYear 年" //中间有空格 没有空格就会将后面的字符当作变量名
"${currentYear}年, ${goodName} ${hour}" //带括号后可以不用空格
}
showOnBoard("xxx", getWords)
}
//具名函数
fun showOnBoard(goodName: String, getWords:(String, Int) -> String) {
val hour:Int = (1..24).shuffled().last()
println(getWords(goodName, hour))
}
//简略后
//定义一个函数是另一个函数的参数
fun main() {
showOnBoard("xxx") { goodName: String, hour: Int ->
val currentYear = 2002
"$currentYear 年" //中间有空格 没有空格就会将后面的字符当作变量名
"${currentYear}年, $goodName $hour" //带括号后可以不用空格
}
}
//具名函数
private fun showOnBoard(goodName: String, getWords:(String, Int) -> String) {
val hour:Int = (1..24).shuffled().last()
println(getWords(goodName, hour))
}
1.13 函数内联
lambda可以让你更灵活地编写应用 但是需要付出一定代价
在JVM上,定义的lambda会以对象实例的形式存在,JVM会为所有同lambda打交道的变量分配内存,这就产生了内存开销
。lambda的内存开销会带来严重的性能问题。但是,Kotlin有一种优化机制叫做内联,有了内联,JVM就不需要使用lambda对象实例了,因而避免了变量内存分配。
哪里需要使用lambda,编译器就会将函数体复制粘贴到哪里
使用lambda的递归函数无法内联,因为会导致复制粘贴无限循环,编译会发出警告。
1.14 函数引用
要把函数作为参数传给其他函数使用,除了传lambda表达式,kotlin还提供了其他方法,传递函数引用,函数引用可以把一个具名函数转换成一个值参,使用lambda表达式的地方,都可以使用函数引用。
fun main() {
//要获得函数引用,使用::操作符,后跟要引用的函数名
showOnBoard("viper", ::getDiscountWords)
}
private fun getDiscountWords(goodName: String, hour:Int):String {
val currentYear = 2002
return "${currentYear}年, $goodName $hour"
}
//具名函数
private fun showOnBoard(goodName: String, getDiscountWords: (String, Int) -> String) {
val hour:Int = (1..24).shuffled().last()
println(getDiscountWords(goodName, hour))
}
1.15 函数类型作为返回类型
函数类型也是有效的返回类型,也就是说可以定义一个能返回函数的函数
fun main() {
val getdiscountWords = configDiscountWords()
println(getdiscountWords("viper"))
}
fun configDiscountWords(): (String) -> String {
val currentYear = 2002
val hour:Int = (1..24).shuffled().last()
return { goodName:String ->
"${currentYear}年, $goodName $hour"
}
}
1.16 闭包
在Kotlin中,匿名函数能修改并引用定义在自己的作用域之外的变量,匿名函数引用着定义自身的函数里的变量,Kotlin中的lambda就是闭包。
作用域重复问题 根据模块分??
能够接收函数或者返回函数的函数叫做高级函数,高级函数广泛应用于函数式编程当中
1.17 lambda与匿名内部类
函数类型能让开发者少写模式化代码,写出更灵活的代码。
Java 8支持面向对象编程和lambda表达式,但不支持将函数作为参数传给另一个函数或变量,不过java的替代方案是**匿名内部类。
通过匿名内部类将函数作为参数传给另一个函数或变量
import java.util.Random;
public class JavaAnonymousClass {
public static void main(String[] args) {
//这里匿名内部类可以调用函数参数??
show("jiejie", new discountWords() {
int a = 2;
@Override
public String getDiscountWords(String goodsName, int hour) {
int currentYear = 2002;
return String.format(String.format("%d年, %s,%d", currentYear, goodsName, a));
}
});
}
public interface discountWords{
String getDiscountWords(String goodsName,int hour);
}
public static void show(String goodsName, discountWords discountWords) {
int hour = new Random().nextInt(24);
System.out.println(discountWords.getDiscountWords(goodsName, hour));
}
}
null
Kotlin是编译型语言,它更多地把运行时可能出现的null问题,以编译时错误的方式,提前在编译期强迫我们重视起来,
而不是等到运行时报错,防患于未然,提高了我们程序的健壮性。
可空性
对于null值问题,Koltin反其道而行之,除非另有规定,变量不可为null值,这样一来,运行时崩溃从根源上得到解决。
不能直接赋值为空,除非声明
Kotlin的null类型
为了避免NullPointerException,Kotlin的做法是不让我们给非空类型变量赋null值,但null在Kotlin中依然存在。
fun main() {
var str:String? = "scout" //可空字符串类型
str = null
}
null安全
Koltin区分可空类型和非可空类型,所以,你要一个可空类型变量运行,而它又可能不存在,对于这种潜在的危险,编译器时刻警惕着。为了应对这种风险,Kotlin不允许你在可空类型值上调用函数,除非你主动接手安全管理。
1. 安全调用操作符(相当于调用前 if 判空)
编译器看到有安全调用操作符,所以它知道如何检查null值。如果遇到null值,就会跳过函数调用,而不是返回null
fun main() {
var str:String? = "scout" //可空字符串类型
str = null
str?.capitalize() //?.安全调用操作符 当str为空时自动跳过这个函数
println(str)
}
使用带let的安全调用
安全调用允许在可空类型上调用函数,但是如果还想做点额外的事,比如创建新值,或判断不为null就调用其他函数,怎么办?
可使用带let函数的安全调用操作符。可以在任何类型上调用let函数,它的主要作用是让你在指定的作用域内定义一个或多个变量。
fun main() {
var str:String? = "scout" //可空字符串类型
str = null
//不为空才会调用后面的let函数 为空不调用
str = str?.let {
//非空白字符串
if(it.isNotBlank()) {
it.capitalize()
}else {
"scout"
}
}
println(str)
}
2. 使用非空断言操作符
!!又称感叹号操作符,当变量值为null时,会抛出KotlinNullPointerException
//可空性
fun main() {
var str:String? = "scout" //可空字符串类型
str = null
//使用非空断言操作符 当str为空时 会抛出异常
str = str!!.capitalize()
println(str)
}
3. 使用 if 判断 null 值情况
也可以使用if判断 但是相比之下安全调用操作符用起来更灵活,代码也更简洁,我们可以使用安全操作符进行多个函数的链式调用
//if判断
if(str != null) {
str.capitalize()
}else {
print("为空")
}
//使用安全操作符链式调用
str?.capitalize().plus("is good")
使用空合并操作符(相当于简略版三元表达式)
?:操作符:如果左边的求值结果为null,就使用右边的结果值
//空合并操作符 ?: 如果左边为空 输出右边的值 如果不为空 输出str
println(str ?: "viper")
空合并操作符也可以和let函数一起使用来代替if/else语句
//可以和let函数一起使用来代替if/else语句
str = str?.let { it.capitalize() } ?: "viper"
异常
//异常
import java.lang.IllegalArgumentException
fun main() {
var num: Int? = null
//处理异常
try {
checkOperation(num)
num!!.plus(1)
}catch (e : Exception) {
println(e)
}
}
//抛出异常
fun checkOperation(num: Int?) {
num ?: throw UnskilledException()
}
//自定义异常
class UnskilledException() : IllegalArgumentException("操作不当")
先决条件函数
Kotlin标准库提供了一些便利函数,使用这些内置函数,可以抛出自定义信息的异常,这些便利函数叫做先决条件函数,你可以用它定义先决条件,条件必须满足,目标代码才能执行。
函数 | 描述 |
---|---|
checkNotNull | 如果参数为null,则抛出lllegalStateException异常,否则返回非null值 |
require | 如果参数为false,则抛出lllegalArgumentException异常 |
requireNotNull | 如果参数为null,则抛出lllegalStateException异常,否则返回非null值 |
error | 如果参数为null,则抛出lllegalStateException异常并输出错误信息,否则返回非null值 |
assert | 如果参数为false,则抛出AssertError异常,并打上断言编译器标记 |
字符串
substring
字符串截取
,substring函数支持IntRange类型(表示一个整数范围的类型)的参数,until创建的范围不包括上限值
const val NAME = "Jimmy's friend"
fun main() {
val index:Int = NAME.indexOf('\'')
val str:String = NAME.substring(0, index)
val str1:String = NAME.substring(0 until index) //IntRange
println(str)
println(str1)
}
split
split函数返回的是List集合数据,List集合又支持解构语法特性
,它允许你在一个表达式里给多个变量赋值,解构常用来简化变量的赋值。
//根据传入参数进行拆分字符串 返回List集合
val data:List<String> = NAMES.split(",")
//解构语法
val (orgin: String, dest:String, proxy:String) = NAMES.split(",")
println("$orgin, $dest, $proxy")
println(data[0])
replace
字符串替换
//replace 替换字符
val str2 = "The people's Republic of China."
val str3 = str2.replace(Regex("[aeiou]")) {
when(it.value) {
"a" -> "8"
"e" -> "6"
"i" -> "9"
"o" -> "1"
"u" -> "3"
else -> it.value
}
}
println(str2)
println(str3)
字符串的比较
在Kotlin中,用检查两个字符串中的字符是否匹配,用=检查两个变量是否指向内存堆上同一对象(常量池),而在Java中==做引用比较,做结构比较的时候用equals方法。
字符串是不可变的!!
“Jason"和"jason”.capitalize()指向不同!!
val str1 = "Jason"
val str2 = "jason".capitalize()
val str3 = "Jason"
val str4 = "jason".capitalize()
println(str1 == str2) //比较字符是否相等 ture
println(str1 === str2) //比较是否指向内存堆上同一个对象 false
println(str1 === str3) //true
println(str2 === str4) //false 字符串操作时每次都会重新创建一个对象
forEach
字符串遍历
//字符串遍历
"The people's Republic of China.".forEach {
println("$it")
}
数字类型
和java一样,Kotlin中所有数字类型都是有符号的,也就是说既可以表示正数,也可以表示负数
类型 | 位 | 最大值 | 最小值 |
---|---|---|---|
Byte | 8 | 127 | -128 |
Short | 16 | 32767 | -32768 |
Int | 32 | 2147483647 | -2147483648 |
Long | 64 | 9223372036854775807 | -9223372036854775808 |
Float | 32 | 3.4028235E38 | 1.4E-45 |
Double | 64 | 1.7976931348623157E308 | 4.9E-324 |
安全转换函数
Kotlin提供了toDoubleOrNull和toIntOrNull这样的安全转换函数,如果数值不能正确转换,与其触发异常不如返回null值
//val num2:Int = "8.98".toInt() //会抛出异常 NumberFormatException 没法将string转成int
val num1:Int? = "8.98".toIntOrNull() //返回null 没有转成功返回空
println(num1)
Double转Int
//double转int
println(8.956789.toInt()) //double转int会丢失小数点后 精度损失 8
println(8.956789.roundToInt()) //四舍五入转int 9
Double类型格式化(返回值是string 且会四舍五入)
//double类型格式化
println("%.2f".format(8.9866)) //返回的是字符串 且会四舍五入 8.99
标准库函数
apply
可看作一个配置函数
,可以传入一个接收者,然后调用一系列函数来配置它以便使用,如果提供lambda给apply函数执行,它会返回配置好的接收者
val file1 = File("C:\\Users\\judicious\\Desktop")
file1.setReadable(true)
file1.setWritable(true)
file1.setExecutable(false)
//apply函数 配置接收者对象 apply中的this和接收者对象是同一个对象
val file2 = File("C:\\Users\\judicious\\Desktop").apply {
setExecutable(true)
setWritable(true)
setReadable(true)
}
可以看到,调用一个个函数类配置接收者时,变量名就省掉了,这是因为,在lambda中,apply能让每个配置函数都作用于接收者,这种行为有时又叫做相关作用域,因为lambda表达式里的所有函数调用都是针对接收者的,或者说,它们是针对接收者的隐式调用。
let
let函数能使某个变量作用于其lambda表达式内,让it关键字能引用它。let与apply比较,let会把接收者传给lambda,而apply什么都不传,匿名函数执行完,apply会返回当前接收者,而let会返回lambda的最后一行
//let
fun main() {
val result = listOf(3,2,1).first().let {
it * it
}
println(result) //9
println(format("viper"))
}
//链式调用风格
fun format(guestName:String?):String{
return guestName?.let {
"welcomt, $it" //let返回最后一行
}?:"What's your name"
}
run
光看作用域行为,和apply差不多,但与apply不同,run函数不返回接收者,run返回的是lambda结果
import java.io.File
//run函数
fun main() {
val file = File("C:\\Users\\judicious\\Desktop")
val result = file.run {
readText().contains("a") //超过2GB不会加载 查看文件内是否包含a
}
println(result)
}
也能用来执行函数引用 ::
//run函数
fun main() {
//::函数引用 返回的是最后一行
val result1 = "capper viper scout".run(::isLong)
println(result1)
"capper viper scout"
.run(::isLong)
.run(::showMessage)
.run(::println)
}
fun isLong(name:String) = name.length >= 10
fun showMessage(isLong: Boolean):String {
return if(isLong) {
"NAME IS LONG"
}else {
"PLEASE RENAME"
}
}
with
with函数是run的变体,它们的功能行为是一样的,但with的调用方式不同,调用with时需要值参作为第一个参数传入
//with函数
fun main() {
//run和with比较 只是传参方式不同
val result1 = "The people's Republic of China".run {
length >= 10
}
val result2 = with("The people's Republic of China"){
length >= 10
}
}
also
also函数和let函数功能类似,和let一样,also也是把接收者作为值参传给lambda,但有一点不同:also返回接收者对象,而let返回lambda结果。所以,also尤其适合针对同一原始对象
,利用副作用做事,既然also返回的是接收者对象,就可以基于原始接收者对象执行额外的链式调用。
可以不用临时变量就交换两个变量的值
import java.io.File
//also
fun main() {
//可以链式调用 和let的区别 also返回接收者对象 let返回lambda表达式结果
val fileContents: List<String>
val file = File("C:\\Users\\judicious\\Desktop\\1.txt")
.also {
println(it.name)
}.also {
fileContents = it.readLines()
}
println(fileContents)
}
takeIf
和其他标准函数有点不一样,takeIf需要判断lambda中提供的条件表达式,给出true或false结果,如果判断结果是true,从takeIf函数返回接收者对象,如果是false,则返回null。
如果需要判断某个条件是否满足,再决定是否可以赋值变量或执行某些任务,
akeIf就很有用,概念上讲,takeIf函数类似于if语句,但它的优势是可以直接在对象实例上调用,避免了临时变量赋值的麻烦。
import java.io.File
//takeIf
fun main() {
//如果takeIf返回true就返回接收者对象 如果为false 返回null
val result = File("C:\\Users\\judicious\\Desktop\\1.txt")
.takeIf { it.exists() && it.canRead() }
?.readText()
println(result)
}
takeUnless
takeIf的辅助函数takeUnless,只有判断你给定的条件结果是false时,takeUnless才会返回原始接收者对象。为true时,返回null,和takeIf相反。
import java.io.File
//takeUnless 结果false时 才会返回原始接收对象
fun main() {
val result = File("C:\\Users\\judicious\\Desktop\\1.txt")
.takeUnless { it.isHidden }
?.readText()
println(result)
}
函数区别
let和apply let会把接收者传给let 而apply什么都不传 apply——this let——it 返回值也不同 apply返回配置好的接收者 let返回lambda的最后一行
let和run let——it run——this
run和apply run返回lambda的最后一行 apply返回配置好的接收者
also和let also返回接收者对象 let返回lambda的最后一行
also和apply also——it apply——this