Kotlin从零学习笔记

40 阅读16分钟

1.包和导入

package

import

2.变量

val var

3.数据类型

3.1. 数值类型

kotlin 不支持八进制

十六进制 0x或0X

二进制0b或0B

Double BigDecimal实际运算

Float 5.0F 5.0f

Int

String

Boolean

有符号整数类型

Byte8
Short16
Int32
Long64L

无符号整数类型:

类型大小(位)最小值
UByte80
UShort160
UInt320
ULong640
fun main() {
    val nnums1 : Int = 2 //常量
    var nnums2 : Int = 2 //变量
    nnums2 = 4
    println(nnums1)
    println("ni ${nnums1} 好")
    println("ni $nnums1 好")
}

var x=100_000 _下划线无实际意义,为了分清数值长度

科学计数法 1.1e2 1.1乘以10的二次方

3.2. 字符和字符串类型

  • 使用Unicode编码
  • + 号 根据左边的,左边是字符串跟着数值可以,左边是数值跟着字符串不可以
  • aString[4]
  • aString.subString(5,8) 小标5到8,不包含8
  • aString.replace("a","e") "a is bbca" -> "e is bbce"
  • StringBuilder kotlin里面的字符串是不可变的 用+去连接会创建多个浪费内存
  • 原样输出 """要输出的东西可以是多行"""
  • .strimMargin("|") 去除边界符号|之前的空格和制表符连续空白符号
  • toUpperCase() 转为大写
val builder: StringBuilder = StringBuilder()
var aString: String = builder { 
    appendln("a")
    append("b")
}

3.3. 可空类型和非空类型

  • 数据类型后面加? ->String? 表示可以为空也可以不为空
  • 相当于java的包装类型 -> 包装(封装)基本数据类型的对象类型
  • 可空类型必须赋值给另一个可空类型,不能赋值给另一个非空类型
  • !!强制调用,非空类型赋值可空
  • 对象?.方法或属性名
var aString: String? = null
println(aString?.subString(1,2)?.replace("a","b"))

//!! 强制调用
var aint: Int = aString?.length!!
var aint: Int = aString!!.length

3.4. 内置数据类型

  • Any所有类的父类,相当于java的Object,比obj少了 wait()和clone等方法
  • Unit 什么都不返回
  • Nothing 所有类的子类,什么都没有

3.5. 数组类型

数据元素的有序集合

var riversArray = arrayOf("Nile", "Amazon", "Yangtze")
riversArray += "Mississippi"
println(riversArray.joinToString())
// Nile, Amazon, Yangtze, Mississippi

//空素组
val nullArray: Array<Int?> = arrayOfNulls(3)
//val nullArray: Array<Int?> = arrayOfNulls<INt>(3)
println(nullArray.joinToString())
// null, null, null

//创建空数组
var exampleArray = emptyArray<String>()

3.5.1. 基本数据类型数组

3.5.1.1. 创建数组

  • 构造方法创建 val sz1 = IntArray(3)空数组,然后指定数据元素
  • 内置函数创建
    • intArrayOf(1,2,3)
    • longArrayOf()
    • shortArrayOf()
    • byteArrayOf()
    • doubleArrayOf()
    • floatArrayOf()
    • booleanArrayOf()
    • chaArrrayOf()

3.5.2. 引用型数组

数组中的所有元素都是引用类型

3.5.2.1. 创建数组

  • 创建空数组arrayOfNull<数据类型>(长度)
  • 指定元素 arrayOf<数据类型>(1,2,3)
  • 使用Array类的构造方法创建
val array1 = Array<Int>(3){
    it + 1 //it存放索引,每次都加1
}

3.5.3. 数组常用操作

  • 获取长度 size
  • 修改值 set(1,2)
  • 获取值 arrays[1]或者arrays.get(1)
  • 打印 joinToString()
  • 索引 array.indices
  • 遍历数组
    • in
    • withIndex()方法 x.index和x.value

3.6 容器类型->集合类型 Collection

Collection类衍生出来

  • 方法
    • size
    • isEmpty()
    • contains(X) containsAll(X) 是否包含元素
    • iterator() 迭代器
    • add(X) addAll(X)
    • remove(X) removeAll(X)
    • clear()清除
    • retainAll(X) 保留指定元素
类型可变性重复性随机访问惰性加载底层
list不可变列表可以重复java.util.Arras$ArrayList
MutableList可变列表可以重复java.util.ArrayList
Set不可变集合不可以重复java.utils.LinkedHashSet
MutableSet可变集合不可以重复java.utils.LinkedHashSet
Range不可变可以重复内置ranges包下
Sequence不可变可以重复内置sequence包下

3.6.1. 列表类型 List

3.6.1.1. 不可变列表 不能对元素修改

  • listOf<>()
  • listOfNull<>()
  • emptyList<>()
  • 构造方法创建

3.6.1.2. 可变列表 支持对元素修改

  • 创建
    • mutableListOf<>()
    • arrayListOf<>()
    • 构造方法创建

