Kotlin核心知识梳理

文章目录

一、数据类型

1.1、基本数据类型定义

image-20210908111833643
val anInt: Int = 8
val aLong: Long = 12368172397127391
val aFloat: Float = 2.0F
val aDouble: Double = 3.0
val aShort: Short = 127
val maxByte: Byte = Byte.MAX_VALUE

//延迟初始化lateinit,可不用赋值
lateinit var s:String

1.2、模板操作符

val arg1: Int = 0
    val arg2: Int = 1
    val charArray:CharArray= charArrayOf('a','b')
    println("" + arg1 + " + " + arg2 + " = " + (arg1 + arg2))
    println("$arg1 + $arg2 = ${arg1 + arg2}")
//运行结果
//0 + 1 = 1
//0 + 1 = 1

    val rawString: String = """
        ${charArray[0]}
        \t
        \n
\\\\\\$$$ salary
    """
    println(rawString)
//运行结果(下面是模板操作符的打印,这里没添加//注释)
Hello "Trump"
$salary

        a
        \t
        \n
\\\\\\$$$ salary

1.3、类和对象初始化

//1,只有一个构造函数时可以省略constructor关键字
open class MyClass (name:String, sex:String): Any() {
		//2.第一个构造函数默认调用init方法,其余的不调用
    init {
        println(this.javaClass.name+"构造方法调用name:$name sex:$sex")
    }
}

class MyClass1(name:String,sex:String):MyClass(name,sex)


fun main() {
    val myClass=MyClass1("zhanglei","男")
    println(myClass is MyClass1)
}

//返回结果
//com.hongshi.test.MyClass1构造方法调用name:zhanglei sex:男
//true

1.4、空类型和智能类型转换

空类型(任意类型都有可空和不可空两种)

val notNull:String = null//错误,不能为空
val nullable:String?= null//正确,可以为空
notNull.length//正确,不为空的值客户直接使用
nullable.length//错误,可能为空,不能直接获取长度
nullable!!.length//正确,强制认定nullable不可空
nullable?.length//正确,若nullable为空,返回空
//kotlin中null不属于基本数据类型,在数据类型后面加"?"允许返回null
fun getName():String?{
    return null
}

fun main() {
    val name= getName()

    //type1
    if (name!=null){
        println(name.length)
    }
    println(name?.length)//判空写法

    //type2:
    if (name==null){
        return
    }
    println(name.length)
    val name= getName() ?: return//判空三目表达式
}

类型转换

//as 类型强转(Child)parent)
//as? 安全的类型强转,转换失败返回null
val child: Child? = parent as? Child

智能类型转换(编译器推导,远离无用的类型转换)

open class Parent()
open class Child():Parent(){
    fun getString(){

    }
}
fun main(args: Array<String>) {
    val parent: Parent = Parent()
    if (parent is Child){
    		//只能类型转换
        parent.getString()
    }
}

as 新增导包可以取别名

import com.hongshi.test2.Test2 as Test2

1.5、数字类型转换

String转数字,使用toDoubleOrNull、toIntOrNull

val number1: Double? = "8.98".toDoubleOrNull()  //8.98
val number2: Int? = "8.98".toIntOrNull()        //null

double转int,使用toInt、roundToInt

println(8.9856.toInt())                         //8
println(8.9856.roundToInt())                    //9

数字四舍五入到2位小数

val s = "%.2f".format(8.956756)     //8.96

数字去尾法到2位小数

val format = DecimalFormat("0.##")
//未保留小数的舍弃规则,RoundingMode.FLOOR表示直接舍弃。
format.roundingMode = RoundingMode.FLOOR
val s2 = format.format(8.956756)    //8.95

二、数组

2.1、定义方式

val arrayOfInt: IntArray = intArrayOf(1,3,5,7)
val arrayOfChar: CharArray = charArrayOf('H', 'e','l','l','o','W','o','r','l','d')
val arrayOfString: Array<String> = arrayOf("我", "是", "码农")

fun main(args: Array<String>) {
    println(arrayOfChar)
    println(arrayOfChar.joinToString(""))
    println(arrayOfChar.joinToString())
    println(arrayOfInt.slice(1..3))
    
    //此外,数组大小
    println(arrayOfInt.size)
    //数组改变指定值
    arrayOfInt[1]=111
    //数组遍历
    for(item in arrayOfInt){
        println(item)
    }
}
//运行结果
//HelloWorld
//HelloWorld
//H, e, l, l, o, W, o, r, l, d
//[3, 5, 7]

定制化数据与普通数据赋值对比

val helloWorldArray: Array<Char> = arrayOf('H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd')
val helloWorldCharArray: CharArray = charArrayOf('H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd')

其中基本数据类型的数组一共8个(去除包装类定制的数组)

image-20210730085407937

2.2、数组处理方法

2.2.1、slice用法
val str:String="HelloWorld"

fun main() {
    println(str.slice(0..1))//[0,1]
    println(str.slice(0 until 1))//[0,1)
}
//返回结果
//He
//H
2.2.2、String接收字符串数组初始化
val helloWorldCharArray: CharArray = charArrayOf('H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd')

fun main() {
    println(String(helloWorldCharArray))
}
//返回结果
//HelloWorld
2.2.3、遍历
val arrayOfInt: IntArray = intArrayOf(1,3,5,7)
    //type1:for-each循环
    for (item in arrayOfInt){
        println(item)
    }
    //type2:array的扩展方法forEach循环
    arrayOfInt.forEach {
        println(it)
    }
		//type3:for-each循环-withIndex
		for ((index,value) in arrayOfInt.withIndex()){
        println("$index -> $value")
    }
    for (item in arrayOfInt.withIndex()){
        println("${item.index} -> ${item.value}")
    }

forEach里面的其实是个lambda表达式,点击跳转到lambda表达式高级写法

2.2.4、解构语法

一次性给多个变量赋值

var names = "张三,李四,王五"
var array = names.split(",")
var (zhangsan, lisi, wangwu) = array
println(zhangsan)
println(lisi)
println(wangwu)

三、程序结构

3.1、常量与变量(val,var)

3.1.1、介绍

1、变量var

var str

可随时修改

2、运行时常量val

val str

并非绝对只读,虽然能修改但不建议

3、编译时常量val(等价于java的final)

const val str

备注:编译时常量只能在函数之外定义,因为编译时常量必须在编译时赋值,而函数都是在运行时调用,函数内的变量也是在运行时赋值,编译时常量要在这些赋值前就已存在。

编译时常量只能是常见的基本数据类型:String、Int、Double、Float、Long、Short、Byte、Char、Boolean

3.1.2、延迟初始化

1,var前面加lateinit修饰

2,val后面加by lazy lambda初始化(使用时初始化)

class X
class A {
    lateinit var s:String   //var延迟初始化
    val x:X by lazy {
        println("init x")
        X()
    }
}

fun main() {
    println("start")
    val a=A()
    println("a初始化完毕")
    a.s="123"
    println(a.s)

    println(a.x)
}
//返回结果
//start
//a初始化完毕
//123
//init x
//com.hongshi.test.X@61bbe9ba
3.1.3、不建议var cc:String?=null声明变量
class A {
    //声明变量,初始值为null
    var cc:String?=null

    //声明变量,后面再初始化
    lateinit var cc2:String
}

fun main() {
    val a=A()
    
    //后面每个表达式都需要加上判空
    println(a.cc?.length)

    //不需要判空
    a.cc2="asdf"
    println(a.cc2.length)
}
3.1.4、=

kotlin中的=相当于java的:比较地址

kotlin中的==相当于java的equals:比较内容

var str1 = "Zhangsan"
var str2 = "Zhangsan"
println(str1 == str2)                           //true 内容相同
println(str1 === str2)                          //true 常量不可变,因此这里做了优化没有再开辟内存
println(str1 === ("zhangsan".capitalize()))     //false 另外创建了"zhangsan"、"Zhangsan"
println(str1 === ("Zhangsan".capitalize()))     //true 未再次开辟内存

3.2、函数

image-20210730085407937
3.2.1、基础写法
//返回值默认Unit,相当于java的void
fun main(args: Array<String>) {
    //基本数据类型转换
    val arg1 = args[0].toInt()
    val arg2 = args[1].toInt()
    println("$arg1 + $arg2 = ${sum(arg1, arg2)}")
}
//返回值:Int
fun sum(arg1: Int, arg2: Int): Int{
    return arg1 + arg2  //返回结果:1 + 2 = 3
}

main函数的入参可以这里输入,也可以通过命令行输入

kotlin com.TestKt.kt 1 2
image-20210730102332386 image-20210730102349839
3.2.2、简化与匿名函数
//sum函数简写为
fun sum(a:Int,b:Int)=a+b
//匿名函数,类似c中函数指针
var sum=fun(a:Int,b:Int)=a+b
val total = "Mississippi".count();
val totalS = "Mississippi".count { letter -> letter == 's' }

