Kotlin学习笔记 —— 集合,类(3)

集合

集合可以方便处理一组数据,也可以作为值参传给函数,和我们学过的其他变量类型一样,List,Set和Map类型的变量也分为两类,只读和可变

List集合

List的创建与元素的获取

list通过listOf来创建

getOrElse是一个**安全索引取值函数,**它需要两个参数,第一个是索引值,第二个是能提供默认值的lambda表达式,如果索引值不存在的话,可用来代替异常。

getOrNull是Kotlin提供的另一个安全索引取值函数,它返回null结果,而不是抛出异常

//LIST集合
fun main() {
    val list:List<String> = listOf("Jason", "Jack", "Jacky")
    println(list[2])        //普通的按索引取元素 越界会报错
    println(list.getOrElse(3){"Unknown"})       //找不到元素会执行后面的lambda式
    println(list.getOrNull(3)?:"Unknown")       //找不到元素返回null  可以和安全操作符一起使用
}

可变列表

在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")

    //不可变转可变
    listOf("Jason", "Jack", "Jacky").toMutableList()
    //可变转不可变
    mutableListOf("Jason", "Jack", "Jacky").toList()

    //跟add是一样的效果
    mutableList += "Jimmy"
    mutableList -= "Jason"

    //如果字符串中包含jack 就移除这个字符串
    mutableList.removeIf{ it.contains("Jack")}
    
    println(mutableList)
}

集合遍历

for in 遍历

forEach 遍历

forEachIndexed 遍历时要获取索引

//函数遍历
fun main() {
    val list = listOf("Jason", "Jack", "Jacky")
    //for循环
    for(s in list) {
        println(s)
    }
    //forEach
    list.forEach{
        print(it)
    }
    //遍历下标
    list.forEachIndexed{index, item ->
        println("$index, $item")
    }
}

解构

通过_符号过滤不想要的元素

一次性通过集合给多个元素赋值

    //_代表跳过当前元素 当前元素不使用 直接给下一个字符串赋值即可
    //将第一个元素赋值给origin 第二个元素不使用 第三个元素赋值给proxy
    //将集合中的元素赋值 可以比集合短
    val(origin:String,_:String) = list
    println("$origin")

Set集合

不允许有重复的元素

set创建与元素获取

通过setOf创建set集合,使用elementAt函数读取集合中的元素

//set集合
fun main() {
    val set = setOf("Jason", "Jack", "Jacky", "Jack")
    //取第二号元素
    println(set.elementAt(2))
   
}

可变集合

通过mutableSetOf创建可变的set集合

    //创建可变集合
    val mutableSet = mutableSetOf("Jason", "Jack", "Jacky", "Jack")
    mutableSet += "Jam"

集合转换

把List换成Set,去掉重复元素

快捷函数

    //将list转为set 去重
    val list = listOf("Jason", "Jack", "Jacky", "Jack")
        .toSet()
        .toList()
    println(list)

    //list转set快捷函数
    println(listOf("Jason", "Jack", "Jacky", "Jack").distinct())

数组类型

Kotlin提供了各种Array,虽然是引用类型,但可以编译成Java基本数据类型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e33oWBCo-1653654335705)(C:\Users\judicious\AppData\Roaming\Typora\typora-user-images\image-20220523222802290.png)]

import java.io.File
//数组类型
fun main() {
    //int数组
    val intArrayOf = intArrayOf(10, 20, 121, 1)

    //list转int型数组
    listOf(10, 20, 121, 1).toIntArray()

    //File数组
    val arrayOf = arrayOf(File("xxx"), File("yyy"))
}

Map集合

to看上去像关键字,但事实上,它是个省略了点号和参数的特殊函数,to函数将它左边和右边的值转化为一对Pair

//map集合
fun main() {
    //根据to创建map
    //to是一个函数 将它左边和右边的值转化为一对Pair
    val mapOf = mapOf("Jack" to 20, "Jason" to 18, "Jack" to 30)

    //可以直接根据Pair创建map
    mapOf(Pair("Jimmy", 10))
}

读取Map的值

[] 取值运算符,读取键对应的值,如果键不存在就返回null

getValue 读取键对应的值,如果键不存在就抛出异常

getOrElse 读取键对应的值 或者使用匿名函数返回默认值

