kotlin知识点大全

718 阅读12分钟

第一章、背景

https://www.kotlincn.net/

第二章、环境搭建

AS中,Java项目增加Kotlin的支持

new - Activity - Empty Activity - 选择kotlin,然后就增加了kotlin的环境,AS会为我们做下面的2件事情:

1、然后在项目的build.gradle中增加 kotlin的插件

classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

2、然后在app的build.gradle中增加插件

id 'kotlin-android'

Gradle目录

(windows电脑)C:\Users\93768\.gradle\wrapper\dists

第三章、内置类型

3.1、基本类型

定义

**
**

区别

1、Java中有基本类型和包装类型,Kotlin里面只有包装类型。 

2、Java中表示一个长整形,后面增加 l 或者 L,kotlIn中只能有L(大写)。 

3、Java中整形赋值给Long类型是可以的,但是在kotlin中会出现编译错误。

        val e:Int = 10;
        val f:Long = e;//编译错误,Google认为跨类型赋值是不被允许的
        val g:Long = e.toLong() //采用这样的形式是可以的

4、和Java不同,kotlin有无符号类型

5、字符串输入形式不同

        val str = "cdx"
        Log.e("test", "你好$str")

6、引入了 ======== 比较的是 引用值== 比较的是

       val str1 = "Hello"
        val str2 = String("Hello".toCharArray())
        // 比较的是引用值
        Log.e("cdx",(str1 === str2).toString()) // false
        // 比较的是值
        Log.e("cdx",(str1 == str2).toString()) // 

 7、按照写的方式输出

        val str =
            """
            轻轻的我走了
            正如我轻轻的来
            我轻轻的招手
        """.trimIndent()
        Log.e("cdx", str)

3.2、数组

数组的类型

**
**

注意:

Int有整形和整形装箱类型,字符串只有字符串装箱。

例子1:创建一个Int数组的4种方式

        // 创建一个Int类型的数组
        // 第一种方式
        var arr1 = IntArray(5)
        // 第二种方式
        var arr2 = IntArray(5) { it }
        // 第三种方式
        var arr3 = intArrayOf(1, 2, 3, 4, 5)
        // 第四种方式
        var arr4 = Array<Int>(5) { it }

例子2:创建一个String类型的数组

        val arr = arrayOf("Hello", "world")
        Log.e(tag, arr.joinToString())

3.3、区间

闭区间的使用

        // 闭区间例子
        var arr1: IntRange = 1..10
        Log.e("cdx", arr1.joinToString()) // 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
        var arr2: CharRange = 'a'..'z'
        Log.e("cdx", arr2.joinToString())
        var arr3: LongRange = 1L..100L
        Log.e("cdx", arr3.joinToString())

开区间的使用

        // 开区间(左必有开区间)
        var arr4 = 1 until 10 //[1,10)
        Log.e("cdx", arr4.joinToString()) //1, 2, 3, 4, 5, 6, 7, 8, 9

倒序区间(Progression:进展、类型)

        // 倒序区间
        var arr5: IntProgression = 10 downTo 1
        Log.e("cdx", arr5.joinToString()) //10, 9, 8, 7, 6, 5, 4, 3, 2, 1
        var arr6 = 'z' downTo 'a'
        Log.e("cdx", arr6.joinToString())

区间的步长

        // 区间的步长
        var arr7 = 1..10 step 2
        Log.e("cdx", arr7.joinToString()) // 1, 3, 5, 7, 9

1..10 和 1..10 step 2 的类型分别是什么?

**
**

3.4、集合框架

集合框架有几种类型

从可变性上分为:可变集合 + 不可变集合  

从集合的性质上分为:List + Map + Set

区别:

Java的数据结构都是可变的,但是kotlin的数据结构是区分不可变或者可变的。

创建不可变List的2中方式

构造器 +  方法 的创建方法

        // 不可变List使用
        var list = List<String>(5) { "" }
        //list[0] = "1" // 出现编译错误

        var list2 = listOf<String>("Hello", "world")
        //list2[0] = "1"