//返回Unit对象可以直接等号后写执行语句
var print=fun(a:Int,b:Int)=printf("asdf")

指定函数类型

// 指定为() -> String函数类型
val testFun1: () -> String = { "test1" }
// 也可以先声明再复制
val testFun2: () -> String
testFun2 = { "test2" }
3.2.3、lambda表达式

lambda是匿名函数

写法{[参数列表]->[函数体,最后一行是返回值]}

3.2.3.1、普通写法
//普通写法
var sum={a:Int,b:Int->a+b}
//没有入参的写法
var printHello={
    println("printHello")
}
//有返回值的写法(如下图所示)
var sum={
    println("printHello")
    1+2
}
//返回结果
//printHello
//3
image-20210730133919832

lambda表达式带入参、返回值的完整写法

val sum = { arg1: Int, arg2: Int ->
    println("$arg1 + $arg2 = ${arg1 + arg2}")
    arg1 + arg2
}
fun main(args: Array<String>) { // (Array<String>) -> Unit
    println(sum(1, 3))
    //这里()调用等价于调用invoke方法,invoke方法:kotlin中运算符重载为方法
    println(sum.invoke(1, 3))
}
3.2.3.2、简化

lambda表达式只要一个参数可以默认为it

val arrayOfInt: IntArray = intArrayOf(1,3,5,7)
    arrayOfInt.forEach {
        println(it)
    }

    //forEach传入参数如果只有一个,可以不写,用it代替
    //也可以加上括号,本质上是lambda表达式的函数体
    arrayOfInt.forEach ({ println(it) })
  • step 1:forEach完整写法
arrayOfInt.forEach ({ element-> println(element) })
  • step 2:加上括号

目前JVM上支持的语言光脚本就有一百多种,Kotlin、Groovy、Scala等如果它的参数最后一个是lambda表达式,这个大括号可以移到小括号外面

Scala:想解决Java表达能力不足的问题

Groovy:想解决Java语法过于冗长的问题

Clojure:想解决Java没有函数式编程的问题

Kotlin:想解决Java

arrayOfInt.forEach (){ println(it) }
  • step 3:删除括号

idea提示小括号没用,可以删除

arrayOfInt.forEach { println(it) }
  • step 4:最终写法:reference

idea提示"Convert lambda to reference"

println函数名作为参数传给forEach,同时println接收Any?任意参数

prinln源码:

@kotlin.internal.InlineOnly
public actual inline fun println(message: Any?) {
    System.out.println(message)
}

forEach源码:

image-20210730144117763

forEach接收参数T,而Any相当于java的Object,为任何参数的父类或者间接父类。println通过reference使用了forEach的参数T。

入参、返回值与形参一致的函数可以用函数引用的方式作为实参传入。最终写法如下

arrayOfInt.forEach(::println)
3.2.3.3、forEach退出循环

forEach接收lambda表达式,如果直接return会造成整个main方法的return

fun main() {
    val arrayOfInt: IntArray = intArrayOf(1,3,5,7)
    arrayOfInt.forEach {
        if (it==5){ return }
        println(it)
    }
    println("end")
}
//返回结果(lambda表达式return造成不能执行到println("end"),lambda函数不会)
//1
//3

解决方法,加上标签ForEach@,return@ForEach

注意:ForEach名称可以随便取

fun main() {
    val arrayOfInt: IntArray = intArrayOf(1,3,5,7)
    arrayOfInt.forEach asdf@{
        if (it==5){ return@asdf }
        println(it)
    }
    println("end")
}
3.2.4、函数类型
fun main() {
    //无参,返回Unit:()->Unit
    var printUsage=fun()= println("asdf")
    println(printUsage)
    //返回结果:Function0<kotlin.Unit>

    //无参,返回一个整型:()->Int
    var sum0=fun()=1
    println(sum0)
    //返回结果:Function0<java.lang.Integer>

    //传入整型,返回一个整型:(Int)->Int
    var sum1=fun(a:Int)=a
    println(sum1)
    //返回结果:Function1<java.lang.Integer, java.lang.Integer>

    //传入两个整型,返回一个整型:(Int,Int)->Int
    var sum2=fun(a:Int,b:Int)=a
    println(sum2)
    //返回结果:Function2<java.lang.Integer, java.lang.Integer, java.lang.Integer>

    //传入字符串,Lambda表达式,返回Boolean:(String,(String)->String)->Boolean
    var sum3=fun(a:String,b:(String)->String)=true
    println(sum3)
    //返回结果:Function2<java.lang.String, kotlin.jvm.functions.Function1<? super java.lang.String, ? extends java.lang.String>, java.lang.Boolean>

    //===========总结:invoke有几个参数就叫Function几

    //具名函数sum类型(Int,Int)->Int
    fun sum(arg1: Int, arg2: Int): Int{
        return arg1 + arg2  //返回结果:1 + 2 = 3
    }
    //对函数的引用reference
    println(::sum)
    //返回结果:function sum (Kotlin reflection is not available),反射无法用,使用is关键字判断
    println(::sum is (Int,Int)->Int)
    //返回结果:true
}
3.2.5、反引号中的函数名

kotlin通过反引号可以实现对函数名千奇百怪的命名

作用是可以解决kotlin调用java时,遇到保留字的问题

public class MyJava {
    public static void is(){
        System.out.println("调用java is 函数");
    }
}
fun main(args: Array<String>) {
    MyJava.is();    // 错误,is是kotlin保留字
    MyJava.`is`();  // 正确
}
3.2.6、it关键字

函数只有一个参数,可以用it关键字指代。

// 不使用it
val testFun1: (String) -> String = { test ->
    "hello ${test}"
}
// 使用it
val testFun1: (String) -> String = {
    "hello ${it}"
}
3.2.7、函数的参数是另外一个函数
fun main(args: Array<String>) {
    val checkFunc: (String) -> String = {
        "很棒${it}"
    }
    toast("小明", checkFunc)
}

fun toast(name: String, checkFunc: (String) -> String) {
    println("我认为$name${checkFunc(",非常棒")}");
}
3.2.8、函数内联

在jvm中,lambda表达式会以对象实例的形式存在,JVM会为所有同lambda打交道的变量分配内存,这就产生了内存开销。更糟的是,lambda的内存开销会带来严重的性能问题。幸运的是,kotlin有一种优化机制叫内联,有了内联,VM就不需要使用lambda对象实例了,因而避免了变量内存分配。哪里需要使用lambda,编译器就会将函数体复制粘贴到哪里。
使用lambda的递归函数无法内联,因为会导致复制粘贴无限循环,编译会发出警告。

private inline fun showOnBoard(goodsName:String, getDiscountWords: (String,Int) -> String){
    val hour = (1..24).shuffled().last()
    println(getDiscountWords(goodsName,hour))
}
3.2.9、函数引用

两个冒汗+函数名为具名函数引用

fun main() {
    showOnBoard("牙膏",::getDiscountWords)
}

private fun getDiscountWords(goodsName: String,hour: Int):String{
    val currentYear = 2027
    return "${currentYear}年,双11${goodsName}促销倒计时:$hour 小时"
}

private fun showOnBoard(goodsName:String, getDiscountWords: (String,Int) -> String){
    val hour = (1..24).shuffled().last()
    println(getDiscountWords(goodsName,hour))
}
3.2.10、函数类型作为返回类型

函数的返回值是另外一个函数,在Kotlin中,匿名函数能修改并引用定义在自己的作用域之外的变量,匿名函数引用着定义自身的函数里的变量,Kotlin中的lambda就是闭包。
能接收函数或者返回函数的函数又叫做高级函数,高级函数广泛应用于函数式编程当中。

fun main() {
    val getDiscountWords = configDiscountWords()
    println(configDiscountWords()("沐浴露"))
    // 也可以直接写作:
//    println(configDiscountWords()("沐浴露"))
}

fun configDiscountWords(): (String) -> String{
    val currentYear = 2027
    val hour = (1..24).shuffled().last()
    return {goodsName: String ->
        "${currentYear}年,双11${goodsName}促销倒计时:$hour 小时"
    }
}

fun configDiscountWords(): (String) -> String{
    return {goodsName: String ->
        val currentYear = 2027
        val hour = (1..24).shuffled().last()
        "${currentYear}年,双11${goodsName}促销倒计时:$hour 小时"
    }
}
3.2.11、使用非空断言操作符!!

!!.又称感叹号操作符,当变量为null时,会空指针异常

3.2.12、空合并操作符?:

?:相当于简化版的三目表达式

var str: String? = null;
str = str ?: "empty"

3.3、类成员

3.3.1、函数与方法的区别

In languages such as C++, functions are bits of code that will perform a particular action - but are not associated with an object. functions that are to do with an object are called methods. in java all functions are methods as they are all to do with objects.

方法位于对象上。

函数独立于对象。

对于Java,只有方法。

对于C,只有函数。

对于C++来说,这取决于你是否在一个类中

函数强调功能本身,不考虑从属