getOrDefault 读取键对应的值 或者返回默认值

    //根据to创建map
    //to是一个函数 将它左边和右边的值转化为一对Pair
    val map = mapOf("Jack" to 20, "Jason" to 18, "Jack" to 30)

    println(map["Jack"])
    println(map.getValue("Jack"))
    println(map.getOrElse("Rose"){"UnKnown"})       //如果没有 返回值就用lambda表达式的值 也就是Unknown
    println(map.getOrDefault("Rose", 0))    //如果没有元素 返回值为第二个参数(默认值) 0

遍历

两种方式 forEach遍历Map

    //forEach遍历
    map.forEach{
        print("${it.key}, ${it.value}")
    }

    map.forEach{ (key: String, value: Int) ->
        println("$key, $value" )

    }

可变集合

通过mutableMapOf创建可变的Map

getOrPut键值不存在,就添加并返回结果,否则就返回已有键对应的值

//可变的map集合
fun main() {
    val mutableMap = mutableMapOf("Jack" to 20, "Jason" to 18, "Jack" to 30)

    //向map中添加(Jimmy 20)
    mutableMap += "Jimmy" to 20
    //将前面的覆盖
    mutableMap.put("Jimmy", 30)
    
    //如果没有Rose 就添加一个 如果有就获取value
    mutableMap.getOrPut("Jimmy"){18}

    println(mutableMap)
}

定义类

field

针对你定义的每一个属性,Kotlin都会产生一个field,一个getter,以及一个setter,field用来存储属性数据,你不能直接定义field,Kotlin会封装field,保护它里面的数据,只暴露给getter和setter使用,属性的getter方法决定你如何读取属性值,每个属性都有getter方法,setter方法决定你如何给属性赋值,所以只有可变属性才会有setter方法,尽管Kotlin会自动提供默认的getter和setter方法,但在需要控制如何读写属性数据时,你也可以自定义他们。

field取得的是属性的数据

//定义类 
class Player {

    //自动生成get和set方法 如果是非可空类型 NotNull 如果是可空类型 Nullable
    //如果有多个属性 使用field就在属性后添加即可
    var name:String = "Jack"
        get() = field.capitalize()
        set(value) {
            field = value.trim()
        }

    var age = "Capper"
        get() = field.capitalize()
        set(value) {
            field = value.trim()
        }
}

fun main() {
    var p = Player()
    p.name = " rose "
    println(p.name)
}

计算属性

计算属性是通过一个覆盖的get或set运算符来定义,这时field就不需要啦

//定义类
class Player {
    //计算属性 不需要取出属性的值
    var age = 12
        get() = (1..6).shuffled().first()
}

防范竞态条件

如果一个类属性既可空又可变,那么引用它之前你必须保证它非空,一个办法是用also标准函数。

//定义类
class Player {
    var name:String = "Jack"

    fun saySomething() {
        name?.also {
            println("Hello ${it.toUpperCase()}")
        }
    }
}

初始化

主构造函数

我们在Player类的定义头中定义一个主构造函数,使用临时变量为Player的各个属性提供初始值,在Kotlin中,为便于识别,临时变量(包括仅引用一次的参数),通常都会以下划线开头的名字命名。

class Player (
    //在类后面的括号内写主构造函数的参数
    //以下划线开头的代表只用一次 临时变量
    _name: String,
    _age: Int,
    _isNormal: Boolean
){
    //将一个属性或者方法private后 后面就不可调用了
    var name = _name
        get() = field.capitalize()
        set(value) {
            field = value.trim()
        }
    var age = _age
    var isNormal = _isNormal
}

fun main() {
    val p = Player("Jack", 20, true)
    p.name = "rose"
}

在主构造函数里定义属性

Kotlin允许你不使用临时变量赋值,而是直接用一个定义同时指定参数和类属性,通常,我们更喜欢用这种方式定义类属性,因为他会减少重复代码。

//主构造函数
class Player2 (
    //在类后面的括号内写主构造函数的参数
    //以下划线开头的代表只用一次 临时变量
    _name: String,
    //在主构造函数里定义属性 直接用一个定义同时指定参数和类属性
    var age: Int,
    var isNormal: Boolean
){
    //将一个属性或者方法private后 后面就不可调用了
    var name = _name
        get() = field.capitalize()
        set(value) {
            field = value.trim()
        }

}

fun main() {
    val p = Player2("Jack", 20, true)
    p.name = "rose"
}

反编译为Java

public final class Player2 {
   @NotNull
   private String name;
   private int age;
   private boolean isNormal;

   @NotNull
   public final String getName() {
      return StringsKt.capitalize(this.name);
   }

   public final void setName(@NotNull String value) {
      Intrinsics.checkNotNullParameter(value, "value");
      boolean var3 = false;
      this.name = StringsKt.trim((CharSequence)value).toString();
   }