3.6.1.3. 常用方法

  • 长度 size
  • 索引 alist[0] alist.get(1)
  • 添加元素 add()
  • 修改元素 set(1,"a")
  • 删除元素 remove("a")删除列表中的a元素 removeAt(1) 删除索引1元素
  • 遍历
    • in
    • list.indices

3.6.2. 集类型 set

  • 不重复的一组数据
  • 创建不可变集合,会先创建LinkedHashSet实例,将该实例作为参数传给java的Collections.singleton()方法,返回一个SingletonSet类的实例,该类只提供了读操作的方法。

3.6.2.1 不可变集合

  • setOf<>()
  • 底层 hashSetOf<>()
  • emptySet<>()

3.6.2.2 可变集合

  • mutableSetOf<>()
  • 底层 linkedHashSet<>()

3.6.2.3. 常用方法

  • 长度 size
  • 是否为空 isEmpty
  • 添加 add
  • 删除 remove()
  • 遍历 in

3.6.3. 区间类型 Range

  • 位于ranges包下
  • IntRange/LongRange/CharRange
  • 创建
    • 开始值.IntRange(结束值) 包含开始结束值
    • 开始值..结束值 包含开始结束值
    • 开始值.until(结束值) 左闭右开不包含结束值
  • 设置步长 step(2)
  • 倒序 reversed()
  • 递减 downTo(1)
  • 是否在这个区间内 contains(2)

3.6.4. 序列类型 sequence

  • 位于sequences包下
  • 创建
    • sequenceOf(1,2,3)
    • generateSequence(seed){} 或者generateSequence{} 有无参数都可以

3.7 容器类型->映射类型 Map

使用键值对保存数据的数据结构,其他语言叫字典

包collections,底层实现java中的LinkedHashMap。

3.7.1. 不可变映射

  • Pair类,Pair(键,值),简单方法 键 to 值
  • mapOf<键类型,值类型>(键值对列表)
  • emptyMap<>()

3.7.2. 可变映射

  • mutableMapOf<>()

3.7.3. 常用方法

  • 长度 size
  • 访问 a["KEY"] 或者 a.ge("KEY") 不存在返回null
    • getValue("KEY") 没有抛出异常
    • getOrDefault("KEY","默认值")/gertOrElse("KEY","lambda表达式")
  • 修改
    • put
    • a["KEY"]="新值"
    • putIfAbsent("KEY","值") 如果key不存在就添加,如果存在不修改
  • 删除 remove("KEY") 不存在的键不会报错
  • mapValues ()方法 对键值对操作返回一个新map
fun main() {
    val map = mapOf("a" to 1, "b" to 2, "c" to 3)
    val result = map.mapValues { (key, value) ->
        val newKey = key + "123" // 将键加上数字
        val newValue = value * 5 // 将键值乘以5
        newKey to newValue
    }
    println(result) // 输出: {a123=5, b123=10, c123=15}
}

3.8 数据类型转换

  • typealias给类型起个别名
  • 用is判断变量的数据类型,然后操作
  • 只有变量赋值时候才支持自动转换, Kotlin不支持自动向上转换 ( 数值运算类型提升和java一样 )。
  • 强制类型转换as -> var aString: String = a1 as String
    • as? 安全类型转换防止Typemismatch异常, 只能赋值给可空类型

4.操作符 运算符

用于连接一个或者两个变量/常量.连接的变量/常量叫做操作数

4.1. 一元操作符

4.1.1. 正负操作符

    • unaryPlus()
    • unaryMinus()

4.1.2. 自增自减操作符

  • inc()
  • dec()
  • var b = a++ 先给b赋值,再运算
  • var b = ++a 先运算,再赋值b

4.1.3. 否定运算符

  • ! not()

4.2. 二元操作符

4.2.1. 四则运算符 +,-,*,/,%

    • plus()
    • minius()
    • times()
  • / div()
  • % rem()

4.2.2. 复合操作符 将算数操作符加上=

  • += plusAssign()
  • -= minusAssign()
  • *= timesAssign()
  • /= remAssign()

4.2.3. 比较操作符

> < >= <= 相当于compareT0() 来自 Comparable 接口

4.2.4. 位运算方法 Kotlin没有专门位运算符

Kotlinjava
and()&
or()
inv()!取反
xor()异或,值同返回0,否则返回1
shl()操作数向左移动一位
shr()<操作数向右移动一位
unshr()>>符号位不变,其余右移一位
var b = 6
println(b.inv())	//-7	Kotlin整型数据长度是32位,需要补全32位二进制

var b = 6
println(b.shl(2))	//6*2^2	 变成2进制,左边添加两位0

var b = 6
println(b.shr(2))	//6/(2^2)	变成2进制,右边去掉两位0

4.2.5. 猫王操作符 ?:

a ?: "空" 如果操作数的值为空,返回指定的默认值

4.2.6. 相等操作符

  • 等同 == 相当于equals()
  • 等于 === 指向同一个对象时候才相等

4.2.7 逻辑操作符

  • in 相当于 contians()
  • !in 相当于minus()

4.2.8. 区间操作符

..相当于rangeTo() 1.rangeTo(5) == 1..5

4.2.9 操作符重写