可变List的3种创建方式

        // 可变List
        // 第一种创建方式
        val list1 = MutableList<Int>(5) { it }
        // 第二种创建方式
        var list2 = mutableListOf<Int>(1)
        // 第三种创建的方式
        var list3 = ArrayList<Int>()

        list1[3] = 4
        Log.e(tag, list1.joinToString("*"))
        // 超出边界将阻碍下面的语句的执行
        list1[8] = 8
        list1.set(2, 5)
        Log.e(tag, list1.joinToString("*"))

ArrayList使用

	// 这个默认是:MutableList
        val list3 = ArrayList<String>()
        list3.add("a")
        list3.add("b")
        list3 += "c" //注意 += 就是add的操作
        list3.remove("b") //
        list3 -= "a" // -= 就是remove操作
        Log.e("cdx", list3.joinToString()) //c

不可变Map创建的2种方式

to + Pair 方式

        // 不可变Map
        val map: Map<String, Any> = mapOf("name" to "xiaohong", "age" to 20)
        val map = mapOf<String, String>(Pair("name", "张三"), Pair("age", "10"))

可变Map

        // 可变Map
        val map2: MutableMap<String, Any> = mutableMapOf("name" to "xiaohong", "age" to 20)
        map2["name"] = 3
        map2.put("weight", 160)
        //遍历Map的key-value对,entris元素返回key-value对组成的Set
        for (en in map2.entries) {
            Log.e("cdx", "${en.key}->${en.value}")
        }

Pair(对)

	val pair = "Hello" to "kotlin"
        val pair2 = Pair("Hello", "kotlin")
        val first = pair2.first
        val second = pair2.second
        // 解构赋值
        val (x, y) = pair
        Log.e(
            "cdx",
            "first:$first,second:$second,x:$x,y:$y"
        ) //  first:Hello,second:kotlin,x:Hello,y:kotlin

Triple(三倍的)

和Pair使用类似

3.5、函数

定义

类似于java中的方法。

一个函数没有返回值,返回类型是什么?

Unit类似于Java的void,可以省略。

    private fun test4(): Unit {

    }

函数VS方法

test方法外面有一个Test(类),那么test就是一个方法。

class Test {
    fun test(): String {
        return ""
    }
}

函数的引用

函数的引用类似于C语言中的指针,可用于函数传递

利用**::**来实现获取函数的引用。

方法函数的引用 和 引用的类型是什么

首先定义方法

    private fun test(str: String): Unit {

    }

然后获取方法的引用

 	// 定义fun1来接受方法的引用, (String) -> Unit表示函数的类型。
        // 其中(String) -> Unit类型可以省略
        val fun1: (String) -> Unit = ::test

类中方法的引用

package com.example.kotlindemo

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

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

        // 接受一个方法的引用,这两个是等价的,表示括号里和哪些有关系
        // 第一个表示有Receiver,第二个形式表示和Test有关系
        val fun2: Test.() -> String = Test::test
        val fun3: (Test) -> String = Test::test

        // 对象取函数的引用,类型不一样了
        val test = Test()
        val fun4: () -> String = test::test

	// 函数里面有函数,并且调用
        test1(::test)
        test1(fun1)
    }

    /**
     * 这个test1函数的类型是一个函数,函数的参数是String类型,返回时是Unit类型,也就是void类型
     */
    private fun test1(p: (String) -> Unit) {
        p("Hello")
    }

    private fun test(str: String): Unit {

    }

}

class Test {
    // 这个就是方法
    fun test(): String {
        return ""
    }
}

对象方法的引用

        // 对象取函数的引用,类型不一样了
        val test = Test()
        val fun1: () -> Unit = test::test

变长参数

vararg 用来修饰参数,就变成了变长参数,类似于数组。

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

        Log.e("cdx",test("1", "2"))
    }

    // 边长参数:类似于数组
    // 使用关键字 vararg
    fun test(vararg params: String): String {
        return params.size.toString();
    }

}