   public final int getAge() {
      return this.age;
   }

   public final void setAge(int var1) {
      this.age = var1;
   }

   public final boolean isNormal() {
      return this.isNormal;
   }

   public final void setNormal(boolean var1) {
      this.isNormal = var1;
   }

   public Player2(@NotNull String _name, int age, boolean isNormal) {
      Intrinsics.checkNotNullParameter(_name, "_name");
      super();
      this.age = age;
      this.isNormal = isNormal;
      this.name = _name;
   }
}

次构造函数

可以定义多个次构造函数来配置不同的参数组合

    //次构造函数 内部会调用主构造函数
    constructor(name: String) : this(name, age = 10, isNormal = false)

使用次构造函数,定义初始化代码逻辑

    //在次构造函数内定义初始化代码逻辑
    constructor(name: String, age: Int) : this(name, age, isNormal = false) {
        this.name = name.toUpperCase()
    }

默认参数

定义构造函数时,可以给构造函数参数指定默认值,如果用户调用时不提供值参,就使用这个默认值

//主构造函数
class Player3 (
    _name: String,
    //在构造函数中给默认值
    var age: Int = 20,
    var isNormal: Boolean
){
    //将一个属性或者方法private后 后面就不可调用了
    var name = _name
}

fun main() {
    //如果要跳过中间的参数 使用具名法
    val p = Player3("Jack", isNormal = false)
    println(p.age)
}

初始化块

初始化块可以设置变量或值,以及执行有效性检查,如检查传给某构造函数的值是否有效,初始化块代码会在构造类实例时执行

和Java中的静态代码块不同

Java中的静态代码块是在类加载时运行的 初始化块代码会在构造类实例时执行

类加载机制

一个.java文件在编译后会形成相应的一个或多个Class文件(若一个类中含有内部类,则编译后会产生多个Class文件),但这些Class文件中描述的各种信息,最终都需要加载到虚拟机中之后才能被运行和使用。事实上,虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型的过程就是虚拟机的 类加载机制 。

类实例化

  1. 前提: .class文件已经被加载完毕(经过加载、解析和初始化)

  2. 系统赋初值:在堆内存中为Xxx类以及其父类(包括间接父类)中的非静态成员变量开辟空间,并赋系统默认初值(0,null,false),因为成员变量有初值,函数才能使用。

    即使子类覆盖了父类成员变量,仍会给父类所有成员变量分配空间,此时子类中多了一个属性,分态性,分空间存储,分别存储父类和子类的成员变量

  3. 父类成员变量的显示初始化和构造代码块的初始化
    两者的执行顺序就是代码排列顺序(和静态变量显示初始化和静态构造代码块初始化一样)。但须注意,和前面静态一样,当构造代码块在前而成员变量声明在后时,构造代码块中只能赋值,不能访问。另外,实际上两者会被自动装入父类构造函数的最前面

  4. 父类构造函数执行
    Java强制要求Object对象(Object是Java的顶层对象,没有超类)之外的所有类的构造函数的第一条语句必须是超类构造函数的调用语句,以保证所创建实例的完整性。

  5. 成员变量的显示初始化和子类构造代码块初始化
    注意事项和父类一样。

  6. 构造函数初始化
    每一个Java中的对象都至少会有一个构造函数,如果我们没有显式定义构造函数,那么JVM会自动生成一个默认无参的构造函数。如果自己定义了,则不会再自动生成任何构造函数。在编译生成的字节码中,这些构造函数会被命名成()方法,参数列表与Java语言书写的构造函数的参数列表相同。

类的加载机制和实例化的区别

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kbs8CF0I-1653654335712)(C:\Users\judicious\AppData\Roaming\Typora\typora-user-images\image-20220526214631806.png)]

//主构造函数
class Player3 (
    _name: String,
    //在构造函数中给默认值
    var age: Int = -1,
    var isNormal: Boolean
){
    //将一个属性或者方法private后 后面就不可调用了
    var name = _name

    //初始化块
    init {
        //如果条件没有被满足 就会抛出后面的异常信息
        require(age > 0) {"age must be positive"}
        require(name.isNotBlank()) {"player must have a name."}
    }
}

初始化顺序

主构造函数里声明的属性

类级别的属性赋值 + init初始化块里的属性赋值和函数调用 (按照顺序执行)

次构造函数里的属性赋值和函数调用

在这里插入图片描述

延迟初始化

告诉编译器该变量可以不用提前初始化

使用lateinit关键字相当于做了一个约定,在用它之前负责初始化