关键字 operator ,函数名称必须是上面操作符的方法名

5.流程控制

5.1. 条件语句

5.1.1. if语句

if (条件){
    
}

// if-else
if (条件){
    
} else {
    
}

//单行表达式
val if1 = if (条件) 1 else 0
//多行表达式
var n1 = 10
var n2 = 20
var res = if (n1 > n2) {
    n1 - n2
} else {
    n2 - n1
}
println(res)

5.1.2. when语句

when (条件){
    条件1 -> 代码1
    else -> 代码2   
}

//可以判断参数值是否在一组给定值中
when (a){
    a,b,c,d -> 代码1
    else -> 代码2   
}

//可以是否在一个区间内
when (a){
    in 1..9 -> 代码1
    else -> 代码2   
}

//可以数据类型
when (a){
    is Int -> 代码1
    else -> 代码2   
}

5.2. 循环语句

5.2.1. while循环

  • while 先判断在循环
  • do while 先循环一次再判断

5.2.2. for循环

  • 相当于java中的 for-each循环,没有初始条件,检测循环变量条件,更新循环变量
for (a in 1..3) {
    
}

5.2.3. repeat 循环

repeat(2){
    println("$it")//it获取循环次数
}

5.3 跳转语句

5.3.1 continue 调出当前循环

使用标签调出代码循环

outer@
for(i in 1..9){
    if(i == 7){
        continue@outer 
    }
    
}

5.3.2. break 停止循环

5.类与对象

5.1. 函数

没有return 默认返回Unit

形参不能像java一样,改变传入的形参值

具名实参 和 默认实参

fun name(inputs: UByte) : returntype {
    
}

5.1.1. 函数返回值

Pari<>两个参数,Triple<>三个类型参数

5.1.2. 函数参数

变参,动态参数 vararg关键字

java变参不能放其他位置,kotlin变参不是放到末尾需要,变参后面的参数传递需要命名参数

*数组绑定到变参上 ; 不加*是绑定到变参第一个参数上

5.2. 类

5.2.1. 访问修饰符

  • public 默认
  • private 类内部可见
  • protected 类内部和子类中可见
  • internal 类所在模块可见
  • open和final 类是否可以被继承,用在方法或属性上是否能被重写,类方法属性默认final

构造方法默认都是public修饰的,如果要改变,需要添加constructor

5.2.2. 构造方法

  • 主构造方法
  • 副构造方法 必须调用主构造方法 先执行主构造方法的init再执行副构造方法
//主构造方法,没有方法体使用init初始化
class A private constructor(val a: String) {
    init {
        
    }
    init {
        
    }
}
//副构造方法,定义的只是形参,主构造方法才能定义属性
constructor(参数列表一) :this(参数列表二)

5.2.3. 属性

字段和访问器的组合

不能直接访问字段,只能在访问器中访问字段(幕后字段),使用field访问隐藏在该属性背后字段的值

5.2.3.1. 延迟初始化

关键字latinit

  • 必须定义在类中,不能定义在构造方法中
  • 必须使用默认的访问器
  • 必须声明为var
  • 必须是非空类型
  • 必须是非基本数据类型

5.2.3.2. 内联属性 inline

相当于节省了一次方法调用过程

class Student{
    var loginName=""
    inline userNmae: String
        get() = loginName
        set(value) {
            this.loginName = value
        }
}

5.2.3.3. 一些特殊方法

5.2.3.3.1 infix
  • 必须只有一个参数
  • 参数不能是可变参数,不能有默认值
  • 必须是成员函数或扩展函数
5.2.3.3.2 componentN方法 解构方法
class Student(val name: String){
    var h: Int = 1
    var w: Int = 2
    operator fun compoment1() :String{
        return name
    }
    operator fun compoment2() :Int{
        return h
    }
    operator fun compoment3() :Int{
        return w
    }
}
//使用
val lp = Student("lp")
val (name,h,w) = lp
val (name,h) = lp
val (_,h,w) = lp
 

5.2.4. 嵌套类

类似java静态内部类

5.2.5.内部类 inner

  • 嵌套类是一个独立的类,内部类是外部类的一个成员
  • 创建内部类时,会同时创建一个外部类对象,所以内部类对象可以直接访问外部类的成员(包括私有成员)

5.2.6. 单例对象 object关键字

只有一个实例,单例模式

可以使用类名直接获取单例对象的唯一实例

5.2.7. 伴生对象 companion

定义在类中的单例对象,可以省略类名,一个类只能有一个伴生对象。

伴生对象可以任意访问外部类私有成员,外部类也可以任意访问伴生对象的私有成员

5.2.8. 对象表达式 匿名类

  • 和java中的匿名内部类类似,用于对现有类型进行扩展,而不用预先显式的创建之类
  • object : 父类型列表
  • 一个对象表达式可以同时扩展多个父类型,但是父类型最多有一个类型为类
  • 可以不指定任何父类型,直接通过对象表达式创建匿名对象
    • 匿名对象只存在本地作用域和私有作用域中。如果定义为public则返回匿名对象的父类,没有指定父类返回Any类型

5.2.9. 数据类 data