多返回值接受的两种方式(通常方式、解构赋值)

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

        // 第一种接受方式
        val test = test()
        // 第二种接受方式:结构赋值
        val (a,b,c) = test()
        Log.e("cdx",test.toString())
    }

    fun test(): Triple<Int, Long, Double> {
        return Triple(1, 2L, 3.0)
    }
}

默认参数

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

        test(1)
    }

    // 默认参数,建议放在最后,也可以放在前面
    fun test(x: Int, y: Int = 0, z: String = "") {
        Log.e("cdx", "x:$x y:$y z:$z")
    }
}

默认参数建议放在哪

默认参数,建议放在最后,也可以放在前面

具名参数

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

        // 具名参数:在传递的时候将键值对都放入这个方法中
        // 使用场景:当函数的默认值不是最后的参数时,可以使用具名参数
        test(y=1)
    }

    // 默认参数,必须放在最后
    fun test(x: Int = 1, y: Int, z: String = "") {
        Log.e("cdx", "x:$x y:$y z:$z")
    }
}

第四章 类型初步

4.1、类和接口

类的定义

1、默认是public 

2、如果类中没有内容,可以将{ } 省略 

3、类中定义属性,必须要初始化。

 4、类中定义构造函数,使用constructor关键字。 

5、构造器分为主构造器和副构造器,要求其它所有的构造器都必须调用它。

构造函数的几种形式

只有主构造器、只有副构造器、主构造器 + 副构造器、主构造器省略constructor、主构造器简化

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

        val test5 = Test5(1)


    }
}

// 只有副构造器
class Test {
    var x: Int = 0

    constructor(x: Int) {
        this.x = x
    }
}

// 包含主构造器和副构造器
class Test2 constructor(x: Int) {
    var x: Int = x
    var y: Int = 0

    constructor(x: Int, y: Int) {
        this.x = x
        this.y = y
    }
}

// 只有主构造器
class Test3 constructor(x: Int) {
    // 赋值
    var x: Int = x
}

// 将主构造器进行省略
class Test4(x: Int) {
    // 赋值
    var x: Int = x
}

// 继续简化:如果在构造器中增加了var/val,那么相当于上面的构造函数
class Test5(var x: Int) {
}

类的初始化

不需要new关键字

接口定义

interface SimpleInter {
    fun test()
}

接口的实现

// 使用:表示实现接口
class Test : SimpleInter {
    override fun test() {
    }
}

interface SimpleInter {
    fun test()
}

接口中增加属性并且实现

class MainActivity2 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)
        val test = Test(0)
        test.param = 4
        Log.e("cdx", test.param.toString())
    }
}

// 接口中定义了参数,这个参数不需要初始化
interface TestInterface {
    var param: Int
    fun test()
}

// 通过构造函数传递进来一个参数
class Test(var x: Int) : TestInterface {

    // 必须要重写这个参数
    override var param: Int
        // 获取参数
        get() {
            return x
        }
        // 设置参数
        set(value) {
            this.x = value
        }

    override fun test() {
    }
    
}

抽象类的定义

abstract class AbsTest {
    // 表示抽象方法
    abstract fun test1()

    // 必须加上open方法,才能告诉编译器,可以被复写
    open fun test2() {}

    // 默认是不可复写
    fun test3() {
    }
}

抽闲类的实现

// 接口继承:必须要加上() 这个和接口的实现不一样,接口是不需要加()
class Test : AbsTest() {
    override fun test1() {
    }

    override fun test2() {
        super.test2()
    }
}

继承一个普通的类要求

继承一个普通的类A,这个A类必须要是open修饰的。

一个类的方法不能被重写

一个类的方法不能被复写,这个方法增加final。

// 要想能够继承一个类,这个类必须是open的
class Test2 : Test() {
    // 如果复写一个final修饰的方法,此时将会出现编译错误
//    override fun test2() {
//        super.test2()
//    }
}

// 接口继承:必须要加上() 这个和接口的实现不一样,接口是不需要加()
open class Test : AbsTest() {

    override fun test1() {
    }

    final override fun test2() {
        super.test2()
    }
}

abstract class AbsTest {
    // 表示抽象方法
    abstract fun test1()

