集合
List、set 、map 分两类,只读和可变
List创建与元素获取
getOrElse是一个安全索引取值函数,他需要两个参数,第一个是索引值,第二个是能提供默认值的lambda表达式,如果索引值不存在的话,可用来代替异常。
getOrNull是Kotlin提供的另一个安全索引取值函数,他返回null结果,而不是抛出异常。
fun main(){
val list :List = listOf("jason","jack","jacky")
//list[4]
println(list.getOrElse(4){"Unknown"})//Unknown
println(list.getOrNull(4))//null
println(list.getOrNull(4)?:"Unknow")
}
可变List集合
在Kotlin中,支持内容修改的列表叫可变列表,要创建可变列表,可以使用mutableListOf函数。list还支持使用toList和toMutableList函数动态实现只读列表和可变列表的相互转换。
fun main(){
val mutableList = mutableListOf("jason","jack","jacky")
mutableList.add("jimmy")
mutableList.remove("jack")
println(mutableList)
listOf("jason","jack","jacky").toMutableList()
mutableListOf("jason","jack","jacky").toList()
}
mutator函数
能够修改可变列表的函数有个统一的名字:mutator函数
添加元素运算符与删除元素运算符
基于lambda表达式指定的条件删除元素
fun main(){
val mutableList =mutableListOf("jason","jack","jacky")
mutableList += "jimmy"
println(mutableList)
mutableList -="jason"
println(mutableList)
//removeIf
mutableList.removeIf{it.contains("jack")}
println(mutableList)
}
List集合遍历
for in 遍历
forEach 遍历
forEachIndexed遍历时要获取索引
fun mian(){
val list = listOf("jason","jack","jacky")
for(s :String in list){
println(s)
}
list.forEach{
println(it)
}
list.forEachIndexed {index,item->
println("$index, $item")
}
}
解构语法过滤元素
通过_符号过滤不要的元素
fun main(){
val list = listOf("jason","jack","jacky")
val (origin,_,proxy) = list
}
set集合
set创建与元素获取
list可以有重复的;set不可以
通过setOf创建set集合,使用elementAt函数读取集合中的元素
fun main(){
val set = setOf("Kotlin","Java","Scala")
//set[3]// 没有这种写法
set.elementAt(2)
}
可变set集合
通过mutableSetOf创建可变的set集合
fun main(){
val mutableSet = mutableSetOf("Kotlin","Java","Scala")
mutableSet +="Groovy"
}
集合转换与快捷函数
把list转换成set,去掉重复元素
快捷函数
fun main(){
val list = listOf("jason","jack","jacky","jacky" )
.toSet()
.toList()
println(list)
println(listOf("jason","jack","jacky","jacky").distinct())
}
数组类型
Kotlin提供各种Array,虽然是引用类型,但是可以编译成java基本数据类型
fun main(){
val intArray = intArrayOf(10,20,30)
listOf(10,20,30).toIntArray()
}
map集合
map的创建
to看上去像关键字,实际上,他是个省略了点号和参数的特殊函数,to函数将它左边和右边的值转化成一对Pair
fun main(){
val map = mapOf("Jack" to 20,"Jason" to 18,"Jacky" to 30)
println(map)
mapOf(Pair("Jack",20),Pair("Jason",18))
}
获取map的值
[]取值运算符,读取键对应的值,如果键不存在就返回null
getValue,读取键对应的值,如果键不存在就抛出异常
getOrElse,读取键对应的值,或者使用匿名函数返回默认值
getOrDefault,读取键对应的值,或者返回默认值
fun main(){
val map = mapOf("Jack" to 20,"Jason" to 18,"Jacky" to 30)
println(map["Jack"])//30
println(map.getValue("Jack"))//30
println(map.getOrElse("Rose"){"unknow"})//unknow
println(map.getOrDefault("Rose"),0)//0
}
遍历map
forEach
fun main(){
val map = mapOf("Jack" to 20,"Jason" to 18,"Jacky" to 30)
map.forEach{ it :Map.Entry
println("${it.key} , ${it.value}")
}
map.forEach{ (key:String, value:Int) ->
println("${key} , ${value}")
}
}
可变map集合
通过mutableMapOf创建可变map
getOrPut键值不存在,就添加并返回结果,否则就返回已有键对应的值
fun main(){
val mutableMap = mutableMapOf("Jack" to 20,"Jason" to 18,"Jacky" to 30)
mutableMap +="Jimmy" to 30
mutableMap.put("Jimmy",31)//覆盖了
println(mutableMap.getOrPut("Jimmy"){18})
mutableMap.getOrPut("Rose"){18}
println(mutableMap )
}
类
定义类与field关键字
针对你定义的每一个属性,kotlin都会产生一个field,一个get,一个setter,field用来存储属性数据,你不能直接定义field,kotlin会封装field,保护她里面的数据,质保路给getter和setter使用。属性的getter方法决定你如何读取属性值,每个属性都有getter方法,setter方法决定你如何给属性赋值,所以只有可变属性才会有setter方法,尽管kotllin会自动提供默认的getter和setter方法,但在需要控制如何读写属性数据时,你也可以自定义他们。
class player{
var name = "jack"
get() = field.capitalize()
set(value){
field = value.trim()
}
}
计算属性与防范竞态条件
计算属性是通过一个覆盖的get或set运算符来定义的,这时field就不需要了
class Player{
val rolledValue
get()=(1..6).shuffled().first()
}
如果一个类的属性即可空又可变,那么引用他之前你必须保证他非空,一个办法是用also标准函数
class Player{
var words:String ?="hello"
fun say(){
words?.also{it:String
println("hello ${it.toUpperCase()}")
}
}
}
主构造函数
我们在Player类的定义头中定义一个柱构造函数,使用临时变量为player的各个属性提供初始值,在kotlin中,为便于识别,临时变量(包括仅仅引用一次的参数),通常都会以下划线开头的名字命名。
class Player(
_name:String,
_age:Int,
_isNormal:Boolean
){
var name = _name
get() = field.capotalize()
set(value){
field = value.trim()
}
var age = _age
var isNormal = _isNormal
}
fun main(){
var player = Player("jack",20,true)
}
在柱构造函数里定义属性
kotlin允许你不使用临时变量赋值,而是直接用一个定义同时指定参数和类属性,通常,我们更喜欢用这种方式定义类属性,因为能减少重复代码
class Player2(
_name:String,
var age:Int,
val isNormal:Boolean
){
var name = _name
get() = field.capotalize()
set(value){
field = value.trim()
}
}
次构造函数
可以定义多个次构造函数来配置不同参数组合
class Player2(
_name:String,
var age:Int,
val isNormal:Boolean
){
var name = _name
get() = field.capotalize()
set(value){
field = value.trim()
}
constructor(_name:String):this(_name,
age=100
isNormal = false){
//...
}
}
默认参数
定义构造函数时可以指定默认值。
class Player3(
_name:String,
var age:Int = 20,
val isNormal:Boolean
){
var name = _name
get() = field.capotalize()
set(value){
field = value.trim()
}
}
初始化块
初始化块可以设置变量或值,以及执行有效性检查,如检查传给某构造函数的值是否有效,初始化块代码会在构造类实例时执行。
init{
require(age>0){"age must be positive"}
require(name.isNotBlank()){"must have a name"}
}
初始化顺序
主构造函数里声明的属性
类级别的属性赋值
init初始化块里的属性赋值和函数调用
次构造函数里的属性赋值和函数调用
class Student(
_name:String,
val age:Int
){
var name = _name
var score = 10
private val hobby ="music"
val subject :String
init {
println("initializing student")
subject = "math"
}
constructor(_name :String):this(_name,10){
score = 20
}
}
fun main(){
Student("Jack")
}
延迟初始化lateinit
使用lateinit关键字相当于做了一个约定:在用它之前负责初始化
只要无法确认lateinit变量是否完成初始化,可以执行inInitialized检查
class Player4{
laterinit var equipment:String
fun ready(){
equipment = "sharp knife"
}
fun battle(){
if(::equipment.isInitialized)println(equipment)//没有初始化就不执行了
}
}
fun main(){
val p = Player4()
p.ready()//如果忘了就异常了
p.battle()
}
惰性初始化by lazy
暂时不初始化某个变量,直到首次使用它
class Player5(_name :String){
var name = _name
val config by lazy{loadConfig()}
private fun loadConfig():String{
println("loading...")
return "xxx"
}
}
fun main(){
val p = Player5("Jack")
Thread.sleep(3000)
println(p.config)
}
初始化陷阱1
在初始化块时,顺序非常重要,必须保证块中所有属性已完成初始化。
class Player6(){
init{
val bloodBonus = blood.times(4)//blood 报错
}
val blood = 100
}
虽然 类级别属性 比初始化块的 先初始化,但还是要放在前面。顺序不能反了,从上到下。
class Player6(){
val blood = 100//要在前面
init{
val bloodBonus = blood.times(4)
}
}
初始化陷阱2
下面这个便已没有问题,因为编译器看到name已经在init里初始化了,但是代码一运行,就会抛出空指针异常,因为name属性还没赋值,firstLetter函数就用了他。
class Player7(){
val name :String
private fun firstLetter() = name[0]
init {
println(firstLetter())
name = "Jack"//和上面一行换一下就可以了
}
}
初始化陷阱3
因为编译器看到所有属性都初始化了,所以编译没有问题,但运行确是null。
因为在用initPlayerName函数初始化playerName时,name属性还没有完成初始化
class Player8(_name :String){
val playerName:String = initPlayerName()//这时拿到的是null
val name:String = _name//把这行放到第一行即可
private fun initPlayerName() = name
}
fun main(){
println(Player8("Jack").playerName)
}