data关键字作为前缀的特殊类

5.2.9.1. 创建数据类

  • 主构造方法至少包含一个属性
  • 主构造方法只能包含属性,不能包含参数,即必须用var或val修饰
  • 声明数据类,不能添加修饰符open,sealed,abstract或inner
  • 主构造方法定义的属性会自动创建如下方法:
    • equals() 对象间比较
    • hashCode() 计算对象的哈希值
    • roString() 计算表示对象的字符串
    • compomentN() 对对象进行解构操作
    • copy() 用于复制一个新对象
  • 声明在类体内的属性不会自动创建
  • 手动定义的equals(),hashCode(),toString()方法,或者数据类的父类final修饰的,编译器不会自动创建
  • 父类型声明了final修饰的componentN()方法,data类会报错
  • 不能手动定义copy()方法和componentN()方法

5.2.9.2. 对象间的复制

copy(),方法是浅引用,

a=b.copy()

b的值变化,a的也会变化,是同一个引用

5.2.10. 枚举类 enum

枚举类的构造方法是私有的,并且不能被继承

enum class 类名 {
    枚举常量列表枚举1,枚举2;
    其余部分   
}
//自定义方法直接定义在枚举类中
enum class WeekDay(val abbr: String) {
    Monday("mom"), Tuesday("tue"), Wednesday("wed"),Thursday("Thu"),Friday("fri"),
    Saturday("sat"),Sunday("sun");
    fun isW(): Boolean {
        return !(this == Saturday || this == Sunday)
    }
}

//自定义方法继承自枚举对象内部类中1
enum class WeekDay(val abbr: String) {
    Monday("mom"){
        override fun getchines() :String{
            return "星期一"
        }
    }, Tuesday("tue"){
        override fun getchines() :String{
            return "星期二"
        }
    };
    //抽象方法
    abstract fun getchines() : String
}

//自定义方法继承自枚举对象内部类中2
interface Chinese{
    fun getc(): String
}
enum class WeekDay(val abbr: String) : Chinese{
    Monday("mom"){
        override fun getc() :String{
            return "星期一"
        }
    }, Tuesday("tue"){
        override fun getc() :String{
            return "星期二"
        }
    }
}
  • 获得枚举对象
    • 直接引用 WeekDay.Monday
    • 常量名引用 a = WeekDay.valueOf("Monday") a.name
    • 位置引用 WeekDay.values()[0]

5.2.11. 密封类 sealed

类似于抽象类,用来作为父类的类,和抽象类不同.密封类的构造方法是私有的,只能由其密封类内部的类或单例对象来继承,外部类继承会报错,要继承密封类里面的内部子类

6.继承、抽象类、接口

6.1. 重写 override

  • override默认是可以被继承的,如果不想让重写的方法再次重写,需要final
  • 重写属性,可以把父类val修改为var,不能把var修改为val

6.2. 子类访问父类 super

子类的内部类访问父类成员使用super@

6.3. 抽象类 abstract

通常用于制定一个类应该包含的通用功能,而它的子类复制具体实现这些功能

6.4. 接口 interface

接口由实体方法、抽象方法和抽象属性组成。声明抽象成员时可以省略abstract

6.4.1. 重写

类继承父类和实现多个接口时,很容易碰到存在相同签名的成员。可以通过super<父类类型>调用指定的父类类型成员。

7.正则表达式 Regex

字符意思**
^首行
$行尾
*任意次
+1或多次
?0或1次
{n}匹配n次
{n,m}最少n,最多m
\d一个数值字符
\D一个非数值字符
?<=表示后面的内容会进行检测,但是结果不会保存

7.1. 匹配数据 matches

//创建正则表达式对象
val regex = """[a-z]""".toRegex()
//检测是否匹配
if(regex.matches("hell1")){
    println("匹配小写字母")
    
}

7.2. 提取数据 find() findAll()

  • 返回MatchResult类型,value属性是匹配的字符串
  • results.map{it.value}.toList() 将MatchResult结果转为list

7.3. 替换数据 replace() replaceFirst()

  • 参数1 给定的文本;参数2 要替换的字符
  • 返回新字符串,原字符串不会变

7.4. 正则分组 ()

  • 小括号包含的部分会自动创建一个分组,分组号从1开始
  • (\d{3})-(\d{2})-\2 \2表示引用第二个分组的匹配结果
  • .groups 获得分组对象,indices遍历迭代
fun main(args : Array<String>){
    val regex = """(\d{2})-\1""".toRegex()
	val matchResultFinAll = regex.findAll("20-20 11-12 111-111")
	for(mc in matchResultFinAll) {
		println("fiall ${mc.value}")
		
		val mgroups = mc.groups
		for (i in mgroups.indices){
			println("$i ${mgroups[i]?.value}")
		}
	}	
}

fiall 20-20
0 20-20
1 20
fiall 11-11
0 11-11
1 11

8.泛型

将类型作为参数进行传递

8.1. 泛型函数

fun <T> afun(t: T) :T{
    return t
}
val a1 = afun(1)
val a2: Int = afun<Int>(1)

8.2. 泛型类