    // 必须加上open方法,才能告诉编译器,可以被复写
    open fun test2() {}

    // 默认是不可复写
    fun test3() {
    }

}

field使用

定义

在get或者set方法中,表示这个属性。

field使用

class MainActivity2 : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)
        val person = Person(25, "张三")
        person.age = 18
        person.name = "小红"
        Log.e("cdx", person.toString())
    }
}

class Person(age: Int, name: String) {
    var age: Int = age
        get() {
            // field就是指向age属性
            return field
        }
        set(value) {
            field = value
        }
    var name: String = name

    override fun toString(): String {
        return "$age,$name"
    }
}

4.2、扩展方法

定义

不改变类的源码的情况下,为类增加自定义的方法。

如何实现

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

        Log.e("cdx", "abc".times(4))
    }

    fun String.times(times: Int): String {
        var builder = StringBuilder()
        for (i in 0 until times) {
            // 这里的this只得调用者本身
            builder.append(this)
        }
        return builder.toString()
    }
    
}

4.3、空类型安全

空类型访问属性

	var str: String? = null
        // 使用?.来安全访问
		val length = str?.length;//null
        Log.e("cdx", length.toString())//null

强制转换为不可空类型

通过两个!!将可空类型变为不可空类型

        // 表示是一个空类型
        var str: String? = "Hello"
        // 通过两个!!强转化成不为空类型
        val str1 = str!!;

        Log.e("cdx", str1.length.toString())

elvis运算符

        // 表示是一个空类型
        var str: String? = null

        // elvis运算符,如果str?.length 返回null的话,那么此时返回0
        val length = str?.length ?: 0
        Log.e("cdx", length.toString()) //0

空类型的继承关系

小范围(String)的可以给大范围(String?) 赋值,但是大范围的不能给小范围赋值。

        // 不为空
        var x: String = "a"
        // 可以为空(范围更大)
        var y: String? = "b"

        // 大范围的赋值给小范围,出现编译错误
        //x = y
        // 小范围的赋值给大范围,没有问题
        y = x

平台类型

**定义
**

1、在java中声明的类型,在kotlin里面会被称为平台类型

2、这种类型的空检查会放宽

例子

首先定义一个person类,这个是Java的类。

package com.example.kotlindemo;

public class Person {

    public String name;

    public String getTitle(){
        return null;
    }
}

title的类型居然是String!

        var person = Person()
        // 去使用title的时候,本质上使用的是Person的getTitle属性
        val title = person.title
        // 编译器不知道title是否是可空类型,所以要进行非空判断
        var length = title?.length

4.4、智能类型转换

kotlin的类型转换

kotlin不需要强制转换为子类,就可以调用子类的属性。(这个和Java不太一样)

package com.example.kotlindemo

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

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

	// 父类的引用指向子类的对象
        var person = Student();
        Log.e("cdx", "xxx" + person.name)
        if (person is Student) {
            // 不需要进行类型转换,自动调用子类的属性,下面的写法是精简的写法
            Log.e("cdx", (person as Student).name)
            Log.e("cdx", person.name)
        }
    }
}

interface Person {
    fun say()
}

class Student : Person {
    val name: String = "xh"
    override fun say() {

    }
}

作用范围

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

        // 作用范围
        var value: String? = null
        // 这个是不行的,因为这个是可空的类型
        // Log.e("cdx", value.length.toString());
        if (value != null) {
            // 在括号里面,由于有非空判断,所以编译器很智能的将value的类型变为了String类型,非空的类型。
            Log.e("cdx", value.length.toString());
        }
        // 在这个地方,value的类型又变为了String可为空的类型
        Log.e("cdx", value?.length.toString());
    }

}

不支持智能转换的情况

class MainActivity2 : AppCompatActivity() {

    var tag: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        // 这个地方理论上有非空判断,为什么不能有作用范围的概念呢?
        // 因为tag是一个全局的字段,虽然判断不为空,
但是在判断为不为空的时候,
其它的线程可能将他的值修改为空,所以智能转换就不生效了
        if (tag != null) {
            // Log.e("cdx",tag.length.toString())
            Log.e("cdx", tag?.length.toString())
        }
    }

}

