变量常量与类型
声明变量
/*可读可改 变量名 类型 值*/ var name : String = "Derry"
内置数据类型
/* String 字符串 Char 单字符 Boolean true/false Int 整形 Double 小数 List 集合 Set 无重复的元素集合 Map 键值对的集合 Int ---> java int Float ---> java float */
只读变量
要声明可修改变量,使用var关键字。 要声明只读变量,使用val关键字。
类型推断
// 对于已声明并赋值的变量,它允许你省略类型定义。 // 显示给定的类型在这里是多余的 val info : String = "is Success"
编译时常量
const val PI = 3.1415 // 定义编译时常量 // 编译时常量只能是常用的基本数据类型:(String,Double,Int,Float,Long,Short,Byte,Char,Boolean) // 编译时常量只能在函数之外定义,为什么?答:如果在函数之内定义,就必须在运行时才能调用函数赋值,何来编译时常量一说 // 结论:编译时常量只能在函数之外定义,就可以在编译期间初始了
查看Kotlin反编译后字节码和java代码
反编译为java代码
引用类型与基本数据类型
Java有两种数据类型:引用类型与基本数据类型。
Kotlin只提供引用类型这一种数据类型,出于更高性能的需要,Kotlin编译器会在Java字节码中改用基本数据类型
条件语句
range表达式
.. 3..4 闭区间 包含首尾 until 1 until 10 半闭区间 含首不含尾 ..< 1 ..< 10 半闭区间 含首不含尾 和 until一样 1.9版本加入
when表达式
val info = when(week) { 1 -> "今天是星期一,非常忙碌的一天开会" 2 -> "今天是星期二,非常辛苦的写需求" 3 -> "今天是星期三,努力写Bug中" 4 -> "今天是星期四,发布版本到凌晨" 5 -> "今天是星期五,淡定喝茶,一个Bug改一天" 6 -> "今天是星期六,稍微加加班" 7 -> "今天是星期七,看剧中,游玩中" else -> { println("养猪去了,忽略星期几") } }
String模版
//模板支持在字符串的引号内放入变量值还支持字符串里计算表达式的值并插入结果,添加在$中的任何表达式,都会作为字符串的一部分求值。 val garden = "黄石公园" val time = 6 println("今天天气很晴朗,去${garden}玩,玩了$time 小时") // 字符串模版的写法
函数
函数头
// 函数默认都是public private fun method01(age: Int, name: String) : Int {} 可见修饰符 函数声明关键字 函数名 函数参数 返回值 函数体
函数参数
默认参数
//默认参数 如果不打算传入值参,可以预先给参数指定默认值 private fun action(name: String, age: Int = 77) { println("我的姓名是:$name, 我的年龄是:$age") } 反编译字节码可知 通过方法调用进行实现
具名函数参数
//如果使用具名函数参数,就可以不用管参数的顺序 action(age = 76,name = "赵六")
Unit函数
// Java语言的void关键字(void是 无参数返回的 忽略类型) 但是他是关键字啊,不是类型,这很矛盾 // : Unit不写,默认也有,Unit代表 无参数返回的 忽略类型 == Unit类型类 为了处理泛型
Nothing类型特点
// TODO函数的任务就是抛出异常,返回Nothing类,就是永远别指望它运行成功 // 下面这句话,不是注释提示,会终止程序的 TODO("not implemented") 源码 @kotlin.internal.InlineOnly public inline fun TODO(): Nothing = throw NotImplementedError() @kotlin.internal.InlineOnly public inline fun TODO(reason: String): Nothing = throw NotImplementedError
反引号中函数名
Kotlin可以使用空格和特殊字符对函数命名,不过函数名要用一对反引号括起来。 private fun `登录功能 2021年8月8日测试环境下 测试登录功能 `(name: String, pwd: String) { println("模拟:用户名是$name, 密码是:$pwd") } 为了支持Kotlin和Java互操作,而Kotlin和Java各自却有着不同的保留关键字,不能作为函数名,使用反引号括住函数名就能避免任何冲突 java代码 // in is 在java里面就是普通函数 public static final void in() { System.out.println("in run success"); } public static final void is() { System.out.println("is run success"); } Kotlin中调用 KtBase21.`is`() KtBase21.`in`()
匿名函数
定义时不取名字的函数,我们称之为匿名函数,匿名函数通常整体传递给其他函数,或者从其他函数返回 匿名函数对Kotlin来说很重要,有了它,我们能够根据需要制定特殊规则,轻松定制标准库里的内置函数。
val len = "abced".count() val len2 = "abced".count({item -> item == 'e' })
函数类型&隐式返回学习
匿名函数也有类型,匿名函数可以当作变量赋值给函数类型变量,就像其他变量一样,匿名函数就可以在代码里传递了。变量有类型,变量可以等于函数,函数也会有类型。
函数的类型,由传入的参数和返回值类型决定,
和具名函数不一样,除了极少数情况外,匿名函数不需要return关键字来返回数据,匿名函数会隐式或自动返回函数体最后一行语句的结果
// 第一步:函数输入输出的声明 函数的类型 val methodAction : () -> String // 第二步:对上面函数的实现 methodAction = { val inputValue = 999999 "$inputValue abc" // == 背后隐式 return "$inputValue Derry"; // 匿名函数不要写return,最后一行就是返回值 } // 第三步:调用此函数 println(methodAction())
函数参数学习
和具名函数一样,匿名函数可以不带参数,也可以带一个或多个任何类型的参数,需要带参数时,参数的类型放在匿名函数的类型定义中,参数名则放在函数定义中
// 第一步:函数输入输出的声明 第二步:对声明函数的实现 val methodAction : (Int, Int, Int) -> String = { number1, number2, number3 -> val inputValue = 999999 "$inputValue 参数一:$number1, 参数二:$number2, 参数三:$number3" }
it关键字特点
定义只有一个参数的匿名函数时,可以使用it关键字来表示参数名。当你需要传入两个值参,it关键字就不能用了,
val methodAction2 : (String) -> String = { "$it abc" }
匿名函数的类型推断
定义一个变量时,如果已把匿名函数作为变量赋值给它,就不需要显示指明变量类型了
val method2 = { 2 } // method2 函数: () -> Int
类型推断也支持带参数的匿名函数,但为了帮助编译器更准确地推断变量类型,匿名函数的参数名和参数类型必须有
val method1 = { v1:Double, v2:Float, v3:Int -> "v1:$v1, v2:$v2, v3:$v3" } // method1 函数: (Double, Float, Int) -> String
定义参数是函数的函数
函数的参数是另外一个函数 fun main() { loginAPI("Derry", "123456") { msg: String, code: Int -> println("最终登录的情况如下: msg:$msg, code:$code") } } public fun loginAPI(username: String, userpwd: String, responseResult: (String, Int) -> Unit) { responseResult("login success", 200) }
简略写法
如果一个函数的lambda参数排在最后,或者是唯一的参数,那么括住lambda值参的一对圆括号就可以省略。
"abc".count({it =='1'}) "abc".count{it =='1'} loginAPI2("Derry", "123456", { msg: String, code:Int -> println("最终登录的情况如下: msg:$msg, code:$code") }) // 第二种方式 loginAPI2("Derry", "123456", responseResult = { msg: String, code: Int -> println("最终登录的情况如下: msg:$msg, code:$code") }) // 第三种方式 loginAPI2("Derry", "123456") { msg: String, code: Int -> println("最终登录的情况如下: msg:$msg, code:$code") }
函数内联
lambda可以让你更灵活地编写应用,但是,灵活也是要付出代价的。
在JVM上,你定义的lambda会以对象实例的形式存在,IVM会为所有同lambda打交道的变量分配内存,这就产生了内存开销。更糟的是,lambda的内存开销会带来严重的性能问题。幸运的是,kotlin有一种优化机制叫内联,有了内联,JM就不需要使用lambda对象实例了,因而避免了变量内存分配。哪里需要使用lambda,编译器就会将函数体复制粘贴到哪里。
使用lambda的递归函数无法内联,因为会导致复制粘贴无限循环,编译会发出警告。
// 此函数有使用lambda作为参数,就需要声明成内联 // 如果此函数,不使用内联,在调用端,会生成多个对象来完成lambda的调用(会造成性能损耗) // 如果此函数,使用内联,相当于 C++ #define 宏定义 宏替换,会把代码替换到调用处,调用处 没有任何函数开辟 对象开辟 的损耗 // 小结:如果函数参数有lambda,尽量使用 inline关键帧,这样内部会做优化,减少 函数开辟 对象开辟 的损耗
public inline fun loginAPI3(responseResult: (String, Int) -> Unit) { responseResult("login success", 200) }
函数引用
要把函数作为参数传给其他函数使用,除了传lambda表达式,kotlin还提供了其他方法,传递函数引用,函数引用可以把一个具名函数转换成一个值参,使用lambda表达式的地方,都可以使用函数引用
login("Derry2", "123456", ::methodResponseResult) fun methodResponseResult(msg: String, code: Int) { println("最终登录的成果是:msg:$msg, code:$code") } inline fun login(name: String, pwd: String, responseResult: (String, Int) -> Unit) { responseResult("登录成功", 200) }
函数类型作为返回类型
函数类型也是有效的返回类型,也就是说可以定义一个能返回函数的函数。
// showMethod函数 再返回一个 匿名函数 fun showMethod(info: String): (String, Int) -> String { println("我是show函数 info:$info") // return 一个函数 匿名函数 return { name: String, age: Int -> "我就是匿名函数:我的name:$name, age:$age" } }
匿名函数与具名函数
// 匿名函数 showPersonInfo("lisi", 99, '男', "学习KT语言") { println("显示结果:$it") } // 具名函数 showResultImpl showPersonInfo("wangwu", 89, '女', "学习C++语言", ::showResultImpl)
闭包
在Kotlin中,匿名函数能修改并引用定义在自己的作用域之外的变量,匿名函数引用着定义自身的函数里的变量,Kotlin中的lambda就是闭包
能接收函数或者返回函数的函数又叫做高级函数,高级函数广泛应用于函数式编程当中
fun configDiscountWords(): (String) -> String{ val currentYear = 2027 val hour = (1..24).shuffled().last() return {goodsName: String -> "${currentYear}年,双11${goodsName}促销倒计时:$hour 小时" } }
null安全与异常
可空性
第一种情况:默认是不可空类型,不能随意给null val name: String = "Derry" 第二种情况:声明时指定为可空类型 val name2: String ?
null安全
安全调用操作符 ?. val name: String? = null // name.length // name是可空类型的 可能是null,想要使用name,必须给出补救措施 val r = name?.length // name是可空类型的 如果真的是null,?后面这一段代码不执行,就不会引发空指针异常 非空断言操作符 !!. val r = name!!.length // !! 断言 不管name是不是null,都执行,这个和java一样了 使用if判断null if (name != null) { // if也算是补救措施,和Java一样了 val r = name.length println(r) }
空合并操作符
?:操作符的意思是,如果左边的求值结果为nu,就使用右边的结果值。
val name = info?: "你是null"
异常处理与自定义异常特点
fun main() { try { var info: String? = null checkException(info) println(info!!.length) }catch (e: Exception) { println("啊呀:$e") } } fun checkException(info: String?) { info ?: throw CustomException() } class CustomException : IllegalArgumentException("你的代码太不严谨了")
先决条件函数
Kotlin标准库提供了一些便利函数,使用这些内置函数,你可以抛出带自定义信息的异常,这些便利函数叫做先决条件函数,你可以用它定义先决条件,条件必须满足目标代码才能执行。
checkNotNull()参数为空抛异常
requireNotNull()参数为空抛异常
require()参数为false抛异常
error()专门用来抛异常
checkNotNull(value1) // java.lang.IllegalStateException: Required value was null. requireNotNull(value1) // java.lang.IllegalArgumentException: Required value was null. require(value2) // java.lang.IllegalArgumentException: Failed requirement.
字符串
substring
字符串截取,substring函数支持IntRange类型(表示一个整数范围的类型)的参数,
println("Success".substring(0, indexOf)) println("Success".substring(0 until indexOf)) // KT基本上用此方式: 0 until indexOf println("Success".substring(2..3))
split
val jsonText = "a,b,c,d" // list 自动类型推断成 list == List<String> val list = jsonText.split(",") // 直接输出 list 集合,不解构 println("分割后的list里面的元素有:$list") // C++ 解构 Kt也有解构 val (v1, v2, v3, v4) = jsonText.split(",") println("解构四个只读变量的值是:v1:$v1, v2:$v2, v3:$v3, v4:$v4")
replace
val sourcePwd = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" println("原始密码是:$sourcePwd") //第一个参数是正则表达式,用来决定要替换哪些字符 //第二个参数是匿名函数,用来确定该如何替换正则表达式搜索到的字符 val newPwd = sourcePwd.replace(Regex("[AKMNO]")) { when(it.value) { // 这里的每一个字符 A B C D ... "A" -> "9" "K" -> "3" "M" -> "5" "N" -> "1" "O" -> "4" else -> it.value // 就啥事不做,直接返回 字符本身 A B C D ... } } println("加密后的密码是:$newPwd")
比较
在Kotlin中,用检查两个字符串中的字符是否匹配,用===检查两个变量是否指向内存堆上同一对象,而在Java中做引用比较,做结构比较时用equals方法。
// == 值 内容的比较 相当于Java的equals // === 引用的比较 val name1 : String = "Java" val name2 : String = "Java" val name3 = "ww" // 小结:name1.equals(name2) 等价于 name1 == name2 都是属于 值 内容的比较 println(name1.equals(name2)) // java println(name1 == name2) // kt // 引用的比较 println(name1 === name2) // true println(name1 === name3) // false // 引用的比较 难度高一点点 val name4 = "java".capitalize() // 修改成 println(name4 === name1) //false
遍历操作
"wasd".forEach { println(it) }
数字类型
安全转换函数
Kotlin提供了toDoubleOrNull和toIntOrNul等这样的安全转换函数,如果数值不能正确转换,与其触发异常不如干脆返回nul值
val number: Int = "666".toInt() println(number) // 字符串里面放入了Double类型,无法转换成Int,会奔溃 // val number2: Int = "666.6".toInt() // println(number2) // 解决什么奔溃的问题 val number2: Int? = "666.6".toIntOrNull() val number3: Int? = "888".toIntOrNull() val number4 = "888.8".toFloatOrNull() val number5 = "888.8".toDoubleOrNull() 反编译后的代码 int number = Integer.parseInt("666"); System.out.println(number); Integer number2 = StringsKt.toIntOrNull("666.6"); System.out.println(number2); Integer number3 = StringsKt.toIntOrNull("888"); System.out.println(number3); Float number4 = StringsKt.toFloatOrNull("888.8"); Double number5 = StringsKt.toDoubleOrNull("888.8"); Object var5 = number4 != null ? number4 : "原来你是null啊"; System.out.println(var5);
Double转Int
println(65.4645654.toInt()) // 65 精度损失 println(65.9645654.toInt()) // 65 精度损失 println(65.4645654.roundToInt()) // 65 四舍五入 println(65.8343433.roundToInt()) // 66 四舍五入
标准库函数
apply
run
with
apply
also
let
takeIf
takeUnless
List Set Map
List、Set和Map类型的变量也分为两类,只读和可变
List
List创建与元素获取学习
getOrElse是一个安全索引取值函数,它需要两个参数,第一个是索引值,第二个是能提供默认值的lambda表达式,如果索引值不存在的话,可用来代替异常。
getOrNul是Kotlin提供的另一个安全索引取值函数,它返回nu结果,而不是抛出异常。
val list = listOf("A", "B", "C", "D") //只读list println(list.getOrElse(3) {"越界"}) println(list.getOrNull(111)) // getOrNull + 空合并操作符 println(list.getOrNull(222) ?: "你越界了哦哦")
可变List
val list = mutableListOf("A", "B", "C", "D") // 可变的集合 list.add("赵六") list.remove("Wangwu") // 不可变集合 to 可变集合 toMutableList() // 可变集合 to 不可变集合 toList() val list3 : MutableList<Int> = listOf(123, 456, 789).toMutableList() val list5: List<Char> = mutableListOf('A', 'B', 'C').toList()
mutator函数
能修改可变列表的函数有个统一的名字:mutator函数
添加元素运算符与删除元素运算符(还记得C++中的运算符重载吗?)
基于lambda表达式指定的条件删除元素
// 1.mutator += -= 操作 val list : MutableList<String> = mutableListOf("A", "B", "C", "D") list += "李四" // mutator的特性 += -+ 其实背后就是 运算符重载而已 list += "王五" list -= "Derry" println(list) // 2.removeIf // list.removeIf { true } // 如果是true 自动变量整个可变集合,进行一个元素一个元素的删除 list.removeIf { it.contains("Derr") } // 过滤所有的元素,只要是有 Derr 的元素,就是true 删除 println(list)
遍历
for in 遍历 forEach 遍历 forEachIndexed 遍历时要获取索引
// 第一种 遍历方式: for (i in list) { print("元素:$i ") } // 第二种 遍历方式: list.forEach { // it == 每一个元素 print("元素:$it ") } // 第三种 遍历方式: list.forEachIndexed { index, item -> print("下标:$index, 元素:$item ") }
解构语法
var(v1, v2, v3) = listOf("李元霸", "李小龙", "李连杰") // 用_内部可以不接收赋值,可以节约一点性能 val(_ , n2, n3) = list
Set
Set创建与元素获取
val set: Set<String> = setOf("lisi", "wangwu", "zhaoliu", "zhaoliu") // set集合不会出现重复元素的 val set2: Set<String> = mutableSetOf("lisi", "wangwu", "zhaoliu", "zhaoliu") // set集合不会出现重复元素的 println(set.elementAt(0)) // [0] println(set.elementAtOrElse(0) {"越界了"}) println(set.elementAtOrNull(111)) // OrNull + 空合并操作符 一起使用 println(set.elementAtOrNull(88) ?: "你越界啦啊")
集合转换
val list2 /*: List<String>*/ = list.toSet().toList() println(list2) // 快捷函数去重 distinct println(list.distinct()) // 内部做了:先转变成 可变的Set结合 在转换成 List集合 println(list.toMutableSet().toList()) // 和上面代码等价
数组类型
/* Kotlin语言中的各种数组类型,虽然是引用类型,背后可以编译成Java基本数据类型 数组类型 创建函数 IntArray intArrayOf DoubleArray doubleArrayOf LongArray longArrayOf ShortArray shortArrayOf ByteArray byteArrayOf FloatArray floatArrayOf BooleanArray booleanArrayOf Array<对象类型> arrayOf 对象数组 */ // 1.intArrayOf 常规操作的越界奔溃 // 2.elementAtOrElse elementAtOrNull // 3.List集合转 数组 // 4.arrayOf Array<File>
Map
Map的创建
val mMap1 : Map<String, Double> = mapOf<String, Double>("Derry" to(534.4), "Kevin" to 454.5) val mMap2 = mapOf(Pair("Derry", 545.4), Pair("Kevin", 664.4)) // 上面两种方式是等价的哦
读取Map的值
// 方式一 [] 找不到会返回null // 方式二 getOrDefault // 方式三 getOrElse // 方式四 getValue 与Java一样 会奔溃
遍历Map
// 第一种方式: map.forEach { // it 内容 每一个元素 (K 和 V) 每一个元素 (K 和 V) 每一个元素 (K 和 V) // it 类型 Map.Entry<String, Int> println("K:${it.key} V:${it.value}") } // 第二种方式: map.forEach { key: String, value: Int -> // 把默认的it给覆盖了 println("key:$key, value:$value") } // 第三种方式: map.forEach { (k /*: String*/, v /*: Int*/) -> println("key:$k, value:$v") } // 第四种方式: for (item /*: Map.Entry<String, Int>*/ in map) { // item 内容 每一个元素 (K 和 V) 每一个元素 (K 和 V) 每一个元素 (K 和 V) println("key:${item.key} value:${item.value}") }
可变Map集合
// 1.可变集合的操作 += [] put // 2.getOrPut 没有的情况 // 3.getOrPut 有的情况 // 1.可变集合的操作 += [] put val map : MutableMap<String, Int> = mutableMapOf(Pair("Derry", 123), "Kevin" to 456, Pair("Dee", 789)) // 下面是可变操作 map += "AAA" to(111) map += "BBB" to 1234 map -= "Kevin" map["CCC"] = 888 map.put("DDD", 999) // put 和 [] 等价的 // 2.getOrPut 没有有的情况 // 如果整个map集合里面没有 FFF的key 元素,我就帮你先添加到map集合中去,然后再从map集合中获取 val r: Int = map.getOrPut("FFF") { 555 } println(r) println(map["FFF"]) // 他已经帮你加入进去了,所以你可以获取 // 3.getOrPut 有的情况 val r2 = map.getOrPut("Derry") {666} // 发现Derry的key是有的,那么就直接获取出来, 相当于666备用值就失效了 println(r2)
定义类
field
针对你定义的每一个属性,Kotlin都会产生一个field、一个getter、以及一个setter,field用来存储属性数据,你不能直接定义field,Kotlin会封装field,保护它里面的数据,只暴露给getter和setter使用。属性的getter方法决定你如何读取属性值,每个属性都有getter方法,setter方法决定你如何给属性赋值,所以只有可变属性才会有setter方法,尽管Kotlin会自动提供默认的getter和setter方法,但在需要控制如何读写属性数据时,你也可以自定义他们。
class KtBase70 { var name = "Derry" get() = field set(value) { field = value } /* 背后做的事情: @NotNull private String name = "Derry"; public void setName( @NotNull String name) { this.name = name; } @NotNull public String getName() { return this.name; } */ var value = "ABCDEFG" // 下面的隐式代码,不写也有,就是下面这个样子 get() = field set(value) { field = value } var info = "abcdefg ok is success" get() = field.capitalize() // 把首字母修改成大写 set(value) { field = "**【$value】**" } /* 背后做的事情: @NotNull private String info = "abcdefg ok is success"; public void setInfo( @NotNull String info) { this.info = "**【" + info + "】**"; } @NotNull public String getInfo() { return StringKt.capitalize(this.info) } */ }
计算属性
计算属性是通过一个覆盖的get或set运算符来定义,这时field就不需要了
class KtBase{ val number2 : Int get() = (1..1000).shuffled().first() // 从1到1000取出随机值 返回给 getNumber2()函数 }
防范竞态条件
当你调用成员,这个成员,可能为null,可能为空值,就必须采用 防范竞态条件,这个是KT编程的规范化
var info: String? = null // "" // 防范竞态条件 当你调用成员,这个成员,可能为null,可能为空值,就必须采用 防范竞态条件,这个是KT编程的规范化 fun showInfo() { // 这个成员,可能为null,可能为空值,就启用 防范竞态条件 info?.also { println(it) } }
类的初始化
主构造函数
在Kotlin中,为便于识别,临时变量(包括仅引用一次的参数),通常都会以下划线开头的名字命名
class KtBase72(_name: String, _sex: Char, _age: Int, _info: String) // 主构造函数 { var name = _name get() = field // get不允许私有化 private set(value) { field = value } val sex = _sex get() = field // set(value) {} 只读的,不能修改的,不能set函数定义 val age: Int = _age get() = field + 1 val info = _info get() = "【${field}】" fun show() { // println(_name) 临时的输入类型,不能直接用,需要接收下来 成为变量才能用 println(name) println(sex) println(age) println(info) } }
在主构造函数里定义属性
Kotlin允许你不使用临时变量赋值,而是直接用一个定义同时指定参数和类属性,通常,更喜欢用这种方式定义类属性,因为他会减少重复代码。
// var name: String 就相当于 var name = _name 这不过你看不到而已 // 一步到位,不像我们上一篇是分开写的 class KtBase73 (var name: String, val sex: Char, val age: Int, var info: String) { fun show() { println(name) println(sex) println(age) println(info) } }
次构造函数
次构造函数,必须要调用主构造函数,否则不通过, 为什么次构造必须调用主构造?答:主构造统一管理 为了更好的初始化设计
class KtBase74(name: String) // 主构造 { // 2个参数的次构造函数,必须要调用主构造函数,否则不通过, 为什么次构造必须调用主构造?答:主构造统一管理 为了更好的初始化设计 constructor(name: String, sex: Char) : this(name) { println("2个参数的次构造函数 name:$name, sex:$sex") } }
默认参数
class KtBase75(name: String = "李元霸") // 主构造 { // 2个参数的次构造函数,必须要调用主构造函数 constructor(name: String = "李连杰", sex: Char = 'M') : this(name) { println("2个参数的次构造函数 name:$name, sex:$sex") } // 3个参数的次构造函数,必须要调用主构造函数 constructor(name: String = "李小龙", sex: Char = 'M', age: Int = 33) : this(name) { println("3个参数的次构造函数 name:$name, sex:$sex, age:$age") } // 4个参数的次构造函数,必须要调用主构造函数 constructor(name: String = "李俊", sex: Char = 'W', age: Int = 87, info: String = "还在学校新开发语言") : this(name) { println("4个参数的次构造函数 name:$name, sex:$sex, age:$age, info:$info") } }
初始化块
初始化块可以设置变量或值,以及执行有效性检查,如检查传给某构造函数的值是否有效,初始化块代码会在构造类实例时执行
class KtBase76 (username: String, userage: Int, usersex: Char) // 主构造 { // 这个不是Java的 static{} // 相当于是Java的 {} 构造代码块 // 初始化块 init代码块 init { println("主构造函数被调用了 $username, $userage, $usersex") require( usersex == '男' || usersex == '女') { "你的性别很奇怪了,异常抛出" } } constructor(username: String) : this(username, 87, '男') { println("次构造函数被调用了") } }
初始化顺序
主构造函数里声明的属性 类级别的属性赋值 init初始化块里的属性赋值和函数调用 两个是同级别的 按照代码顺序执行 次构造函数里的属性赋值和函数调用
// 第一步:生成val sex: Char class KtBase77(_name: String, val sex: Char) // 主构造 { // 第二步: 生成val mName // 由于你是写在 init代码块前面,所以先生成你, 其实类成员 和 init代码块 是同时生成 val mName = _name init { val nameValue = _name // 第三步:生成nameValue细节 println("init代码块打印:nameValue:$nameValue") } // 次构造 三个参数的 必须调用主构造 constructor(name: String, sex: Char, age: Int) :this(name, sex) { // 第五步:生成次构造的细节 println("次构造 三个参数的, name:$name, sex:$sex, age:$age") } // 第四步 val derry = "AAA" // init代码块 和 类成员 是同时的,只不过你写在 init代码块前面 就是先生成你 }
延迟初始化lateinit
使用lateinit关键字相当于做了一个约定:在用它之前负责初始化
只要无法确认lateinit变量是否完成初始化,可以执行isInitialized检查
lateinit var responseResultInfo: String // 我等会儿再来初始化你,我先定义再说,所以没有赋值 fun showResponseResult() { // 由于你没有给他初始化,所以只有用到它,就奔溃 // if (responseResultInfo == null) println() // println("responseResultInfo:$responseResultInfo") if (::responseResultInfo.isInitialized) { println("responseResultInfo:$responseResultInfo") } else { println("你都没有初始化加载,你是不是忘记加载了") } }
惰性初始化by lazy
延迟初始化并不是推后初始化的唯一方式,你也可以暂时不初始化某个变量,直到首次使用它,这个叫作惰性初始化。
// >>>>>>>>>>>>>>>>>>> 下面是 不使用惰性初始化 by lazy 普通方式(饿汉式 没有任何懒加载的特点) // val databaseData1 = readSQlServerDatabaseAction() // >>>>>>>>>>>>>>>>>>> 使用惰性初始化 by lazy 普通方式 val databaseData2 by lazy { readSQlServerDatabaseAction() } private fun readSQlServerDatabaseAction(): String { println("开始读取数据库数据中....") println("加载读取数据库数据中....") println("结束读取数据库数据中....") return "database data load success ok." }
初始化陷阱
在使用初始化块时,顺序非常重要,你必须保证块中的所有属性已完成初始化。
class KtBase80 { init { number = number.times(9) //报错 init代码和类级别的属性初始化顺序是同级的 是按照代码顺序进行执行的 } var number = 9 }
这段代码编译没有问题,因为编译器看到info属性已经在init块里初始化了,但代码一运行,就会抛出空指针异常,因为info属性还没赋值,getInfoMethod函数就应用它
class KtBase81 { val info: String init { // info = "DerryOK" 运行报错 getInfoMethod() info = "DerryOK" } fun getInfoMethod() { println("info:${info[0]}") } }
因为编译器看到所有属性都初始化了,所以代码编译没问题,但运行结果却是nul,问题出在哪?
在用getInfoMethod函数初始化content时,info属性还未完成初始化。
class KtBase82 (_info: String) { // private val info = _info val content : String = getInfoMethod() private val info = _info // 把这种 转换info的代码,写到最前面,这样保证,就不会出现这种问题 private fun getInfoMethod() = info // 当此函数调用info变量的时候,你以为是赋值好了,但是还没有赋值 }
类的继承
继承与重载的open关键字
//KT所有的类,默认是final修饰的,不能被继承,和Java相反 //open:移除final修饰 open class Person(private val name: String) { private fun showName() = "父类 的姓名是【$name】" // KT所有的函数,默认是final修饰的,不能被重写,和Java相反 open fun myPrintln() = println(showName()) fun myPrintln2() = println(showName()) } class Student(private val subName: String) : Person(subName) { private fun showName() = "子类 的姓名是【${subName}】" override fun myPrintln() = println(showName()) }
对象
接口
抽象类
泛型
扩展
函数式编程
Kotlin和java互操作