class A12<T>(var t: T)

//java和kotlin不同
kotlin不支持原始类型,如果需要显式指定泛型变量的类型必须明确指定类型参数,不能省略
A12<String>可以,或者A12(1)也可以,不能A12<>

8.3. 泛型参数约束

  • 限制泛型上界,必须是某个具体类的子类
    • class A12<T: Number>(var t: T)
  • 指定多个上界 使用where
fun<T>printIfNumberOrString(t: T)where T : Number, T : String {
    when (t) {
        is Number -> println("Number: $t")
        is String -> println("String: $t")
        else -> println("Unknown type")
    }
}

8.4. 类型型变

一个泛型类可以创建多个拥有不同类型参数的对象,这些类型相同但是类型参数不同的对象间进行转换的过程叫型变.kotlin默认不支持型变无法将泛型类对象直接赋值给另一个属于同样泛型类的对象

8.4.1. 在声明处协变 -作用范围是整个类

b类是a类的子类,那么b类的类型参数可以作为a类的类型参数的子类型,这种特性叫做协变。

  • 使用协变需要在类型参数前加上out关键字
    • class A(val obj: T)
  • 如果类型参数是协变的,只能用于输出类型。

8.4.2. 在声明处逆变 -作用范围是整个类

b类是a类的子类,b类的参数类型作为a类参数类型的父类型,成为逆变

  • 需要in关键字
  • 只能用于输入类型

8.4.3. 在使用处型变 -作用在函数内部

  • 在函数的类型参数前加上in或者out关键字

8.4.4. 星号投影 *

完全不知道类型参数是什么类型时候使用*

  • 使用星号投影时会自动将类型限制为Any类型或者Nothing类型。
    • 对于普通泛型类A,A<*>在读取时相当于A,在写入时相当于A
    • 对于声明式协变类A,A<*>等价于A<out Any?>,取值时返回Any?类型
    • 对于声明式逆变类A,A<*>等价于A<in Nothing?>,无法进行任何写入操作

8.5. 类型擦除

编译后的字节码中不会包含泛型类型信息,kotlin通过内联函数使用关键字reified修饰类型参数保留泛型类型信息。

9.扩展和委托

9.1. 扩展

为现有的类添加新的方法和属性。

9.1.1. 扩展方法

class A(var x: Int)
//扩展方法
fun A.funx(y: Int){
    //使用this调用该方法的A类的对象
    this.x +=y
    //如果类中已经有了funx名称的方法,实际调用的总是类中定义的方法
}

9.1.2. 扩展属性

因为定义过的类没有额外空间存储新的属性只,所以扩展属性不能有初始值和幕后字段

//为List类添加一个能够直接访问第二个元素的扩展属性
val <T> List<T>.second: T
    get{} =[1]

9.1.3. 扩展伴生对象

class A(var title:String){
    companion object{
        fun crA(title: String) : A{
            return A(title)
        }
    }
}

//为a类的伴生对象扩展一个方法
fun A.Companion.crA2(): A{
    return A("a2")
}
//测试扩展方法
A.crA2()

9.1.4. 扩展可空类型

可以在扩展功能中添加接受者为空时该进行的业务逻辑,这样在实际调用可空类型的扩展功能时就不用强制加上安全操作符了。

fun BigDecimal?.add(y: BigDicmal): BigDecimal {
    if(this == null){
        return y
    }
    return this.add(y)
}
//测试
val x: BigDecimal? = null
println(x.add(BugDecimal(100)))

9.1.5. 静态绑定

在调用扩展方法时,接受者类型为调用者的声明类型,而不是程序运行时的实际类型。

9.2. 委托

委托模式是设计模式中的一种,是指类中成员方法的实际执行过程是由另一个类的方法来完成,没有继承那样的强关联关系,是继承的轻量级替代方案。

9.2.1. 使用方法委托

9.2.1.1. 在应用层实现委托

代码层面实现委托模式,接口包含多少方法,委托类也要一一实现这些方法,过程很繁琐。

Kotlin直接在语言层提供了委托类的创建方法

9.2.1.2. 在语言层实现委托

委托的类型 by 委托的对象

在类声明语句的末尾加上以上语句,编译器会自动生成对应的委托方法,并将这些方法交给委托对象来执行。

假设有一个接口 Printer 定义了打印方法:

kotlinCopy Codeinterface Printer {
    fun print(message: String)
}

现在,我们有一个 ConsolePrinter 类,它实现了 Printer 接口,并提供了打印到控制台的功能:

kotlinCopy Codeclass ConsolePrinter : Printer {
    override fun print(message: String) {
        println(message)
    }
}

然后,我们有一个 FilePrinter 类,它也实现了 Printer 接口,但提供了将消息写入文件的功能:

kotlinCopy Codeclass FilePrinter : Printer {
    override fun print(message: String) {
        // 将消息写入文件的逻辑
    }
}

现在,假设我们有一个 Logging 类,它需要打印日志。我们可以使用方法委托来将打印操作委托给 Printer 接口的实现类。

kotlinCopy Codeclass Logging(printer: Printer) : Printer by printer {
    fun log(message: String) {
        print("Log: $message")
    }
}