类型的安全转换

**as ?
**

安全转换:

如果是person是Student类型,那么就转换

如果不是Student类型,那么返回null

       // 父类的引用指向子类的对象
        var person = Student();
        // 安全转换:
        //   如果是person是Student类型,那么就转换
        //   如果不是Student类型,那么返回null
        Log.e("cdx", (person as? Student)?.name.toString())

建议:

1、尽量使用val来申明不可变的引用,让程序的含义更加清晰确定。

5章、表达式

5.1、只读变量和变量和常量

变量表达

var 来表示变量

只读变量

val表示只读变量

只读变量VS常量

常量的意思是:永远不变。

但是请看下面的例子,Test中的b,它会随着Math的值不一样,获取到的b的值是不一样的,它的值是变化的,所以称为只读变量

class Test {
    val b: Int
        get() {
            return (Math.random() * 1000).toInt()
        }
}

常量要求和表示

1、只能修饰基本类型 

2、只能定义在全局范围 

3、必须立即用字面量初始化

val b = 3

5.2、分支表达式

if - else表达式(和Java不同、三目运算符)

注意:if-else表达式是有返回值的。

        var a: Int = 4
        var c = if (a == 3) 4 else 5
        Log.e("cdx", "c:$c")

when 表达式写法

**
**

when表达式 - 条件转移到分支、简写写法

条件转移到分支上的写法

        val a: Any = 4
        val c: Any
        when {
            a is String -> c = a.length
            a == 3 -> c = 3
            else -> c = 5
        }

简化写法

        val a: Any = 4
        val c = when {
            a is String -> a.length
            a == 3 -> 3
            else -> 5
        }

try - catch 表达式

和Java不同的地方:try - catch表达式可以有返回值。

        var a = 1
        var b = 0
        val c = try {
            a / b
        } catch (e: Exception) {
            0
        }

5.3、运算符与中缀表达式

运算符网站

https://kotlinlang.org/docs/operator-overloading.html

== 运算符 VS equal

        val str1 = "Hello"
        val str2 = "World"
        // 下面的两种方式是完全等价的
        val result1 = (str1 == str2)
        var result2 = str1.equals(str2)

+ 运算符 VS plus

        val str1 = "a"
        val str2 = "b"
        // 下面的两种方式是完全等价的
        val result1 = (str1 + str2)
        var result2 = str1.plus(str2)

        Log.e("cdx", result1.toString())
        Log.e("cdx", result2.toString())

in 运算符 VS contains

        var list = listOf(1, 2, 3, 4)
        var result = 2 in list
        Log.e("cdx", result.toString())
        Log.e("cdx", list.contains(2).toString())

[] 运算符 VS get

        val map = mapOf("name" to "张三", "age" to 20)
        // [] 和 get 的方式完全一样,编译器推荐使用第二种方式
        Log.e("cdx", map.get("name").toString())
        Log.e("cdx", map["name"].toString())

> VS compareTo

        var result1 = 2 > 3
        var result2 = 2.compareTo(3) > 0
        Log.e("cdx", result1.toString())
        Log.e("cdx", result2.toString())

() 与 invoke

表示 执行。

       // 定义匿名函数,用变量去接受这个函数
        var func = fun() {
            Log.e("cdx", "方法执行了")
        }

        // 第一种调用函数方法
        func()

        // 第二种调用函数方法
        func.invoke()

自定义运算符

第一步:定义运算符,相当于给Complex这个哥么增加了2个标签(比如幽默),比如 + 这个标签,那么两个Complex哥么通过标签就可以相互的认识。

plus和运算符+ 是对应的关系。

package com.example.kotlindemo

class Complex(var real: Double, var image: Double) {
    override fun toString(): String {
        return "$real + $image"
    }
}

// 自定义操作符
// 相当于给Complex增加了plus操作符的功能,相当于增加了 + 的功能
operator fun Complex.plus(other: Complex): Complex {
    return Complex(this.real + other.real, this.image + other.image)
}