方法的称呼通常是从类的角度出发

总结就是一句话:类里边的是方法,类外边的是函数

3.3.2、定义属性

构造方法参数中val/var修饰的都是属性

类内部也可以定义属性,比如

class Hello(val a:int,b:int){
}
3.3.3、重载运算符

使用operator关键字重定义符号

image-20220515155800296
class Complex (var real:Double,var imaginary:Double){
    //重载运算符:Complex类型
    operator fun plus(other:Complex):Complex{
        return Complex(real+other.real,imaginary+other.imaginary)
    }
    //重载运算符:Int类型
    operator fun plus(other: Int): Complex {
        return Complex(real+other,imaginary)
    }

    override fun toString(): String {
        return "$real+${imaginary}i"
    }

    //DSL语法:infix中缀表达式
    infix fun on(temp:Complex):Boolean{
        //比较实部是否相等
        return temp.real==real;
    }
}

fun main() {
    val c1=Complex(3.0,4.0)
    val c2=Complex(2.0,7.5)
    println(c1+c2)//5.0+11.5i
    println(c1+5)//8.0+4.0i

    println(c1 on Complex(3.0,4.0))//true
    println(c1 on Complex(2.0,4.0))//false
}
3.3.4、range表达式

in A…B,in关键字用来检查某个值是否在指定范围。

val age = 3;
if (age in 0..3) {//[0,3]的闭区间
    println("婴幼儿")
}
3.3.5、条件表达式

条件表达式取的是条件语句的最后一个

val str=if (true){
    "str-1"
    "str-2"
}else{
    "str-3"
    "str-4"
}
println(str)
//输出结果
//str-2

同时try catch也是表达式

fun main() {
    var num=try {
        10/0
    }catch (e:Exception){
        0
    }
    println(num)
}
//返回结果
//0
3.3.6、when表达式

when表达式是加强版的switch,支持任意类型

when表达式第一个表达式执行了,不用break后面的也不会执行了

val x = 5
when (x) {
    is Int -> println("x是Int类型")
    in 1..100 -> println("x在1..100区间内")
    else -> println("aad")
}
3.3.7、多层循环嵌套的终止结合标签使用

举例一个普通的while循环,自增到2则退出循环

fun main() {
    var i=0
    while (true){
        println("i的值为$i")
        i++
        if (i===2){
            break
        }
        var j=0
        while (true){
            println("j的值为$j")
            j++
            if (j===2){
                break;
            }
        }
    }
}
//输出
//i的值为0
//j的值为0
//j的值为1
//i的值为1

如果我们想要在j为2的时候终止整个循环,则需要添加标签

可以参考3.2.3.3、forEach退出循环

fun main() {
    var i=0
    outer@while (true){
        println("i的值为$i")
        i++
        if (i===2){
            break
        }
        var j=0
        while (true){
            println("j的值为$j")
            j++
            if (j===2){
                break@outer;
            }
        }
    }
}
//输出
//i的值为0
//j的值为0
//j的值为1
3.3.8、具名参数

在给函数传参数时把形参也附上,具名参数交换参数位置不影响

fun main() {
    fun sum(arg1:Int,arg2:Int)="arg1:$arg1,arg2:$arg2"
    println(sum(1,2))
    println(sum(arg1 = 1,arg2 = 2))

    //具名参数
    println(sum(arg2 = 1,arg1 = 2))
}
//返回结果
//arg1:1,arg2:2
//arg1:1,arg2:2
//arg1:2,arg2:1
3.3.9、变长参数

边长参数使用关键字vararg声明

注意java的变长参数只能放在最后位置,而kotlin由于具名函数的存在,变长参数可以放在任意位置

fun main() {
    hello(1,2,3,4,string = "asdf")
}
fun hello(vararg ints:Int,string:String){
    ints.forEach (::println)
    println(string)
}
//返回结果
//1
//2
//3
//4
//asdf
3.3.10、展开运算符(Spread Operator)

展开运算符:在数组前加上*可以展开里面的元素。展开运算符目前只支持数组,不支持list

val array= intArrayOf(1,2,3,4)
hello(*array,string = "asdf")

有点像javascriptES6的拓展操作符"…"

const arr=[1,2,3]
[...arr]
3.3.11、默认参数

默认参数:给函数参数指定默认值

使用展开运算符和默认参数可以实现函数所需的参数与实际传的参数不一致的效果

fun main() {
    val array= intArrayOf(1,2,3,4)
    hello(ints=*array,string = "asdf")
}
fun hello(double:Double=3.0,vararg ints:Int,string:String){
    ints.forEach (::println)
    println(string)
}
3.3.12、get set方法

kotlin中使用field指代当前属性变量

class TestBean{
    var name:String="张三"
    get() {
        return "name:$field"
    }
    set(value) {
        field=value
    }
}
fun main() {
    var testBean=TestBean()
    testBean.name="李四"
    println(testBean.name)
}
//返回结果
//name:李四
3.3.13、Nothing类型

TODO函数的任务就是跑出异常,就是用公园别指望它运行成功,返回Nothing类型。

fun main(args: Array<String>) {
    TODO("发生了错误")
}

3.4、标准库函数

3.4.1、使用带let的安全调用

安全调用允许在可空类型上调用函数,但是如果还想做点额外的事,比如创建新值,或判断不为u就调用其他函数,怎么办?可以使用带let函数的安全调用操作符。你可以在任何类型上调用lt函数,它的主要作用是让你在指定的作用域内定义一个或多个变量。

let函数最后一行是返回值

fun main(args: Array<String>) {
    var str: String? = "butterfly";
//    str = "";
    str = str?.let {
        if (it.isNotBlank()) {
            it.capitalize()
        } else {
            "butterfly"
        }
    }
    println(str)
}

let前使用问号”?“会判断调用者是否为null,从而起到三目表达式的效果

var str: String? = null;
str = str?.let { "a" } ?: "b"   // 输出b
println(str)
str = str.let { "a" } ?: "b"    // 输出a
println(str)

let可以起到简洁语法的效果

val result= listOf<Int>(3,4,5).first().let {
    it*it
}
3.4.2、apply函数

apply函数可看作一个配置函数,你可以传入一个接收者,然后调用一系列函数来配置它以便使用,如果提供lambda给apply函数执行,它会返回配置好的接收者。

val file1 = File("/User/zhangsan/test.text")
file1.setExecutable(false)
file1.setReadable(true)
file1.setWritable(true)

// 隐式调用,this就是这里的匿名类File()
val file2 = File("/User/zhangsan/test.text").apply {
    setExecutable(false)
    setReadable(true)
    setWritable(true)
}
3.4.3、let与apply函数区别

Iet函数能使某个变量作用于其lambda表达式里,让it关键字能引用它。Iet与apply比较,let会把接收者传给lambda,而apply什么都不传,匿名函数执行完,apply会返回当前接收者,而let会返回lambda的最后一行。

3.4.4、run函数

光看作用域行为,run和applya差不多,但与apply不同,run函数不返回接收者,run返回的是lambda结果,也就是最后一行的值。

var file=File("/Users/zhanglei/Desktop/NewFile.txt")
val result=file.run {
  // readText kotlin对文件读取的增强函数
    readText().contains("kotlin")
}
println(result)//true

支持传函数引用。

注意这里run传函数引用使用小括号

fun main(args: Array<String>) {
    val result = "check the express is too long".run(::isLong)
    println(result)//true
}

fun isLong(text: String) = text.length > 10

run函数支持链式调用

fun main(args: Array<String>) {
    "check the express is too long"
        .run(::isLong)
        .run(::showMessage)
        .run(::println)//name is to long
}

fun isLong(text: String) = text.length > 10

fun showMessage(isLong: Boolean): String {
    return if (isLong) {
        "name is to long"
    } else {
        "please rename"
    }
}
3.4.5、with函数

with函数是run的变体,他们的功能行为是一样的,但with的调用方式不同,调用with时需要值参作为其第一个参数传入。

val result = "check the express is too long".run { length > 10 }
val result2 = with("check the express is too long") {
    length > 10
}
println(result)//true
println(result2)//true
3.4.6、also函数

also函数和let函数功能相似,和let一样,also也是把接收者作为值参传给lambda,但有一点不同:also返回接收者对象,而let返回lambda结果。因为这个差异,also尤其适合针对同一原始对象,利用副作用做事,既然so返回的是接收者对象,你就可以基于原始接收者对象执行额外的链式调用。

适用于使用同一个it做处理的情况

fun main(args: Array<String>) {
    var fileContents: List<String>
    File("/Users/zhanglei/Desktop/NewFile.txt")
        .also {
            println(it.name)
        }.also {
            fileContents = it.readLines()
        }
    println(fileContents)
}
//NewFile.txt
//[Kotlin is the android offical lanugage., Kotlin is the android offical lanugage.]
3.4.7、takeif函数