在这个例子中,Logging 类通过 by 关键字委托了 print 方法给传入的 printer 对象。这意味着调用 Logging 类的 print 方法实际上会调用 printer 对象的 print 方法。

现在,我们可以创建 Logging 的实例,并根据需要选择将日志打印到控制台还是写入文件:

kotlinCopy Codeval consoleLogging = Logging(ConsolePrinter())
val fileLogging = Logging(FilePrinter())

consoleLogging.log("This is a console log message")
fileLogging.log("This is a file log message")

通过方法委托,我们可以轻松地实现代码的重用和组合,而无需编写重复的代码逻辑,如果需要在委托方法中执行自定义惭怍,可以在类中重写override

9.2.2.使用属性委托

将属性的读写访问委托给其他类对象的getValue()/setValue()方法来执行。

val/var <属性名>: <类型> by <委托对象>

//类成员的属性委托
class L{
    //将la属性委托给Delei对象
    var la: Int by Delei()
}
class Delei{
    private var logi1=-1
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int{
        return logi1
    }
    //thisRef表示实际被委托的对象,prop表示对委托属性的引用
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int){
        logi1 = value
    }
}
//局部变量的属性委托
val aa: Int by Delei()
println(aa)

9.2.3. 使用内置的属性委托

9.2.3.1. 延迟加载属性委托 lazy

一般对象创建时属性值就确定了,使用lazy,为属性设置初始值从创建对象时候延迟到了第一次使用该属性的时候。

val lazyValue: String by lazy {
    println("Computed!")
    "Hello"
}

fun main() {
    println(lazyValue) // 第一次访问,lazyValue 会被初始化
    println(lazyValue) // 第二次访问,直接使用上次初始化的值
}

lazy()函数带参数的版本,LazyThreadSafetyMode.SYNCHRONIZED 同步,一次只有一个线程初始化,相同的初始化值:

val lazyValue: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
    println("Computed!")
    "Hello"
}

fun main() {
    val thread1 = Thread {
        println("Thread 1: ${lazyValue}")
    }
    val thread2 = Thread {
        println("Thread 2: ${lazyValue}")
    }

    thread1.start()
    thread2.start()
}
//如果 lazyValue 属性尚未被初始化,两个线程都会进入 lazy 块中进行初始化,
//但由于采用了线程安全模式 LazyThreadSafetyMode.SYNCHRONIZED,
//只有一个线程会成功执行初始化,而另一个线程会等待。
//初始化完成后,两个线程都会得到相同的已初始化值。

LazyThreadSafetyMode.PUBLICATION 多线程值保存第一个完成计算的结果:

LazyThreadSafetyMode.NONE 无限制,可能多次打印Computed,最终取得的值可能是任意线程的结果

9.2.3.2. 观察者属性委托 ReadWritePropertylei类的Delegates对象observable()方法

用于观察属性值的变化,并在属性变化后执行用户自定义的操作。

依赖 ReadWriteProperty类的单例对象Delegates的observable()方法实现,第一个参数是初始值,第二个参数为lambda表达式,当属性变化时,lamdba表达式会执行.

import kotlin.properties.Delegates

class Person {
    var name: String by Delegates.observable("Initial Value") { prop, old, new ->
        println("$old -> $new")
    }
}
fun main() {
    val person = Person()
    person.name = "Alice"
    person.name = "Bob"
}
//输出结果
//Initial Value -> Alice
//Alice -> Bob

vetoable() 的lambda表达式包含布尔类型的返回值,若返回true执行对属性的修改,否则取消对属性的修改:

import kotlin.properties.Delegates

class Person {
    var age: Int by Delegates.vetoable(18) { prop, old, new ->
        if (new < 0) {
            println("Age cannot be negative, resetting to $old.")
            false // 取消修改
        } else {
            println("Setting age to $new.")
            true // 属性值修改
        }
    }
}

9.2.3.3. 非空属性委托 也依赖Delegates对象 notNUL()方法

kotlin中非空属性定义类时就要执行初始化操作,使用委托将初始化延迟到使用属性时

var notnullStr: String by Delegates.notNUll<String>(),如果再使用时没有赋值,会报错IllegalStateException异常

9.2.3.4. 使用映射属性委托 Map MutableMap

允许将类中的属性值保存在map对象中,访问属性就变成了访问改变map对象的键值对,只需在构造方法中定义一个map参数。

class P(map: Map<String,Any?>){
    val name: String by map
    val age: Int by map
}
//使用
val p = P(mapof(
    "name" to "liangpu",
    "age" to 40
))
println(p.name)//相当于p.map["name"]

9.2.4. 委托工厂类

基于provideDelegate()方法,返回一个继承ReadWriteProperty类的子类,该子类是实际的委托类