// 自定义减的运算符
// 给Complex增加了减的运算符,增加了 - 的功能
operator fun Complex.minus(other: Complex): Complex {
    return Complex(this.real - other.real, this.image - other.image)
}

第二步:然后在页面里面去调用

class MainActivity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        var c1 = Complex(1.0, 2.0)
        var c2 = Complex(3.0, 2.0)

		// 
        var result1 = c1 + c2
        Log.e("cdx", result1.toString())
        var result2 = c1 - c2
        Log.e("cdx", result2.toString())

    }
}

中缀表达式-infix的介绍

Kotlin允许在不使用括号点号的情况下调用函数,那么这种函数被称为 infix函数。

中缀表达式要求和使用

1、只有一个参数。

2、增加infix参数,表示可以省略括号和点号。

class MainActivity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        2.to(3)
        2 to 3

    }

    // infix:中缀表达式的的标识
    // A,B 都是泛型
    infix fun <A, B> A.to(b: B): Pair<A, B> {
        return Pair(this, b)
    }
}

5.4、Lambda表达式

匿名函数的传递

        // func:是变量名
        // 匿名函数赋值给了一个变量
        var func = fun() {
            Log.e("cdx", "test")
        }

匿名函数的调用

       // func:是变量名
        // 匿名函数赋值给了一个变量
        var func = fun() {
            Log.e("cdx", "test")
        }

        // 通过()运算符去执行
        func()
        // 通过invoke()去执行
        func.invoke()

Lambda表达式的类型

Lambda表达式就是匿名函数的简化。

        // 没有参数的Lambda表达式
        // 注意func1的类型 () -> Int
        var func1: () -> Int = {
            Log.e("cdx", "test")
        }

        // 有参数的Lambda表达式
        // 注意func2的类型 (Int, Int) -> Int
        var func2: (Int, Int) -> Int = { p1: Int, p2: Int ->
            Log.e("cdx", "test,$p1 $p2")
        }

Lambda表达式返回值

是由Lambda中的最后一行的语句决定。

5.5、为Person实现equals 和hashCode

package com.example.kotlindemo

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

class MainActivity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        val hashSet = HashSet<Person>()
        hashSet.add(Person("张三", 15))
        hashSet.add(Person("张三", 15))

        Log.e("cdx", hashSet.size.toString())

    }
}

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

    override fun equals(other: Any?): Boolean {
        Log.e("cdx", "equals")
        val person = (other as? Person) ?: return false
        return person.name == name && person.age == age
    }

    override fun hashCode(): Int {
        Log.e("cdx", "hashCode")
        return name.hashCode() * 7 + age * 5;
    }
}

5.6、为String实现四则运算

需要实现下面的效果,实现字符串的加减乘除

分析:

1、没有办法修改String的源码,所以我们需要使用扩展方法。

2、借助于自定义运算符。

package com.example.kotlindemo

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import java.lang.StringBuilder

class MainActivity2 : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        val str: String = "HelloWorld"
        val result = str - "World"
        Log.e("cdx", result)

        val result2 = str * 5
        Log.e("cdx", result2)

        val result3 = str / "l"
        Log.e("cdx", result3.toString())
    }
}

operator fun String.minus(other: Any): String {
    return this.replaceFirst(other.toString(), "")
}

operator fun String.times(time: Int): String {
    var sb = StringBuilder()
    for (i in 0 until time) {
        sb.append(this)
    }
    return sb.toString()
}

operator fun String.div(str: String): Int {
    // 这个东西很好玩
    // String字符串上一点一点的划,划的长度是str.lenght,
然后间隔是1,表示一个一个的划
    // 然后获取到划到的集合
    return this.windowed(str.length, 1) {
        it == str
    }
        // 然后数一数里面欧多少个满足条件的,it为true的就筛选出来
        .count {
            it
        }
}

6章、函数进阶

6.1、高阶函数

定义

参数包含函数类型或者返回值为函数类型。

// 参数param是函数类型
fun test1(param: () -> Int) {
// 涉及函数的调用
// param()
param.invoke()
}