和其他标准函数有点不一样,takelfi函数需要判断lambda中提供的条件表达式,给出true或false结果,如果判断结果是true,从takelfi函数返回接收者对象,如果是false,则返回null。如果需要判断某个条件是否满足,再决定是否可以赋值变量或执行某项任务,takelf就非常有用,概念上讲,takelfi函数类似于if语句,但它的优势是可以直接在对象实例上调用,避免了临时变量赋值的麻烦。

var result = File("/Users/zhanglei/Desktop/NewFile.txt")
    .takeIf { it.exists() && it.canRead() }
    ?.readText()
println(result)
//存在:Kotlin is the android offical lanugage.
//不存在:null
3.4.8、takeUnless

takelf辅助函数takeUnless,只有判断你给定的条件结果是false时,takeUnless才会返回原始接收者对象。

var result = File("/Users/zhanglei/Desktop/NewFile.txt")
    .takeUnless { it.isHidden }//非isHidden,也就是可见
    ?.readText()

四、面向对象

4.1、抽象类与接口

与java不同,kotlin允许接口有一个默认实现

interface B{
    var a: Int
    fun callback(){
        println("callback$a")
    }
}
class Test(override var a: Int) :B
fun main() {
    Test(1).callback()
}
//返回结果
//callback1

4.2、接口代理

实现接口的时候通过by关键字指定代理对象,可以不用再实现接口里的方法。例如普通接口我们需要覆写接口方法

interface writerInterface {
    fun write()
}

interface driverInterface {
    fun drive()
}

//普通实现需要覆写接口里的方法
class Person : writerInterface, driverInterface {
    override fun write() {
        println("=====write")
    }

    override fun drive() {
        println("=====drive")
    }
}

我们引入接口代理

class SuperPerson(val writer: writerInterface, val driver: driverInterface) :
    writerInterface by writer, driverInterface by driver

fun main() {
    class writer : writerInterface {
        override fun write() {
            println("=====write")
        }
    }

    class driver : driverInterface {
        override fun drive() {
            println("=====drive")
        }
    }

    val superPerson = SuperPerson(writer(), driver())
    superPerson.write()
    superPerson.drive()
}

五、Decompile Kotlin to Java

推荐工具将kotlin转化为java,便于学习kotlin的实质含义。

5.1、androidstudio

点击Tools -> Kotlin -> Show Kotlin bytecode

image-20210908110706597

点击Decompile

image-20210908110842299

得到翻译后的java代码

image-20210908111100758

5.2、idea

安装插件”Kotlin to Java decompiler“

image-20210908111347172

点击Code -> Decompile Kotlin to Java

image-20210908111511735

得到翻译后的java代码

image-20210908111833643

六、集合

6.1、List集合

6.1.1、List创建与元素获取

三种获取方式:list[3]、list.getOrElse、list.getOrNull

val list = listOf<String>("zhangsan", "lisi", "wangwu")
//println(list[3])//越界异常
println(list.getOrElse(3, { "lambda表达式" }))
println(list.getOrNull(3))
6.1.2、可变List集合

可变集合mutableList提供了add、remove方法

val mutableList = mutableListOf<String>("zhangsan", "lisi", "wangwu")
mutableList.add("test")
mutableList.remove("zhangsan")
println(mutableList)//[lisi, wangwu, test]

mutableList与list互转

val list1 = mutableListOf<String>("zhangsan", "lisi", "wangwu").toList()
val list2 = listOf<String>("zhangsan", "lisi", "wangwu").toMutableList()
6.1.3、mutator函数

能修改可变列表的函数有个统一的名字:mutator函数

运算符重载+=、-=

var mutableList = mutableListOf<String>("zhangsan", "lisi", "wangwu")
mutableList += "张三"
mutableList -= "zhangsan"
println(mutableList)//[lisi, wangwu, 张三]

removeIf遍历删除

var mutableList = mutableListOf<String>("zhangsan", "lisi", "wangwu")
mutableList.removeIf {
    it.contains("n")
}
println(mutableList)//[lisi]
6.1.4、集合遍历

for in遍历

var list = listOf<String>("zhangsan", "lisi", "wangwu")
for (item in list) {
    println(item)
}

forEach遍历

list.forEach {
    println(it)
}

forEachIndexed遍历

list.forEachIndexed { index, item ->
    println("$index,$item")
}
6.1.5、解构赋值中的占位符

解构赋值时,可以用下划线_占位

var list = listOf<String>("zhangsan", "lisi", "wangwu")
val (origin, x, proxy) = list;

show kotlin Bytecode,可以看到对x也进行了赋值。

public static final void main(@NotNull String[] args) {
      Intrinsics.checkNotNullParameter(args, "args");
      List list = CollectionsKt.listOf(new String[]{"zhangsan", "lisi", "wangwu"});
      boolean var7 = false;
      String origin = (String)list.get(0);
      var7 = false;
      String x = (String)list.get(1);
      var7 = false;
      String proxy = (String)list.get(2);
      boolean var5 = false;
   }
}

如果不需要,可以用下划线_替代

var list = listOf<String>("zhangsan", "lisi", "wangwu")
val (origin, x, proxy) = list;

show kotlin Bytecode

public static final void main(@NotNull String[] args) {
    Intrinsics.checkNotNullParameter(args, "args");
    List list = CollectionsKt.listOf(new String[]{"zhangsan", "lisi", "wangwu"});
    boolean var6 = false;
    String var2 = (String)list.get(0);
    var6 = false;
    String proxy = (String)list.get(2);
}

6.2、Set集合

set集合不允许重复

6.2.1、Set创建与元素获取
var set = setOf<String>("zhangsan", "lisi", "wangwu","lisi")
println(set.elementAt(1))//lisi
6.2.2、可变Set集合

可变set集合:mutableSetOf

var set = mutableSetOf<String>("zhangsan", "lisi", "wangwu", "lisi")
set += "test"

6.3、List与Set互转

toSet、toList

var list = listOf<String>("zhangsan", "lisi", "wangwu", "lisi")
    .toSet()
    .toList()
println(list)//[zhangsan, lisi, wangwu]

去重可以使用distinct

var list = listOf<String>("zhangsan", "lisi", "wangwu", "lisi")
    .distinct()
println(list)//[zhangsan, lisi, wangwu]

6.4、数组类型

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

image-20220127202810611
val intArray = intArrayOf(10, 20, 30)
listOf(10, 20, 30).toIntArray()
val array = arrayOf(10,20,30)
intArray.forEach {
    println(it)
}
array.forEach {
    println(it)
}

6.5、Map集合

6.5.1、Map创建

使用to关键词、Pair键值对

val map = mapOf("Jack" to 16, "Jason" to 17, "Jack" to 18)
val map2 = mapOf(Pair("jimmy", 20), Pair("Jacky", 20))

其中to关键词是内联函数,会转化为Pair

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
6.5.2、读取Map的值

[]取值运算符,读取键对应的值,如果键不存在就返回nul
getValue,读取键对应的值,如果键不存在就抛出异常
getOrElse,读取键对应的值,或者使用匿名函数返回默认值
getOrDefault,读取键对应的值,或者返回默认值

val map = mapOf("Jack" to 16, "Jason" to 17, "Jack" to 18)
println(map.getValue("Jack"))//18
println(map.getOrElse("Rose"){"unknown"})//unknown
println(map.getOrDefault("Rose",0))//0
6.5.3、集合遍历
val map = mapOf("Jack" to 16, "Jason" to 17, "Jack" to 18)
map.forEach { key, value ->
    println("$key,$value")
}
map.forEach {
    println("${it.key},${it.value}")
}
6.5.4、可变Map集合

运算符重载+=、put、getOrPut

val mutableMap = mutableMapOf("Jack" to 16, "Jason" to 17, "Jack" to 18)
mutableMap += "Jimmy" to 30
mutableMap.put("Jimmy", 30)
mutableMap.getOrPut("Rose") { 31 }
println(mutableMap)//{Jack=18, Jason=17, Jimmy=30, Rose=31}

七、类

7.1、field

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

7.1.1、默认的get、set方法
class Test {
    var name = "Jack"
}

Show kotlin bytecode

public final class Test {
   @NotNull
   private String name = "Jack";

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

   public final void setName(@NotNull String var1) {
      Intrinsics.checkNotNullParameter(var1, "<set-?>");
      this.name = var1;
   }
}
7.1.2、自定义get、set方法
class Test {
    var name = "Jack"
        get() = field.capitalize()
        set(value) {
            field = value.trim()
        }
}
7.1.3、计算属性

get方法获取的值是用表达式计算出来的,没用到field,我们叫做计算属性

class Test {
    val random
        get() = (1..6).shuffled().first()
}

fun main() {
    val test = Test();
    println(test.random)//随机数
}

7.2、对象初始化

7.2.1、主构造函数

和类名同名的函数叫构造函数,kotlin在这里做了简化

class Test(
    _name: String,
    _age: Int,
) {
    var name = _name
        get() = field.capitalize()
        set(value) {
            field = value.trim()
        }
    var age = _age
}