class L{
    //la属性使用了属性委托
    var la: String by LFactory()
}
//委托工厂类
class LFactory{
    //返回实际的委托类
    operator fun provideDelegate(thisREf: L, prop: KProperty<*>): ReadWriteProperty<L,String>{
        return LDelegate()
    }
}
//委托类
class LDelegate : ReadWriteProperty<L,String>{
    private val value = "file"
    override fun getValue(thisRef: L, property: KProperty<*>): String{
        return value
    }
    //thisRef表示实际被委托的对象,prop表示对委托属性的引用
    override fun setValue(thisRef: L, property: KProperty<*>, value: String){
        this.value = value
    }
}

10.函数高级运用

10.1. 高阶函数

将函数作为参数或返回值的返回

10.1.1. 函数类型

(参数类型列表) -> (返回值类型)

调用函数类型变量:


var sumc: (Int) -> Int = fun(x: Int): Int{
    return x + 1
}
println(sumc(10))

将函数声明赋值给函数类型变量:

fun sumaddc(x: Int): Int{
    return x + 1
}
fun main(args: Array<String>){
    //::为反射api中的操作符
    val incref: (Int) -> Int = ::sumaddc
    println(incref(10))
}

10.1.2. 将函数作为参数

val ina = fun(x: Int): Int{
    return x + 1
}
val ind = fun(x: Int): Int{
    return x - 1
}
//高阶函数,参数为函数类型
fun jisuan100(f: (Int) -> Int): Int{
    return f(100)   
}
fun main(args: Array<String>){
    println(jisuan100(ina))
    println(jisuan100(ind))
}

10.1.3. 将函数作为返回值

//高阶函数,返回值为函数类型
fun jisuan( f: Int): Pair<Int,(Int) -> Int>{
    fun inta( x: Int): Int {
        return x+f
    }

    return Pair(f+1,::inta)
}
fun main(args: Array<String>){
    val (xf,funa) = jisuan(12)
    println(funa(xf))//25
}

10.2. 匿名函数和函数表达式

10.3. lambda表达式

{参数列表 -> 执行语句}

lambda表达式和匿名函数的区别

  • lambda表达式最后一行结果自动返回,匿名函数需要return
  • lambda表达式无法声明返回类型,匿名函数需要声明返回值类型
  • 如果可以推断出参数类型,lambda表达式参数类型可以省略,匿名函数不可以

如果函数参数列表最后一个参数是函数类型并且实参是lambda表达式,kotlin允许吧lambda表达式提取到参数列表的小括号之外,如果只有一个函数类型参数,可以省略小括号

fun addi(a: Int, f: (Int) -> Int) : Int {
    return f(a)
}
fun main(args: Array<String>){
    val x = addi(1){a ->a+1}
    println(x)//2
}

lamdba返回数据

fun addi(a: Int, f: (Int) -> String)  {
    for (i in 1..a){
        println("i:$i   , 结果是${f(i)}")

    }
}
fun main(args: Array<String>){
    addi(10){
        if(it % 2 ==0) {
            return@addi "偶数"
        }else{
            return@addi "奇数"
        }
    }
}
//1-10个数字

10.4. 内联函数

Kotlin 中的一种特殊函数,它允许在调用函数时将函数体内的代码直接插入到调用处,而不是像普通函数那样创建一个新的函数调用栈帧。这样做的好处是可以减少函数调用的开销,特别是在一些频繁调用的场景下,如高阶函数或函数式编程中常见的情况。是以空间换时间。

10.4.1. 创建内联函数 inline

要声明一个内联函数,只需在函数前面加上 inline 关键字即可。内联函数通常用于高阶函数,比如接受函数作为参数或返回函数的函数。

以下是一个简单的内联函数的示例:

inline fun <T> myInlineFunction(action: () -> T): T {
    println("Before action")
    val result = action()
    println("After action")
    return result
}

fun main() {
    val value = myInlineFunction { 
        println("Executing action")
        42
    }
    println("Value: $value")
}

10.4.2. 取消参数内联 noinline

默认情况下,内联函数内的函数类型参数也会被内联到函数调用处。如果希望某些参数不被内联,使用noinline修饰

inline fun reduce(list: List<Int>,noinline f: (Int,Int) -> Int): Int{
    var result = 0
    for (i in list){
        result = f(i,result)
    }
    return result
}

10.4.3. 本地返回与非本地返回

  • 在调用函数时,如果该函数包含return语句,那么程序执行到该语句时会退出该函数并返回到调用处,这种方式叫做本地返回或局部返回
  • 由于内联函数是直接复制到调用处,所以调用return语句时可以直接退出调用处,这种方式叫非本地返回或非局部返回
inline fun traverse(list: List<Any>,f: (Any)->Unit){
    for(item in list){
        println("应用$item")
        f(item)
    }
}
fun main(args: Array<String>){
    println("执行内联函数前")
    traverse(listOf(1,2,3)){
        if(it==2){
            return
        }
    }
    println("执行内联函数后")
} 

用crossiinline修饰内联函数的函数类型参数,这样内联函数内部试用rerun语句会直接报错。

10.5. 尾递归函数 tailrec

函数调用自身是递归函数;如果函数最后一个语句是调用函数自身的语句,不能出现任何运算符,编译器会优化尾递归函数:

tailrec fun factorial(n: Int, accumulator: Int = 1): Int {
    if (n == 1) {
        return accumulator
    }
    return factorial(n - 1, n * accumulator)
}