// 返回值是函数类型
fun test2(): () -> Int {
// 返回的是一个匿名函数,返回值是1
return { 1 }
}



        // 定义一个数组
        var arr = intArrayOf(1, 2, 3, 4)
        // 遍历功能
        arr.forEach {
            Log.e(TAG, it.toString())
        }
        Log.e(TAG, arr.joinToString())

        // 遍历,然后转成另外的元素,然后存到数组中
        val map = arr.map {
            it * it
        }
        Log.e(TAG, map.joinToString())

高阶函数的调用

        // 定义一个数组
        var arr = intArrayOf(1, 2, 3, 4)
        // 高阶函数的引用 ::println表示对函数的引用
        arr.forEach(::println)

高阶函数调用-简化的几个阶段

非常关键

  	// 定义一个数组
        var arr = intArrayOf(1, 2, 3, 4)
        // 第一种方式
        arr.forEach({
            println("Hello $it")
        })

        // 如果接受的高阶函数是最后一个参数,小括号可以移动到外面
        arr.forEach() {
            println("Hello $it")
        }

        // 如果括号中没有任何的参数,那么可以将小括号省略
        arr.forEach {
            println("Hello $it")
        }

高阶函数应用 - 计算方法的时间

        // 首先传入一个匿名函数
//        const({
//            (1..10000000).forEach { it * it }
//        })

        // 优化成下面的样式
//        const(){
//            (1..10000000).forEach { it * it }
//        }

        // 继续优化
        const {
            (1..10000000).forEach { it * it }
        }

6.2、内联函数

定义

减少了函数的调用,性能优化。

分析:

我们可以看到Array.kt文件中forEach的定义,里面有一个关键字inline,它有什么作用呢?

public inline fun IntArray.forEach(action: (Int) -> Unit): Unit {
    for (element in this) action(element)
}

我们在代码中调用格式如下:

        // 定义一个数组
        var arr = intArrayOf(1, 2, 3, 4)
        // 第一种方式
        arr.forEach({
            println("Hello $it")
        })

编译器最终转换为的是一个内联函数

       // 定义一个数组
        var arr = intArrayOf(1, 2, 3, 4)
        // 第一种方式
        for(element in arr){
           println(element.toString())
		}

内联函数的优点

减少了函数的调用(主要是减少了函数的压栈、调用执行、出栈的操作),性能更好。

高阶函数与内联函数更加匹配

比如下面的函数

class MainActivity2 : AppCompatActivity() {
    val TAG = "cdx"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)
        const({
            print("1")
        })
    }

    fun const(block: () -> Unit) {
        var start = System.currentTimeMillis()
        block()
        var end = System.currentTimeMillis()
        Log.e(TAG, (end - start).toString())
    }

}