在主构造函数里定义属性

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

class Test(
    var name: String,
    var age: Int,
) {

}
7.2.2、次构造函数

次构造函数要继承于主构造函数,可以省略函数体

class Test(
    var name: String,
    var age: Int,
    var sex: String,
) {

    // 不带函数体
    constructor(name: String) : this(name, age = 10, sex = "女")

    // 带函数体
    constructor(name: String, sex: String) : this(name, age = 10, sex = "女") {

    }
}
7.2.3、默认参数

默认参数可以省略不传

class Test(
    var name: String,
    var age: Int = 18,
    var sex: String = "女",
) {

}

fun main() {
    val test = Test("张三")
    val test2 = Test(name = "张三")
}
7.2.4、初始化块

写法类似于java的静态代码块,在类加载时执行

kotlin的初始化代码块在构造函数之后执行。

class Test(
    var name: String,
    var age: Int = 18,
) {
    init {
        // require先决条件函数
        require(name.isNotBlank()) { "name must hava value" }
    }
}
7.2.5、初始化顺序

1、主构造函数里声明的属性。这里是val age。
2、类级别的属性赋值。这里是 var name、var score、val hobby
3、init初始化块里的属性赋值和函数调用。这里是subject
4、次构造函数里的属性赋值和函数调用。这里是this.score = 20;

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

Show kotlin bytecode

image-20220127202810611
7.2.6、延迟初始化

使用lateinit关键字相当于做了一个约定:在用它之前负责初始化
只要无法确认lateiniti变量是否完成初始化,可以执行isInitialized检查

class Player {
    lateinit var equipment: String
    fun ready() {
        equipment = "sharp knife"
    }

    fun battle() {
        if (::equipment.isInitialized) println(equipment)
    }
} 

fun main() {
    val p = Player()
    p.ready()
    p.battle()
}
7.2.7、惰性初始化

延迟初始化并不是推后初始化的唯一方式,你也可以暂时不初始化某个变量,直到首次使用它,这个叫作惰性初始化。

class Player(_name: String) {
    var name = _name
    val config by lazy { loadConfig() }
    fun loadConfig(): String {
        println("loading...")
        return "xxx"
    }
}

fun main() {
    val p = Player("Jack")
    Thread.sleep(3000)
    println(p.config)
}
//loading...
//xxx

7.3、open关键字

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

open class Product(val name: String) {
    fun description() = "Product:$name"
    open fun load() = "Nothing"
}

class LuxuryProduct : Product("Luxury") {
    //override是关键字,不再是注解
    override fun load() = "LuxuryProduct loading..."
}

fun main() {
    val p: Product = LuxuryProduct()
    println(p.load())//LuxuryProduct loading...
}

7.4、类型判断is、类型转换as

println(p is Product)//true
println(p is LuxuryProduct)//true
println(p is File)//false

if (p is LuxuryProduct){
    (p as LuxuryProduct).special()//LuxuryProduct specail function
}

智能类型转换

kotlin在转换过程中第一次转换成功,后面可以省略as的转换代码

(p as LuxuryProduct).special()
p.special()

7.5、Any超类

无须在代码里显示指定,每一个类都会继承一个共同的叫作Ay的超类。

println(p is Any)//true

7.6、对象声明

对象声明有利于组织代码和管理状态,尤其是管理整个应用运行生命周期内的某些一致性状态。

相当于单例

object ApplicationConfig{
    init {
        println("loading config...")
    }
    fun setSomething(){
        println("something")
    }
}

fun main() {
    //类名、实例名
    ApplicationConfig.setSomething()
    println(ApplicationConfig)
    println(ApplicationConfig)
}
//loading config...
//something
//ApplicationConfig@3a4afd8d
//ApplicationConfig@3a4afd8d

7.7、对象表达式

有时候你不一定非要定义一个新的命名类不可,也许你需要某个现有类的一种变体实例,但只需用一次就行了,事实上,对于这种用完就丢的类实例,连命名都可以省了。这个对象表达式是X的子类,这个匿名类依然遵循object:关键字的一个规则,即一旦实例化,该匿名类只能有唯一一个实例存在。

也是单例

open class Player {
    open fun load() = "loading nothing..."
}

fun main() {
    //构造一个Player变体的实例,相当于java里面的匿名内部类OnClickListener
    val p = object : Player() {
        override fun load() = "anonymous nothing..."
    }
    println(p.load())//anonymous nothing...
}

7.8、伴生对象

如果你想将某个对象的初始化和一个类实例捆绑在一起,可以考虑使用伴生对象,使用
companion修饰符,你可以在一个类定义里声明一个伴生对象,一个类里只能有一个伴生对象。

open class ConfigMap {
    companion object {
        private const val PATH = "xxxx"
        fun load() = File(PATH).readBytes()
    }
}

fun main() {
  	//1,可直接调用伴生对象的方法
  	//2,在创建ConfigMap类实例、或者直接调用伴生对象的方法load时创建,且全局单例。
  	//3,kotlin里没有static静态变量,这种类似于java的static,不同的是这种不调用不会生成
    ConfigMap.load()
}

7.9、嵌套类

如果一个类只对另一个类有用,那么将其嵌入到该类中并使这两个类保持在一起是合乎逻辑的,可以使用嵌套类。

class Player {
    class Equipment(var name: String) {
        fun show() = println("equipment:$name")
    }

    fun battle() {
        Equipment("sharp knife").show()
    }
}

fun main() {
    Player.Equipment("AK47").show()//equipment:AK47
}

7.10、数据类

1,数据类,是专门设计用来存储数据的类
2,数据类提供了toString的个性化实现
3,==符号默认情况下,比较对象就是比较它们的引用值,数据类提供了equals和hash
Code的个性化实现

7.10.1、回顾==、===
class Coordinate(var x: Int, var y: Int) {
    val isInBounds = x > 0 && y > 0
    override fun equals(other: Any?): Boolean {
        print("重写了equals ")
        return super.equals(other)
    }
}

fun main() {
    val data1 = Coordinate(10, 20)
    val data2 = Coordinate(10, 20)
    println(data1)//Coordinate@45ee12a7
    // ==比较的是内容,调用类的equals方法。但由于这里重新定义equals规则,使用Any超类的equals,所以这里仍比较的是引用
    println(data1 == data2)//重写了equals false
    // ===比较的是引用
    println(data1 === data2)//false
}
7.10.2、添加data为数据类
data class Coordinate(var x: Int, var y: Int) {
    val isInBounds = x > 0 && y > 0
}

fun main() {
    val data1 = Coordinate(10, 20)
    val data2 = Coordinate(10, 20)
    println(data1)//Coordinate(x=10, y=20)
    println(data1 == data2)//true
    println(data1 === data2)//false
}

Show kotlin bytecode

image-20220514183214800
7.10.3、copy方法

除了重写Any类的部分函数,提供更好用的默认实现外,数据类还提供了一个函数,它可以用来方便地复制一个对象。假设你想创建一个Students实例,除了name属性,它拥有和另一个现有Student实例完全一样的属性值,如果Student是个数据类,那么复制现有Student实例就很简单了,只要调用copy函数,给想修改的属性传入值参就可以了。

存在坑:copy方法不会执行次构造函数中的代码

data class Student(var name: String, val age: Int) {
    private val hobby = "music"
    val subject: String
    var score = 0

    init {
        println("initializing student...")
        subject = "math"
    }

    // 次构造函数
    constructor(_name: String) : this(_name, 10) {
        score = 10
    }

    override fun toString(): String {
        return "Student(name='$name', age=$age, hobby='$hobby', subject='$subject', score=$score)"
    }
}

fun main() {
    val s = Student("Java")
    val copy = s.copy("Rose")
//    copy.score=10
    println(s)//Student(name='Java', age=10, hobby='music', subject='math', score=10)
    println(copy)//Student(name='Rose', age=10, hobby='music', subject='math', score=0)
}
7.10.4、解构声明

解构声明的后台实现就是声明component1、component2等若干个组件函数,让每个函数负责管理你想返回的一个属性数据,如果你定义一个数据类,它会自动为所有定义在主构造函数的属性添加对应的组件函数。

data class PlayerScore(val experence: Int, val level: Int) {
}

fun main() {
    val (x, y) = PlayerScore(10, 20)
}

如果不是数据类,则需要自己实现解构语法

class PlayerScore(val experence: Int, val level: Int) {
    operator fun component1() = experence
    operator fun component2() = level
}
7.10.5、使用数据类的条件

正是因为上述这些特性,你才倾向于用数据类来表示存储数据的简单对象,对于那些经常需要比较、复制或打印自身内容的类,数据类尤其适合它们。然而,一个类要成为数据类,也要符合一定条件。总结下来,主要有三个方面:
数据类必须有至少带一个参数的主构造函数
数据类主构造函数的参数必须是val或var
数据类不能使用abstract、open、sealed和inner修饰符

7.11、枚举类

普通枚举