只要无法确认lateinit变量是否完成初始化,可以执行isInitialized检查

//延迟初始化
class Player4 {
    //延迟初始化
    lateinit var equipment: String

    fun ready() {
        equipment = "share knife"
    }

    fun battle() {
        //进行判断 如果没有被初始化就不执行后面的
        if(::equipment.isInitialized) println(equipment)

    }
}

fun main() {
    var p = Player4()
    p.ready()
    p.battle()
}

惰性初始化

可以暂时不初始化某个变量,直到首次使用它,这个叫作惰性初始化

与延迟初始化的区别

延迟初始化之前要先执行初始化操作 需要自己调用初始化方法

惰性初始化可以事先写好初始化函数 当需要调用该变量时会调用该初始化代码进行初始化

和单例模式中的懒汉式和饿汉式相同

懒汉式 是什么都不干 等到需要的时候在加载 lazy

饿汉式 是提前加载好需要的东西 等到用的时候直接拿

//惰性初始化
class Player5 (_name:String) {
    var name = _name

    //惰性初始化 懒汉式 等到三秒后在执行loadConfig()
    //val config by lazy { loadConfig() }
    //饿汉式 先执行loadConfig() 3秒后执行println(p.config)
    val config = loadConfig()

    private fun loadConfig() : String{
        println("loading...")
        return "xxx"
    }
}

fun main() {
    var p = Player5("Jack")
    //线程睡3秒
    Thread.sleep(3000)
    println(p.config)
}

初始化陷阱一

类级别的代码块和init代码块按顺序执行 所以在init中初始化某些变量的时候需要注意有没有定义

在使用初始化块时,顺序非常重要,你必须保证块中的所有属性已完成初始化

初始化陷阱二

因为在使用变量前没有赋值 所以会报空指针异常

class Player6() {

    val name:String

    private fun firstLetter() = name[0]

    init {
        //name为空 需要先赋值
        println(firstLetter())
        name = "Jack"
    }


}
fun main() {
    Player6()
}

初始化陷阱三

//初始化陷进 赋值为空 
class Player7(_name : String) {

    val playerName:String = initPlayerName()

    val name:String = _name

    private fun initPlayerName() = name


}
fun main() {
    //此时name为空 
    println(Player7("Jack").playerName)

}

继承

类默认都是封闭的,要让某个类开放继承,必须使用open关键字来修饰它

函数重写

父类函数也要以open关键字修饰,子类才能覆盖它

//类的继承
open class Product (val name:String) {
    fun description() = "Product: $name"

    //如果需要重写方法 也需要使用open修饰要重写的方法
    open fun load() = "Nothing..."
}

class LuxuryProduct : Product("Lucury") {
    //重写方法前需要添加override关键字
    override fun load() = "LuxuryProduct loading..."
}

fun main() {
    //类似于java中多态
    val p:Product = LuxuryProduct()

    println(p.load())
}

类型检测

使用is关键字来检查某个对象的类型

as修饰符来实现类型转化

//类的继承
open class Product (val name:String) {
    fun description() = "Product: $name"

    //如果需要重写方法 也需要使用open修饰要重写的方法
    open fun load() = "Nothing..."
}

class LuxuryProduct : Product("Lucury") {
    //重写方法前需要添加override关键字
    override fun load() = "LuxuryProduct loading..."

    fun special() = "LuxuryProduct special function"
}

fun main() {
    //类似于java中多态
    val p:Product = LuxuryProduct()

    println(p.load())

    println(p is Product)
    println(p is LuxuryProduct)
    println(p is File)

    //向下转型 使用as操作符
    if (p is LuxuryProduct) {
        println((p as LuxuryProduct).special())
    }
}

智能类型转换

Kotlin编译器只要能确定any is父类条件检查属实,他就会将any当做子类类型对待,因此,编译器允许你不经类型转换直接使用

//类的继承
open class Product (val name:String) {
    fun description() = "Product: $name"

    //如果需要重写方法 也需要使用open修饰要重写的方法
    open fun load() = "Nothing..."
}

class LuxuryProduct : Product("Lucury") {
    //重写方法前需要添加override关键字
    override fun load() = "LuxuryProduct loading..."

    fun special() = "LuxuryProduct special function"
}

fun main() {
    //类似于java中多态
    val p:Product = LuxuryProduct()

    //转型转一次后 p就指向这个转化后的对象了
    println((p as LuxuryProduct).special())

    p.special()
}

Kotlin层次

每一个类都会继承一个共同的叫作Any的超类 基类:Any

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值