然后我们查看下编译后的代码(输入Show Kotlin Bytecode

我们看下其中2个消耗时间的地方

1、调用了const方法。

2、创建了一个匿名函数。

然后我们优化const方法的定义,增加inline关键字,让他变成一个内联函数

            inline fun const(block: () -> Unit) {
            var start = System.currentTimeMillis()
            block()
            var end = System.currentTimeMillis()
            Log.e(TAG, (end - start).toString())
        }

然后我们查看下编译后的代码(输入Show Kotlin Bytecode

我们可以看到,省去了函数的调用,性能更高。

内联函数return 之 local return(本地返回)

设想这样一个场景,当遇到3时,不打印,如何在forEach()中实现这一点呢?操作如下:

//Kotlin
var ints = intArrayOf(1, 2, 3, 4)

fun main() {
    ints.forEach {
        if (it == 3) return@forEach
        println(it)
    }
    println("Dividing")
    ints.forEach {
        if (it == 3) return
        println(it)
    }
    println("Ending")
}

控制台输出:

return@forEach和return的打印结果完全不同。

return@forEach将提前结束本次循环,相当于continue;

return将结束所在函数,此处即为main()函数,因为Ending没有打印。 

像return@forEach这样的返回称为local return。(continue的功能的就是local-return )

内联函数return 之 non-local return

不是本地返回,就是全局返回。

上面的return就是non-local return。

内联函数local return 和 non-local return 的研究讨论

首先看下面的例子,将高阶函数定义为内联函数:

//Kotlin
inline fun nonLocalReturn(block: ()->Unit){
    block()
}

fun main() {
    println("Starting")
    nonLocalReturn { return }
    println("Ending")
}

控制台将打印:

Starting

我们看到执行了 non-local return,全局进行了返回。

然后我们看看下面的例子,如果将高阶函数定义成非内联函数

这个地方不能执行return,就是不能执行non-local return

为什么会出现上面的情况呢?

对于第一个例子,由于是一个内联函数,内联函数没有压栈和出栈的操作,会将代码直接插入调用处,所以会在main函数内部调用return,所以会出现non-local retuan 全局返回。

crossinline

noinline

noline:禁止函数参与内联

内联属性

没有back-field的属性的getter和setter可以被内联,只需要在getter和setter上增加inline

class MainActivity2 : AppCompatActivity() {
    val TAG = "cdx"

    var pocket: Double = 0.0
    // 内联属性
    var money: Double
        inline get() = pocket
        inline set(value) {
            pocket = value
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main2)

        money = 5.0
    }
}

然后我们编译下

发现money属性已经没有了,money属性已经被替换了。

6.3、几个有用的高阶函数

let、run

let:当我们需要定义一个变量在特定的作用域内,let函数是一个不错的选择。

        // 定义对象
        var person = Person("张三", 25)

        val name = person.let {
            it.name
        }
        Log.e(TAG, name.toString())

run:和let的作用相似,只是一个是it,一个是this

        // 定义对象
        var person = Person("张三", 25)

        var result = person.run {
            this.age
        }

        Log.e(TAG, result.toString())

also、apply

        // 定义对象
        var person = Person("张三", 25)

        // 利用also来修改对象属性
        var person2 = person.also {
            it.name = it.name + "V2"
        }
        Log.e(TAG, person2.toString())

        var person3 = person.apply {
            name = "xxx"
        }
        Log.e(TAG, person3.toString())

6.4、集合变换与序列

filter操作

筛选出来合适的,然后放到集合中

        val list = intArrayOf(1, 2, 3, 4)
        // 取出偶数的值,过滤
        val result = list.filter { it % 2 == 0 }
        Log.e(TAG, result.toString()) // [2, 4]

map操作

遍历数组,然后进行操作,然后放到集合中

        val list = intArrayOf(1, 2, 3, 4)
        val result = list.map { it * 2 }
        Log.e(TAG, result.joinToString())

flatMap操作

flatMap操作的流程图

1)首先取出来元素

2)然后根据元素生成数组

3)然后在将这些数组进行拼接。

        val list = intArrayOf(1, 4, 7)
        // 初始化的值为StringBuilder()
        // acc为拼接的值
        list.fold(StringBuilder()) { acc, i ->
            Log.e(TAG, "acc=$acc")
            Log.e(TAG, i.toString())
            acc.append(i)
        }

懒序列懒在哪里?

通过调用asSequence(),然后只有调用forEach方法(fold、ruduce、sum)以后,

才会去执行上面的filter、map、flatMap方法

6.5、SAM(单一抽象方法转换)转换

SAM转换例子

如果匿名内部类内部只有一个方法,那么这个匿名内部类可以非常的简化,只有一个{ } 。

        // SAM例子
        var exe: ExecutorService? = null
        // 匿名内部类
//        exe?.submit(object : Runnable {
//            override fun run() {
//                Log.e(TAG,"任务执行的")
//            }
//        })
        // 简化写法
//        exe?.submit({
//            Log.e(TAG, "任务执行的")
//        })
        // 继续简化
        exe?.submit {
            Log.e(TAG, "任务执行的")
        }

kotlin的匿名内部类

        // 匿名内部类的通常写法
        object : Runnable {
            override fun run() {
                Log.e(TAG,"任务执行的")
            }
        }

        // 匿名内部类的简写写法
        Runnable {
            Log.e(TAG,"任务执行的")
        }