enum class Direction {
    EAST,
    WEST,
    SOUTH,
    NORTH,
}

fun main() {
    println(Direction.EAST)//EAST
    println(Direction.EAST is Direction)//true
}

枚举类定义函数

data class Coordinate(var x: Int, var y: Int)

enum class Direction2(private val coordinate: Coordinate) {
    EAST(Coordinate(1, 0)),
    WEST(Coordinate(-1, 0)),
    SOUTH(Coordinate(-1, 0)),
    NORTH(Coordinate(1, 0));

    fun updateCoordinate(playerCoordinate: Coordinate) =
        Coordinate(playerCoordinate.x + coordinate.x, playerCoordinate.y + coordinate.y)
}

fun main() {
    println(Direction2.EAST.updateCoordinate(Coordinate(10, 20)))//Coordinate(x=11, y=20)
}

7.12、密封类

对于更复杂的ADT,你可以使用Kotlin的密封类(sealed class)来实现更复杂的定义,
密封类可以用来定义一个类似于枚举类的ADT,但你可以更灵活地控制某个子类型。密封类可以有若干个子类,要继承密封类,这些子类必须和它定义在同一个文件里。

sealed class LicenseStatus2 {
    object UnQualified : LicenseStatus2()//单例
    object Learning : LicenseStatus2()//单例
    class Qualified(val licenseId: String) : LicenseStatus2()//类
}

class Driver2(var status: LicenseStatus2) {
    fun checkLicense(): String {
        return when (status) {
            is LicenseStatus2.UnQualified -> "没资格"
            is LicenseStatus2.Learning -> "在学"
            is LicenseStatus2.Qualified -> "有资格,驾驶证编号:${(this.status as LicenseStatus2.Qualified).licenseId}"
        }
    }
}

fun main() {
    val status = LicenseStatus2.Qualified("123")
    val driver = Driver2(status)
    println(driver.checkLicense())//有资格,驾驶证编号:123
}

7.13、接口

Kotlin规定所有的接口属性和函数实现都要使用override关键字,接口中定义的函数
公并不需要open关键字修饰,他们默认就是open的。

接口中的属性、函数都需要(在构造方法、方法体中)重写,并且属性需要提供get set方法

interface Movable{
    var maxSpeed:Int
    var wheels:Int
    fun move(movable: Movable):String
}

class Car(_name:String, override var wheels: Int=4):Movable{
    override var maxSpeed: Int
        get() = TODO("Not yet implemented")
        set(value) {}

    override fun move(movable: Movable): String {
        TODO("Not yet implemented")
    }
}

接口中的属性可以提供get默认实现,子类通过super使用父类的

interface Movable{
    val maxSpeed:Int
    get() = (1..500).shuffled().last()
    var wheels:Int
    fun move(movable: Movable):String
}

class Car(_name:String, override var wheels: Int=4):Movable{
    override val maxSpeed: Int
        get() = super.maxSpeed
    override fun move(movable: Movable): String {
        TODO("Not yet implemented")
    }
}

7.14、抽象类

抽象类与java类似

abstract class Gun(val range: Int) {
    // 实现的方法
    protected fun doSomething() {

    }

    // 抽象方法
    abstract fun pullTrigger(): String
}

class AK47(val price: Int) : Gun(range = 500) {
    override fun pullTrigger(): String {
        TODO("Not yet implemented")
    }
}

7.15、泛型

7.15.1、泛型参数
class MagicBox<T>(item: T) {
    private var subject: T = item
}

class Boy(val name: String, val age: Int)

class Dog(val weight: Int)

fun main() {
    val box1 = MagicBox(Boy("Jack", 20))
    val box2 = MagicBox(Dog(20))
}
7.15.2、泛型函数

fetch返回T?泛型数据

class MagicBox<T>(item: T) {
    var available=false
    private var subject: T = item
    fun fetch():T?{
        return subject.takeIf { available }
    }
}

class Boy(val name: String, val age: Int)

fun main() {
    val box1 = MagicBox(Boy("Jack", 20))
    box1.available=true
    box1.fetch()?.run {
        println("you find $name")
    }
}
7.15.3、多泛型参数

示例中,box1调用fetch方法,入参是it,返回Man(it.name, it.age.plus(10))

class MagicBox<T>(item: T) {
    var available = false
    private var subject: T = item
    fun fetch(): T? {
        return subject.takeIf { available }
    }

    fun <R> fetch(subjectModFunction: (T) -> R): R? {
        return subjectModFunction(subject).takeIf { available }
    }
}

class Boy(val name: String, val age: Int)
class Man(val name: String, val age: Int)

fun main() {
    val box1 = MagicBox(Boy("Jack", 20))
    box1.available = true
    box1.fetch()?.run {
        println("you find $name")
    }
    val man = box1.fetch {
        Man(it.name, it.age.plus(10))
    }
    println(man?.age)//30
}
7.15.4、泛型类型约束

示例这里约束MagicBox为Human类型,所以使用Dog类型编译不通过

class MagicBox<T:Human>(item: T) {
    var available = false
    private var subject: T = item
    fun fetch(): T? {
        return subject.takeIf { available }
    }

    fun <R> fetch(subjectModFunction: (T) -> R): R? {
        return subjectModFunction(subject).takeIf { available }
    }
}

open class Human(val age: Int)
class Boy(val name: String, age: Int) : Human(age)
class Man(val name: String, age: Int) : Human(age)
class Dog(val weight: Int)

fun main() {
    val box1 = MagicBox(Boy("Jack", 20))
//    val box2 = MagicBox(Dog("Jack", 20))//编译提示报错
    box1.available = true
    box1.fetch()?.run {
        println("you find $name")
    }
    val man = box1.fetch {
        Man(it.name, it.age.plus(10))
    }
    println(man?.age)//30
}
7.15.5、泛型与varage关键字

varage关键字用于可变参数。

示例中Array用于接收可变参数,表示Human及其子类类型的数组

class MagicBox<T : Human>(vararg item: T) {
    var available = false
    private var subject: Array<out T> = item
    fun fetch(index: Int): T? {
        return subject[index].takeIf { available }
    }

    fun <R> fetch(index: Int, subjectModFunction: (T) -> R): R? {
        return subjectModFunction(subject[index]).takeIf { available }
    }
}

open class Human(val age: Int)
class Boy(val name: String, age: Int) : Human(age)
class Man(val name: String, age: Int) : Human(age)

fun main() {
    val box1 = MagicBox(
        Boy("Jack", 20),
        Boy("Jack1", 21),
        Boy("Jack2", 22),
    )
    box1.available = true
    box1.fetch(1)?.run {
        println("you find $name")//you find Jack1
    }
    val man = box1.fetch(2) {
        Man(it.name, it.age.plus(10))
    }
    println(man?.age)//32
}
7.15.6、in&out

在java中,只有extends继承能实现类型转换,implement接口没法实现。如String实现了CharSequence接口但没法实现类型的互转。

//ArrayList<CharSequence>list=new ArrayList<String>();//报错

kotlin对这一点做了优化,引入in、out关键字。定义

一、in(逆变):如果泛型类只将泛型类型作为函数的入参(输入),那么使用 in,可以称之为消费者类/接口,因为它主要是用来消费(consume)指定的泛型对象。

//int
interface Consumer<in T> {
    fun product(item: T)
}

二、out(协变):如果泛型类只将泛型类型作为函数的返回(输出),那么使用 out,
可以称之为生产类/接口,因为它主要是用来生产(produce)指定的泛型对象。

//out
interface Production<out T> {
    fun product(): T
}

三、不变的情况。既有输入又有输出

//不变
interface ProductionConsumer<T> {
    fun product(): T
    fun product(item: T)
}

示例

//out
interface Production<out T> {
    fun product(): T
}

//int
interface Consumer<in T> {
    fun consumer(item: T)
}

//不变
interface ProductionConsumer<T> {
    fun product(): T
    fun product(item: T)
}

open class Food
open class FastFood : Food()
class Burger : FastFood()

//生产者
//食品商店
class FoodStore : Production<Food> {
    override fun product(): Food {
        println("product food")
        return Food()
    }
}

//快餐商店
class FastFoodStore : Production<FastFood> {
    override fun product(): FastFood {
        println("product FastFood")
        return FastFood()
    }
}

//汉堡商店
class BurgerStore : Production<Burger> {
    override fun product(): Burger {
        println("product Burger")
        return Burger()
    }
}

//消费者
class EveryBody : Consumer<Food> {
    override fun consumer(item: Food) {
        println("Eat Food")
    }
}

class ModernPeople : Consumer<FastFood> {
    override fun consumer(item: FastFood) {
        println("Eat FastFood")
    }
}

class American : Consumer<Burger> {
    override fun consumer(item: Burger) {
        println("Eat Burger")
    }
}