fun main() {
    val result = factorial(3,2)
    println("Factorial of 5 is $result")
}

10.6. 函数式编程

面向对象编程OO、过程式编程PO 和函数式编程编程FO 规范

  • PO 分析出解决问题的所需要的步骤,然后用函数把这些步骤一步一步实现
    • 第一步打开冰箱,第二步把大象放进去,第三步观赏冰箱门
  • OO 把所涉及到的事物分解成一个个对象,然后由对象之间加工合作,就是(对象和方法)
    • 创建大象和冰箱两个类,大象有钻进冰箱的方法,冰箱有打开关闭的方法

10.6.1. map()方法

数据转换为另一种类型数据 map(transform: (T)->R): List<R>

fun main(args: Array<String>){
    val numbers1 = intArrayOf(1,2,3,4,5,6)
    //将Int类型计算后转为Int类型
    val numbersRet =  numbers1.map{it * 10}
    //输出10,20,30,40,50,60
    println(numbersRet)

    val num1 = listOf(20,30,40)
    //将Int类型转为string
    val str = num1.map{ if it>60 "及格" else "不及格"}
    println(str)
}

10.6.2. flatMap()方法

将多个List类型中的数据抽取出来组装一个List类型的数据

flatMap(transform: (T) -> Iterable<R>): List<R>

fun main(args: Array<String>){
    val list1 = listOf(1,2,3)
    val list2 = listOf(5,6,7)
    val numbers = listOf(list1,list2)
    val flatMapR: List<Int> = numbers.flatMap{it}
    println(flatMapR)
}

10.6.3. zip()方法

按位置连接两个数据集中的数据,生成一个新的数据集,新的数据集的长度取决于两个数据集中长度更短的``zip(other: Array<out R>): List<Pair<T,R>>

fun main(args: Array<String>){
    val list1 = listOf(1,2,3)
    val list2 = listOf(5,6,7)
    val zip1: List<Pair<Int,Int>> = list1.zip(list2)
    val zip2: List<Pair<Int,Int>> = list2.zip(list1)
    println(zip1)//[(1, 5), (2, 6), (3, 7)]
}

10.6.4. reduce()方法 reduceRight()方法

对数据集中的所有数据进行累计操作,要求累计值类型和结果类型一直 reduce(operation: (acc: S,T) -> S): S

fun main(args: Array<String>){
    val list1 = listOf(1,2,3)
    val r = list1.reduce{acc,n -> acc-n}
    println(r)//-4   
}

10.6.5. fold()方法 foldRight()方法

和reduce()类似对数据进行累计操作,但是fold()可以制定初始值 fold(initial :R, operation:(acc: R,T) -> R): R ,并且fold()的累计值类型和结果类型无限制

fun main(args: Array<String>){
    val list1 = listOf(1,2,3)
    val r = list1.fold(2){acc,n -> acc-n}
    println(r)//-4   
}

10.6.6. filter()方法

过滤数据并生成新数据集 filter(predicate: (T) -> Boolean): List<T>

fun main(args: Array<String>){
    val list1 = listOf(1,2,3)
    val r = list1.filter{it%2 == 0}
    println(r)//[2] 
}

10.6.7. forEach()方法

对数据进行遍历操作 forEach(action: (T) -> Unit): Unit

fun main(args: Array<String>){
    val list1 = listOf(1,2,3)
    val r = list1.forEach{print("$it->")}//1->2->3->
}

10.6.8. partition()方法

对数据集进行分区操作,如果true放到第一个分区,flase放到第二个分区 partition(predicate: (T) -> Boolean): Pair(List<T>,List<T>)

fun main(args: Array<String>){
    val list1 = listOf(1,2,3,4,5,6)
    val r = list1.partition{it%2 == 0}
    println(r)//([2, 4, 6], [1, 3, 5])
}

10.6.9. flatten()方法

nestedList.flatten() 将嵌套列表展平为单一列表

10.6.10. map-reduce操作实例wordcount

fun main() {
    // 模拟文本文件内容
    val text = listOf(
        "hello world",
        "hello kotlin",
        "kotlin world"
    )

    // 使用 flatMap() 方法将每一行文本分割成单词列表
    val words = text.flatMap { it.split(" ") }

    // 使用 groupBy() 方法按单词分组,并计算每个单词出现的次数
    val wordCount = words.groupBy { it }.mapValues { it.value.size }

    println("Word count: $wordCount")//Word count: {hello=2, world=2, kotlin=2}
}
//
fun main() {
    val text = listOf(
        "Hello world",
        "Kotlin is awesome",
        "Hello Kotlin"
    )
    val wordCountMap = text
        .map { it.split(" ") } // 将每行文本拆分成单词列表
        .flatten() // 扁平化单词列表
        .map{Pair(it,1)}
        .fold(mutableMapOf<String, Int>()) { acc, word ->
            val count = acc[word.first]
            acc[word.first] = (count?:0) + word.second
            acc
        }

    println("Word Count:")
    wordCountMap.forEach { (word, count) ->
        println("$word: $count")
    }
}