fun main() {
    //赋值
    //实际返回类型还是由后面执行结果决定,相当于多态的概念。这一点在java中不能实现

    //子类泛型对象可以赋值给父类泛型对象,用out
    val production1: Production<Food> = FoodStore()
    val production2: Production<Food> = FastFoodStore()
    val production3: Production<Food> = BurgerStore()

    //父类泛型对象可以赋值给子类泛型对象,用in
    val consumer1: Consumer<Burger> = EveryBody()
    val consumer2: Consumer<Burger> = ModernPeople()
    consumer2.consumer(Burger())//Eat FastFood
    val consumer3: Consumer<Burger> = American()
}

总结

父类泛型对象可以赋值给子类泛型对象,用in。
子类泛型对象可以赋值给父类泛型对象,用out。

image-20220127202810611
7.15.7、reified

有时候,你可能想知道某个泛型参数具体是什么类型,reified关键字能帮你检查泛型参数类型。Kotlin不允许对泛型参数T做类型检查,因为泛型参数类型会被类型擦除,,也就是说,T的类型信息在运行时是不可知的,Java也有这样的规则。

class MagicBox<T : Human>() {
//    fun <T> randomOrBackup(backup: () -> T): T {
//        val items = listOf(
//            Boy("Jack", 20),
//            Man("John", 35)
//        )
//        val random = items.shuffled().first()
//        println(random.javaClass.name)
//        //这里random可以通过反射拿到类型,但是T不能推断,编译报错
//        return if (random is T) {
//            random
//        } else {
//            backup()
//        }
//    }

    inline fun <reified T> randomOrBackup(backup: () -> T): T {
        val items = listOf(
            Boy("Jack", 20),
            Man("John", 35)
        )
        val random = items.shuffled().first()
        println(random)
        return if (random is T) {
            random
        } else {
            backup()
        }
    }
}

open class Human(val age: Int)
class Boy(val name: String, age: Int) : Human(age) {
    override fun toString(): String {
        return "Boy(name='$name',age='$age')"
    }
}

class Man(val name: String, age: Int) : Human(age) {
    override fun toString(): String {
        return "Man(name='$name',age='$age')"
    }
}

fun main() {
    val box1: MagicBox<Human> = MagicBox()
    //由backup函数,推断出T的类型
    val subject = box1.randomOrBackup {
        Boy("Jimmy", 38)
    }
    println(subject)
}
//case 1
//Boy(name='Jack',age='20')
//Boy(name='Jack',age='20')
//case 2
//Man(name='John',age='35')
//Boy(name='Jimmy',age='38')

7.16、扩展函数

扩展可以在不直接修改主类增加类功能,扩展可以用于自定义类,也可以用于比如List、String,,以及Kotlin标准库里的其他类。和继承相似,扩展也能共享类行为,在你无法接触某个类定义,或者某个类没有使用open修饰符,导致你无法继承它时,扩展就是增加类功能的最好选择。

7.16.1、定义扩展函数

定义扩展函数和定义一般函数差不多,但有一点不一样,除了函数定义,你还’需要指定接受功能扩展的接收者类型。

//给字符串增加若干个感叹号
fun String.addExt(amount: Int = 1) = this + "!".repeat(amount)
//Any超类添加打印
fun Any.easyprint() = println(this)

fun main() {
    println("abc".addExt(2))//abc!!

    "asdf".easyprint()//asdf
}

扩展函数的作用范围是整个工程,如果设置是private就只在当前文件有效

private fun Any.easyprint() = println(this)
7.16.2、泛型扩展函数

新的泛型扩展函数不仅可以支持任何类型的接收者,还保留了接收者的类型信息,使用泛型类型后,扩展函数能够支持更多类型的接收者,适用范围更广了。

比如我们需要在addExt方法前后打印,那么这样就不行

image-20220127202810611

这里使用fun T.easyprint(): T构造泛型扩展函数

//给字符串增加若干个感叹号
fun String.addExt(amount: Int = 1) = this + "!".repeat(amount)

//Any超类添加打印
//fun Any.easyprint(): Any = {
//    println(this)
//    this
//}
fun <T> T.easyprint(): T {
    println(this)
    return this
}

fun main() {
    "abc".easyprint().addExt(2).easyprint()
}
//abc
//abc!!

同样分析:在let的源码中T.let作为调用者,所以下面表达式字符串“abc”就是T,R为lambda表达式的结果,所以这里是int

@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}
val a = "abc".let {
    30
}

7.17、扩展属性

除了给类添加功能扩展函数外,你还可以给类定义扩展属性,给String类添加一个扩展,这个扩展属性可以统计字符串里有多少个元音字母。

val String.numVowels
    get() = count { "aeiou".contains(it) }

fun <T> T.easyPrint(): T {
    println(this)
    return this
}

fun main() {
    "The people's Republic of China".numVowels.easyPrint()//10
}

7.18、可空类扩展

你也可以定义扩展函数用于可空类型,在可空类型上定义扩展函数,你就可以直接在扩展函数体内解决可能出现的空值问题。

fun String?.printWidthDefault(default: String) = println(this ?: default)
fun main() {
    var nullableString: String? = null;
    nullableString.printWidthDefault("abc")//abc
    nullableString = "efg"
    nullableString.printWidthDefault("abc")//efg
}

7.19、infix关键字

infix关键字适用于有单个参数的扩展和类函数,可以让你以更简洁的语法调用函数,如果一个函数定义使用了ifix关键字,那么调用它时,接收者和函数之间的点操作以及参数的一对括号都可以不要。

infix fun String?.printWidthDefault(default: String) = println(this ?: default)
fun main() {
    var nullableString: String? = null;
    nullableString.printWidthDefault("abc")//abc
    nullableString printWidthDefault "abc"//abc
}

这里map的声明也是使用infix将to转换为Pair

val map = mapOf("Jack" to 16, "Jason" to 17, "Jack" to 18)
val map2 = mapOf(Pair("jimmy", 20), Pair("Jacky", 20))
image-20220127202810611

7.20、定义扩展文件

扩展函数需要在多个文件里面使用,可以将它定义在单独的文件,然后import

package com.zhanglei.extension

fun <T> Iterable<T>.randomTake(): T = this.shuffled().first()

定义randomTake扩展函数,然后import就可以简写使用

import com.zhanglei.extension.randomTake

fun main() {
    val list = listOf("zhangsan", "lisi", "wangwu")
    val set = setOf("zhangsan", "lisi", "wangwu")

    list.shuffled().first()
    //等价于
    list.randomTake()
}

取别名。使用randomizer

import com.zhanglei.extension.randomTake as randomizer

fun main() {
    val list = listOf("zhangsan", "lisi", "wangwu")
    val set = setOf("zhangsan", "lisi", "wangwu")

    list.shuffled().first()
    //等价于
    list.randomizer()
}

八、函数式编程

函数类别

一个函数式应用通常由三大类函数构成:变换transform、过滤filter、合并combine。每类函数都针对集合数据类型设计,目标是产生一个最终结果。函数式编程用到的函数生来都是可组合的,也就是说,你可以组合多个简单函数来构建复杂的计算行为。

8.1、变换

变换是函数式编程的第一大类函数,变换函数会遍历集合内容,用一个以值参形式传入的变换器函数,变换每一个元素,然后返回包含已修改元素的集合给链上的其他函数。最常用的两个变换函数是map和flatMap。

8.1.1、map变换

map变换函数会遍历接收者集合,让变换器函数作用于集合里的各个元素,返回结果是包含已修改元素的集合,会作为链上下一个函数的输入。

val person = listOf("zhangsan", "李四", "wangwu")
val person2 = person
    .map { item -> "word $item" }
    .map { item -> "hello $item" }
println(person)
println(person2)
//[zhangsan, 李四, wangwu]
//[hello word zhangsan, hello word 李四, hello word wangwu]

可以看到,原始集合没有被修改,map变换函数和你定义的变换器函数做完事情后,返回的是一个新集合,这样,变量就不用变来变去了。
事实上,函数式编程范式支持的设计理念就是不可变数据的副本在链上的函数间传递。

map返回的集合中的元素个数和输入集合必须一样,不过,返回的新集合里的元素可以是不同类型的。

val lengths=person2.map { it.length }
println(lengths)//[19, 13, 17]
8.1.2、flatMap

flatMap函数操作一个集合的集合,将其中多个集合中的元素合并后返回一个包含所有元素的单一集合。

val result = listOf(listOf(1, 2, 3), listOf(4, 5, 6)).flatMap { it }
println(result)//[1, 2, 3, 4, 5, 6]

flat铺平的意思

8.2、过滤

过滤是函数式编程的第二大类函数,过滤函数接受一个oredicate函数,用它按给定条件检查接收者集合里的元素并给出true或false的判定。如果predicate函数返回true,受检元素就会添加到过滤函数返回的新集合里。如果predicate函数返回false,那么受检元素就被移出新集合。

8.2.1、filter

filter过滤函数接受一个predicate函数,在flatMap遍历它的输入集合中的所有元素时,filteri函数会让oredicate函数按过滤条件,将符合条件的元素都放入它返回的新集合里。最后,flatMap会把变换器函数返回的子集合合并在一个新集合里。

val result = listOf("Jack", "Tom", "Jason")
    .filter { it.contains("J") }
println(result)//[Jack, Jason]

混合使用示例:找素数算法

除了1和本身外不能被其他数整除的数为素数

val numbers = listOf(7, 4, 8, 4, 3, 22, 18, 11)
val result = numbers.filter { number ->
    (2 until number).map { number % it }
        .none { it == 0 }
}
println(result)//[7, 3, 11]

8.3、合并

合并是函数式编程的第三大类函数,合并函数能将不同的集合合并成一个新集合,这和接收者是包含集合的集合的flatMap函数不同。

8.3.1、zip

zp合并函数来合并两个集合,返回一个包含键值对的新集合。

val names = listOf("zhangsan", "lisi", "wangwu")
val ages = listOf(12, 13, 14)
println(names.zip(ages))//[(zhangsan, 12), (lisi, 13), (wangwu, 14)]
println(names.zip(ages).toMap())//{zhangsan=12, lisi=13, wangwu=14}
8.3.2、fold

另一个可以用来合并值的合并类函数是fod,这个合并函数接受一个初始累加器值,随后会根据匿名函数的结果更新。

val ages = listOf(12, 13, 14).fold(0) { accumulator: Int, element: Int ->
    println("element:$element")
    accumulator + (element + 3)
}
println("ages:$ages")
//element:12
//element:13
//element:14
//ages:48

8.4、序列

Kotlin有两类集合

1、及早集合(eager collection)这些集合的任何一个实例在创建后,它要包含的元素都会被加入并允许你访问。对应及早集合,包括List、Set、Map集合类型

2、惰性集合(lazy collection)类似于类的惰性初始化,惰性集合类型的性能表现优异,尤其是用于包含大量元素的集合时,因为集合元素是按需产生的。

Kotlin有个内置惰性集合类型叫序列(Sequence),序列不会索引排序它的内容,也不记录元素数目,事实上,在使用一个序列时,序列里的值可能有无限多,因为某个数据源能产生无限多个元素。

  • generateSequence

针对某个序列,你可能会定义一个只要序列有新值产生就被调用一下的函数,这样的函数叫迭代器函数,要定义一个序列和它的迭代器,你可以使用Koti的序列构造函数generateSequence,generateSequencei函数接受一个初始种子值作为序列的起步值,在用generateSequence定义的序列上调用一个函数时,generateSequence函数会调用你指定的迭代器函数,决定下一个要产生的值。

fun Int.isPrime(): Boolean {
    (2 until this).map {
        if (this % it == 0) {
            return false
        }
    }
    return true
}

//产生头1000个素数
//假设0-5000之内,可以找到1000个素数
val toList = (1..5000).toList().filter { it.isPrime() }.take(1000)
println(toList.size)//670

val oneTousandPrimes = generateSequence(2) { value ->
    value + 1
}.filter { it.isPrime() }.take(1000)
println(oneTousandPrimes.toList().size) // 1000

九、java与kotlin互调

Java世界里所有对象都可能是nul,当一个Kotlin函数返回String类型值,你不能想当然地认为它的返回值就能符合Kotlin关于空值的规定。

9.1、kotlin调用java

public class JavaTest {
    public int num = 123;

    public String generateString() {
        return "String";
    }

    public String generateStringOrNull() {
        return null;
    }
}
fun main() {
    val javaTest = JavaTest()
    println(javaTest.generateString())//String

    val stringOrNull = javaTest.generateStringOrNull()
    println(stringOrNull)//null

    //代码运行时,所有的映射类型都会重新映射会对应的java类型
    println(javaTest.num.javaClass)//int
}
image-20220127202810611

单kotlin项目,注意这里目录结构java、kotlin分开,否则会找不到javaclass

kotlin访问java属性会去掉get、set

public class JavaTest {
    private int num = 123;

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }
}
val javaTest = JavaTest()
println(javaTest.num.javaClass)

9.2、java调用kotlin

对于kotlin文件级别的函数直接文件名调用

public class JavaTest {
    public static void main(String[] args) {
        String str = KotlinTestKt.kotlinFun();
        System.out.println(str);//from kotlin
    }
}
fun main() {

}

fun kotlinFun(): String {
    return "from kotlin"
}
@JvmName取别名

给kotlin文件取别名@file:JvmName(“KotlinTest”)

@file:JvmName("KotlinTest")

fun kotlinFun(): String {
    return "from kotlin"
}
KotlinTest.kotlinFun();
@JvmField暴露字段

java不能直接访问kotlin字段,必须调用相应的get、set方法。然而你可以给Kotlin属性添加@JvmField注解,暴露它的支持字段给Java调用者,从而避免使用getter方法

class KotlinTest {
    @JvmField
    val str = "asdf"
}
public class JavaTest {
    public static void main(String[] args) {
        (new KotlinTest()).str
    }
}
@JvmOverloads强制重载

@JvmOverloads注解协助产生Kotlin函数的重载版本。设计一个可能会暴露给Java用户
使用的API时,记得使用@JvmOverloads注解,这样,无论你是Kotlin开发者还是Java
开发者,都会对这个API的可靠性感到满意。

fun executeFun(name: String = "name", address: String = "address") {
}
image-20220127202810611

如果要实现这种重载,只需要在kotlin的函数上加@JvmOverloads

@JvmOverloads
fun executeFun(name: String = "name", address: String = "address") {
}
@JvmStatic直接调用伴生对象里的函数

@JvmField注解还能用来以静态方式提供伴生对象里定义的值

@JvmStatic注解的作用类似于@JvmField,允许你直接调用伴生对象里的函数

class KotlinTest {
    companion object {
        val name = "adfd"
        fun getMyString() = println("getMyString")
    }
}
KotlinTest.Companion.getName();
KotlinTest.Companion.getMyString();
class KotlinTest {
    companion object {
        @JvmField
        val name = "adfd"

        @JvmStatic
        fun getMyString() = println("getMyString")
    }
}
String name = KotlinTest.name;
KotlinTest.getMyString();
@Throws

抛出一个需要检查的指定异常,Java和Kotlin有关异常检查的差异让@Throws注解给解决掉了,在编写供Java开发者调用的Kotlin APl时,要考虑使用@Throws注解,这样,用户就知道怎么正确处理任何异常了。

import java.io.IOException

fun throwKotlinException() {
    throw IOException()
}
image-20220127202810611

在java中没办法catch到指定的异常,show kotlinbytecode。发现是转成了Throwable类型了

public final class KotlinTestKt {
   public static final void throwKotlinException() {
      throw (Throwable)(new IOException());
   }
}

可以通过加上注解解决@Throws(IOException::class)

import java.io.IOException

@Throws(IOException::class)
fun throwKotlinException() {
    throw IOException()
}

反过来kotlin接受java的异常则没有这种问题

public class JavaTest {
    public static void main(String[] args) {
        try {
            (new JavaTest()).exceptionHand();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void exceptionHand() throws IOException {
        throw new IOException();
    }
}
import java.lang.Exception

fun main() {
    val javaTest = JavaTest()
    try {
        javaTest.exceptionHand()
    } catch (e: Exception) {
        println(e)//java.io.IOException
    }
}
函数类型操作

函数类型和匿名函数能提供高效的语法用于组件间的交互,是Kot编程语言里比较新颖的特性。他们简洁的语法因->操作符而实现,但Java8之前的DK版本并并不支持lambda表达式。在Java里Kotlin函数类型使用FunctionN这样的名字的接口来表示的,FunctionN中的N代表值参数目。这样的Function:接口由23个,从Function0到Function22,每一个FunctionN都包含一个invoke函数,专用于调用函数类型函数,所以,任何时候需要调一个函数类型,都用它调用invoke。

val translator: (String) -> Unit = { utterance: String ->
    println(utterance.toLowerCase().capitalize())
}
public class JavaTest {
    public static void main(String[] args) {
        Function1<String, Unit> translator = KotlinTestKt.getTranslator();
        translator.invoke("LDSFLJDF");//Ldsfljdf
    }
}

十、android开发使用kotlin

10.1、setOnClickListener

androidx.core:core-ktx插件给我们做了简写

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val textView = findViewById<TextView>(R.id.textView)
        textView.text = "asdf"

        textView.setOnClickListener(View.OnClickListener {

        })
      	//简写
        textView.setOnClickListener {

        }
    }
}

10.2、省略findViewById

除了databinding方式,kotlin也提供了插件

apply plugin: 'kotlin-android-extensions'

Gradle中先导入’kotlin-android-extensions’插件,然后使用的页面上

import kotlinx.android.synthetic.main.activity_main.*即可

import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

//        val textView = findViewById<TextView>(R.id.textView)
        textView.text = "asdf"

        textView.setOnClickListener(View.OnClickListener {

        })
        textView.setOnClickListener {

        }
    }
}
  • 2
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流星雨在线

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值