kotlin基础

kotlin诞生

2011年,JetBrains宣布开发Kotlin编程语言,这门新语言可以在Java虚拟机上运行的代码,kotlin语法简介,具备现代高级语言特性,并与Java遗留代码无缝互操作

kotlin与jvm

Java Kotlin   Scala   Groovy   被编译成字节码    运行在 JVM   通过指令   在操作操作系统上  windows linux macOS

kotlin是编译型语言

kotlin的跨平台特性

Kotlin不仅支持编写代码在虚拟机上运行,而且还是一门跨平台的通用型语言,我们可以用Kotlin开发各种类型的原生应用,如Android macOS Windows Javascript应用

Kotlin能脱离虚拟机层,直接编译成可以在windows linux macos平台上运行的原生二进制代

码、

声明变量和内置数据类型

var max : Int = 5              

var :  变量定义关键字

max  变量名

Int     类型定义

=       赋值运算符

5       赋值

fun main{
    var str : String = "Hello World"
    println(str)
}

kotlin内置数据类型

类型描述示例
String字符串“Hello World”
Char单字符'A'
Booleantrue / false  true false
Double小数3.14
List元素集合

1,8,10

"a","b","c","a"

Set无重复元素集合"a","b","c"
Map键值对集合"small" to 5 , "medium " to 8 , "large" to 9
Int整数5

只读变量

声明可修改变量    使用var关键字

声明只读变量        使用val关键字

fun main(args : Array<String>){
    val name : String = "jack"
    var age : Int = 10
    age += 1
    println(name)
}

类型推断

类型推断:对于已声明并赋值的变量 ,它允许你省略类型定义

Remove explicit type specification

编译时常量

只读变量并非绝对只读。

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

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

查看kotlin字节码

查看kotlin编译之后的字节码

两种方式:

1.Shift 键两次,输入Show kotlin

2.Tools -> Kotlin -> Show Kotlin Bytecode

kotlin的引用类型于基本数据类型

Java有两种数据类型:引用类型与基础数据类型

Kotlin只提供引用类型这一种数据类型,但出于更高性能的需要,Kotlin编译器会在Java字节码中改为基本数据类型

表达式

if / else 表达式

range表达式

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

when表达式

          允许你编写条件式 ,在某个条件满足时,执行对应的代码

          只要代码包含else if分支,都建议改为when表达式

fun main() {
    val age = 3
    if(age in 0..3){
        println("婴幼儿")
    }else if(age in 3..12){
        println("少儿")
    }else{
        println("未知")
    }

    if(age !in 1..3){

    }

    val school = "0小学"
    val level = when(school){
        "学前班"  -> "幼儿"
        "小学" -> "少儿"
        "中学" -> "青少年"
        else -> {
            println("未知")
        }
    }
    println(level)
}

String模板

模板支持在字符串的引号内放入变量值

还支持字符串里计算表达式的值并插入结果,添加在${}中的任何表达式,都会作为字符串的一部分求值

fun main() {
    val origin = "Jack"
    val dest = "Rose"
    println("$origin love $dest")

    val flag = false
    println("Answer is: ${if(flag) "我可以" else "对不起"}")
}

函数头

private fun doSomething(age : Int , flag : Boolean) : String

private 可见性修饰符

fun       函数声明关键字

doSomething 函数名

flag      函数参数

String   返回类型

fun main() {
    println(fix(10,"Rose"))
 
}

private fun doSomething(age:Int = 2, flag:Boolean):String{
    return "result"
}

fun fix(name:String, age:Int = 2){
    println(name + age)
}

函数参数默认值参数

默认值参

        如果不打算传入值参 ,可以预先给参数指定默认值

具名函数参数

        如果使用命名值参,就可以不用管值参的顺序

fun main() {
    //使用具名函数参数   也就是指定参数指定名字  键值对形式
    println(fix(age=10,name="Rose"))
    
}
//默认值     
private fun doSomething(age:Int = 2, flag:Boolean):String{
    return "result"
}

fun fix(name:String, age:Int = 2){
    println(name + age)
}

Unit函数

Kotlin没有返回值的函数叫Unit函数   返回类型是Unit    有点像Java的void

fun main() {
    println(fix(age=10,name="Rose"))
   
}


fun fix(name:String, age:Int = 2){
    println(name + age)
}

输出 Rose10

         kotlin.Unit 没有返回参数类型

Nothing函数   

也是没有返回值类型

TODO函数的任务就是抛出异常,就是永远别指望它运行成功,返回Nothing类型

fun main() {
    //使用具名函数参数   也就是指定参数指定名字  键值对形式
    println(fix(age=10,name="Rose"))
    TODO("nothing")
    println("after nothing.")
}
//默认值     
private fun doSomething(age:Int = 2, flag:Boolean):String{
    return "result"
}

fun fix(name:String, age:Int = 2){
    println(name + age)
}

反引号中的函数名

Kotlin可以使用空格和特殊字符对函数命名,不过函数名要用一对反引号括起来。

为了支持Kotlin和Java互操作,而Kotlin和Java各自却有着不同的保留字关键字,不能作为函数名,使用反引号括住函数名就能避免冲突。

fun main() {
    MyJava.`is`()
}

fun `**~special function with weird name~**`(){

}
public class MyJava {

    public static void is(){
        System.out.println("is invoked");
    }

}

匿名函数

定义时不取名字的函数,我们称匿名函数,匿名函数通常整体传递给其他函数,或者从其他函数返回。

匿名函数对Kotlin来说很重要,有了它,我们能够根据需要定制特殊规则,轻松定制标准库里的内置函数

fun main() {
    val total = "Mississippi".count()

    val totalS = "Mississippi".count{it == 's'}

    println(total)
    println(totalS)

    //变量的类型是一个匿名函数
    /*val blessingFunction:()->String = {
        val holiday = "New Year."
        "Happy $holiday"
    }*/

    /*val blessingFunction:(String) -> String = { name ->
        val holiday = "New Year."
        "$name, Happy $holiday"
    }*/

    /*val blessingFunction:(String) -> String = {
        val holiday = "New Year."
        "$it, Happy $holiday"
    }*/

    /*val blessingFunction = {
        val holiday = "New Year."
        "Happy $holiday"
    }*/

    /*val blessingFunction:(String,Int) -> String = {name, year ->
        val holiday = "New Year."
        "$name, Happy $holiday $year"
    }*/


    val blessingFunction = {name:String, year:Int ->
        val holiday = "New Year."
        "$name, Happy $holiday $year"
    }

    println(blessingFunction("Jack",2027))

}

函数类型与隐式返回

匿名函数也有类型,匿名函数可以当作变量赋值给函数类型变量,就像其他变量一样,匿名函数就可以在diamagnetic里传递了。变量由类型,变量可以等于函数,函数也会有类型,函数的类型,由传入的参数和返回值类型决定。

和具名函数不一样,除了极少数情况外,匿名函数不需要return关键字来返回数据,匿名函数会隐式或自动返回函数体最后一行语句的结果。

    val blessingFunction:(String) -> String = {
        val holiday = "New Year."
        "$it, Happy $holiday"
    }

    val blessingFunction = {
        val holiday = "New Year."
        "Happy $holiday"
    }

    val blessingFunction:(String,Int) -> String = {name, year ->
        val holiday = "New Year."
        "$name, Happy $holiday $year"
    }

函数参数

和具名函数一样,匿名函数可以不带参数,也可以带一个或多个任何类型的参数,需要带参数时,参数的类型放在匿名函数类型定义中,参数名则放在函数定义中

val blessingFunction:(String) - > String = {name -> 
    val holiday = "New Year"
    "$name , Happy $holiday"
}
println(blessingFunction("Jack"))

it关键字

定义只要一个参数的匿名函数时,可以使用it关键字来表示参数名。当你需要传入两个值参,it关键字就不能用了

val blessingFunction:(String) -> String = {
    val holiday = "New Year"
    "$it , Happy $holiday"
}

println(blessingFunction("Jack"))

匿名函数的类型推断

定义一个变量时,如果已把匿名函数作为变量赋值给它,就不需要显示指明变量类型了

val blessing function {
    val holiday = "New Year"
    "Happy $holiday"
}

类型推断也支持带参数的匿名函数,但为了帮助编译器更准确地推断变量类型,匿名函数的参数名和参数类型必须有

val blessingFunction = {name :Stirng, year: Int -> 
    val holiday = "New Year"
    "$name , Happy $holiday $year"
}

lambda

我们将匿名函数称为lambda  ,将它的定义称为lambda表达式,它返回的数据称为lambda

定义参数式函数的函数

函数的参数是另外一个函数

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

    showOnBoard("卫生纸",getDiscountWords)
}

//具名函数
private fun showOnBoard(goodsName:String, getDiscountWords: (String,Int) -> String){
    val hour = (1..24).shuffled().last()
    println(getDiscountWords(goodsName,hour))
}

const val MAX = 500

简略写法

如果一个函数的lambda参数排在最后,或者是唯一的参数,那么括住lamdba值参的一堆圆括号就可以省略

val totalS  =  "Mississipi".count({it = 's'})
val totalS  =  "Mississipi".count{it = 's'}

函数内联  inline 

在JVM上,你定义的lambda会以对象实例的形式存在,JVM会为所有同lambda打交道的变量分配内存,就会产生内存开销。kotlin有一种优化机制叫内联,有了内联,JVM就不需要使用lambda对象实例了,因而避免了变量内存分配。哪里需要使用lambda,编译器就会将函数体复制粘贴到哪里

使用lambda的递归函数无法内联,因为会导致复制粘贴无线循环,编译会发出警告

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

    showOnBoard("卫生纸",getDiscountWords)
}

//具名函数
private inline fun showOnBoard(goodsName:String, getDiscountWords: (String,Int) -> String){
    val hour = (1..24).shuffled().last()
    println(getDiscountWords(goodsName,hour))
}

函数引用

要把函数作为参数传给其他函数使用,除了传lambda表达式,kotlin还提供了其他方法,传递函数引用,函数引用可以把一个具名函数转换成一个值参,使用lambda表达式的地方,都可以使用函数引用

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

函数类型作为返回类型

函数类型也是有效的返回类型,也就是说可以定义一个能返回函数的函数

fun main() {
    val getDiscountWords = configDiscountWords()
    println(getDiscountWords("沐浴露"))
}

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

闭包

在Kotlin中,匿名函数能修改并应用定义在自己的作用域之外的变量,匿名函数引用者定义自身的函数里的变量,Kotlin中的lambda就是闭包

能接收函数或者返回函数的函数又叫做高级函数,高级函数广泛应用于函数式编程当中

简单理解:在同一个包里两个不同类   定义两个相同名字的参数  就会报错

lambda与匿名函数内部类

函数类型能让开发者少写模式化代码,写出更灵活的代码。Java8支持面向对象编程和lambda表达式,但不支持将函数作为参数传给另一个函数或变量,不过Java的替代方案式匿名内部类

public class JavaAnonymousClass {

    public static void main(String[] args) {
        showOnBoard("牙膏", new DiscountWords() {
            @Override
            public String getDiscountWords(String goodsName, int hour) {
                int currentYear = 2017;
                return String.format(String.format("%d年,双11%s促销倒计时:%d 小时",currentYear,goodsName,hour));
            }
        });
    }

    public interface DiscountWords{
        String getDiscountWords(String goodsName,int hour);
    }

    public static void showOnBoard(String goodsName,DiscountWords discountWords){
        int hour = new Random().nextInt(24);
        System.out.println(discountWords.getDiscountWords(goodsName,hour));
    }

}

Kotlin的可空性

对于null值问题,Kotlin反其道而行之,除非另有规定,变量不可为null值,这样一来,运行时崩溃从根源上得到解决

fun main(){
    val str = "butterfly"
    str = null   //这里报红
    pringln(str)
}

为了避免NullPointerException,Kotlin的做法时不让我们给非空类型变量赋null值,但null在Kotlin中依然存在   加个问号就是可空类型

fun main() {
    var str:String? = ""
    str = null
    println("input:$str")
}

安全调用操作符  null安全

Kotlin区分可空类型和非可空类型,所有,你要一个可空类型变量运行,而它又可以不存在,对于这种潜在危险,编译器时刻警惕着,为了应对这种风险,Kotlin不循序你在可空类型值上面调用函数,除非你主动接手安全管理

1.安全调用操作符

这次kotlin不报错了,编译器看到有安全调用操作符,所以它知道如何检查null值,如果遇到null值,他就跳过函数调用,而不是返回null    ?.就是安全调用符

fun main() {
    var str:String? = "butterfly"
    
    str = str?.capitalize()?.plus(" is great.")

}

使用带let的安全调用

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

fun main() {
    var str:String? = "butterfly"
    str = null
    str = str?.let {
        //非空白的字符串
        if(it.isNotBlank()){
            it.capitalize()
        }else{
            "butterfly"
        }
    }
    println(str)
}

非空断言操作符

2.使用非空断言操作符

!!.又称感叹号操作符,当变量为null时,会抛出KotlinNullPointerException

fun main() {
    var str:String? = "butterfly"
    str = null
    println(str!!.capitalize())
}

对比使用if判断null值情况

3.我们也可以使用if判断,但是相比之下安全调用操作符用起来更灵活,代码也更简介

我们可以使用安全操作符进行多个函数的链式调用

fun main() {
    var str:String? = "butterfly"
    str = null
    if(str != null){
        str = str.capitalize()
    }else{
        println("为null.")
    }
    str = str?.capitalize().plus(" is great.")  链式   前面为空 后面都不执行
    println(str)
}

空合并操作符

?: 操作符的意思是  如果左边的求值结果为null 就使用右边的结果值

val strWithSafe:String = str ?: "butterfly"

空合并操作符也可以和let函数一起使用来代替if/else语句

fun main(){
    var str : String? = readLine()
    str = str?.let{it.capitalize() }?:"butterfly"
    println(str)
}

异常处理与自定义异常

fun main() {
    var number: Int? = null

    try {
        checkOperation(number)
        number!!.plus(1)
    }catch (e: Exception){
        println(e)
    }
}

fun checkOperation(number: Int?){
    //number ?: throw UnskilledException()
    checkNotNull(number, {"Something is not good."})
}

//自定义异常
class UnskilledException() : IllegalArgumentException("操作不当")

先决条件函数

Kotlin标准库提供了一些便利函数,使用这些内置函数,你可以抛出带自定义信息的异常,这些遍历函数叫做先决条件函数。您可以用它定义先决条件,条件必须满足,目标代码才能执行

函数描述
chechNotNull如果参数为null,则抛出IIIegalStateException异常,否则返回非null值
require如果参数为false,则抛出IIIegaIArgumentException异常
requireNotNull如果参数为null,则抛出IIIegaIStateException异常,否则返回非null值
error如果参数为null ,则抛出IIIegaIStateException异常并输出错误信息,否则返回非null值
assert如果参数为false,则抛出AssertError异常,并打上断言编译器标记
fun checkOperation(number: Int?){
    //number ?: throw UnskilledException()
    checkNotNull(number, {"Something is not good."})
}

substring

字符串截取,substring 函数支持IntRange函数(表示一个整型范围的类型)的参数,until创建的范围不包括上限值

const val NAME = "Jimmy's friend"
fun main() {

    val index = NAME.indexOf('\'')
    //val str = NAME.substring(0, index)
    var str = NAME.substring(0 until index)
    println(str)
}

split

split函数返回的是List集合数据,List集合又支持解构语法特性,它允许你在一个表达式里多个变量赋值,解构常量来简化变量的赋值

const val NAMES = "jack,jacky,jason"
fun main() {
    val data = NAMES.split(",")
    //data[0]

    val (origin,dest,proxy) = NAMES.split(",")
    println("$origin $dest $proxy")  
}

replace

字符串替换

fun main() {
    //加密替换一个字符串
    val str1 = "The people's Republic of China."
    //第一个参数式正则表达式,用来决定要替换哪些字符
    //第二个参数式匿名函数,用来确定该如何替换正则表达式搜索到的字符
    val str2 = str1.replace(Regex("[aeiou]")){
        when(it.value){
            "a" -> "8"
            "e" -> "6"
            "i" -> "9"
            "o" -> "1"
            "u" -> "3"
            else -> it.value
        }
    }

    println(str1)
    println(str2)

    

}

== 与 ===比较

在Kotlin中,用==检查两个字符串中的字符是否匹配,用===检查两个变量是否指向内存堆上同一对象,而在Java中==做引用比较,做结构比较时用equals方法

fun main() {
    val str1 = "Jason"
    val str2 = "jason".capitalize()
    println(str1 == str2)
    println(str1 === str2)  //true 1 false 2

}

字符串遍历

 "The people's Republic of China.".forEach {
        print("$it *")
    }

数字类型的安全转换函数

Kotlin提供了toDoubleOrNull和toIntOrNull这样的安全转换函数,如果数值不能正确转换,与其触发异常不如干脆返回null值

fun main() {

    //val number1: Int = "8.98".toInt()
    val number1: Int? = "8.98".toIntOrNull()
    println(number1)

}

Double转Int与类型格式化

精度损失与四舍五入

fun main() {

    println(8.956756.toInt())
    println(8.956756.roundToInt())

    val s = "%.2f".format(8.956756)
    println(s)
}

apply

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

fun main() {
    val file1 = File("E://i have a dream_copy.txt")
    file1.setReadable(true)
    file1.setWritable(true)
    file1.setExecutable(false)

    val file2 = File("E://i have a dream_copy.txt").apply {
        setReadable(true)
        setWritable(true)
        setExecutable(false)
    }
}

可以看到,调用一个个函数类配置接收者时没变量名就省掉了,这是因为,在lambda表达式里,apply能让每个配置函数都作用于接收者,这种行为有时又叫做相关作用域,因为lambda表达式里的所有函数调用都针对接收者,或者说,他们是针对接收者的隐式调用

let

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

fun main() {
    val result = listOf(3, 2, 1).first().let {
        it * it
    }
    println(result)

    //val firstElement = listOf(3, 2, 1).first()
    //val result = firstElement * firstElement

    println(formatGreeting("Jack"))

}

fun formatGreeting(guestName: String?): String {
    return guestName?.let {
        "Welcome, $it."
    } ?: "What's your name?"
}

fun formatGreeting2(guestName: String?): String {
    return if(guestName != null){
        "Welcome, $guestName."
    }else{
        "What's your name?"
    }
}

run

光看作用域行为,run和apply差不多,但与apply不同,run函数不返回接收者,run返回的是lambda结果,也就是true或者false

fun main() {
    var file = File("E://i have a dream_copy.txt")
    val result = file.run {
        readText().contains("great")
    }
    println(result)

    val result2 = "The people's Republic of China.".run(::isLong)
    println(result2)

    "The people's Republic of China."
        .run (::isLong)
        .run (::showMessage)
        .run (::println)
}

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

fun showMessage(isLong:Boolean):String{
    return if(isLong){
        "Name is too long."
    }else{
        "Please rename."
    }
}

with

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

fun main() {
    val result1 = "The people's Republic of China.".run {
        length >= 10
    }
    val result2 = with("The people's Republic of China.") {
        length >= 10
    }
}

also

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

fun main() {
    var fileContents:List<String>
    val file = File("E://i have a dream_copy.txt")
        .also {
            println(it.name)
        }.also {
            fileContents = it.readLines()
        }

    println(fileContents)
}

takeIf

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


fun main() {
   val result = File("D://i have a dream_copy.txt")
        .takeIf { it.exists() && it.canRead() }
        ?.readText()

    println(result)

   
}

takeUnless

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

fun main(){
    val result = File("E://i have a dream_copy.txt")
        .takeUnless { it.isHidden }
        ?.readText()

    println(result)
}

List创建与元素获取

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

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

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

fun main(){
    val list:List<String> = listOf("a","b","c")
    printlin(list.getOrElse(4){"Unknown"})
    printlin(list.getOrNull(4))
    printlin(list.getOrNull(4) ?: {"Unknown"})
    
}

可变List集合

在kotlin中支持内容修改的列表叫可变列表,要创建可变列表,可以使用mutableListOf函数。List还支持使用toList和toMutableList函数动态实现只读列表和可变列表的互相转换

fun main(){
    val mutableList:MutableList<String> = mutableListOf("a","b","c")
    mutableList.add("d")
    mutableList.remove("b")
    println(mutableList)
    listOf("a","b","c").toMutableList()
    mutableListOf("a","b","c").toList()
}

mutator函数

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

添加元素运算符与删除元素运算符

基于lambda表达式指定的条件删除元素

fun main(){
    val mutableList:MutableList<String> = mutableListOf("a","b","c")
    mutableList += "d"
    println(mutableList)
    mutableList -= "d"
    println(mutableList)
    mutableList.removeIf{it.contains("a")}
    println(mutableList)
}

List集合遍历

for in 遍历

forEach 遍历

forEachIndexed 遍历时要获取索引

fun main(){
    val list:List<String> = listOf("a","b","c")
    for(s:String in list){
        println(s)
    }
    list.forEach{it:String
        println(it)
    }
    list.forEachIndexed{index, item ->
        println("$index,$item")
    }
}

解构语法过滤元素

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

fun main(){
    val list:List<String> = listOf("a","b","c")
    val (origin:String,_:String,proxy:String) = list
}

Set创建与元素获取

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

fun main(){
    val set : Set<String> = setOf("kotlin","java","scala")
    //没有这样写法
    //set[3]
    set.elementAt(2)
}

可变Set集合

通过mutableSetOf创建可变的set集合

fun main(){
    val mutableSet : MutableSet<String> = mutableSetOf("kotlin","java",scala)
    mutableSet += "Groovy"
}

集合转换与快捷函数

把list转换成set ,去掉重复元素

fun main(){
    val list:List<String> = listOf("java","jack","jacky","jacky")
        .toSet()
        .toList()
    println(list)
    println(listOf("jason","jack","jacky","jacky").distinct())
}

数组类型

kotlin提供各种array. 虽然是引用类型,但可以编译成java基础数据类型

数组类型创建函数
InArrayinArrayOf
DoubleArraydoubleArrayOf
LongArraylongArrayOf
ShortArrayshortArrayOf
ByteArraybyteArrayOf
FloatArrayfloatArrayOf
BooleanArraybooleanArrayOf
ArrayarrayOf

Map的创建

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

fun main(){
    val map : Map<String,Int> = mapOf("java" to 20,"kotlin" to 80)
    println(map)
    mapOf(Pair("Jack",20),Pair("Jason",18))
}

读取Map的值

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

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

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

getOrDefalut,读取键对应的值。或者返回默认值

fun main(){
    val map : Map<String,Int> = mapOf("java" to 20,"kotlin" to 80)
    println(map["java"])
    println(map.getValue["java"])
    println(map.getOrElse["java"])
    println(map.getOrDefault["java"])
}

遍历Map

forEach遍历Map

fun main(){
    val map : Map<String,Int> = mapOf("java" to 20,"kotlin" to 20)
    map.forEach{it:Map.Entry<String,Int>
        println("${it.ket},${it.value}")       
    }
    map.forEach{(key : String,value : Int) ->
        println("${it.ket},${it.value}")       
    }
}

可变Map结合

通过mutableMapOf创建可变的Map

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

fun main(){
    val mutableMap : MutableMap<String,Int> = mutableMapOf("java" to 20,"kotlin" to 20)
    mutableMap += "Jimmy" to 30
    mutableMap.put("Jimmy",31)
    println(mutableMap.getOrPut("Jimmy"){18})
    mutableMap.getOrPut("Rose"){18}
    println(mutableMap)

}

定义类的fieId关键字

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

定义一个player类

class Player{
      var name = "abc"
          get() = field.capitalize()
          set(value){
              field = value.trim()
          }
      var age = 10
          get() = field.absoluteValue
          private set(value){
              field = value.absoluteValue
          }
}

计算属性与防范竞态条件

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

class Player{
      val rolledValue
          get() = (1..6).shuffled)().first()
}

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

class Player{
      var words : String? = "hello"
      fun saySomething(){
          words?.also{it:String
               println("Hello ${it.toUpperCase()}")
          }
      }
}

主构造函数

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

class Player(
      _name : String,
      _age :Int,
      _isNormal : Boolean
){
   var name = _name
       get() = field.capitalize()
       private set(value){
           field = value.trim()
       }
   var age = _age
   var isNormal = _isNormal   

}

fun main(){
    var player = Player("Jack",20,true)
}

在主构造函数里定义属性

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

calss Player2(
      _name : String,
      var age : Int,
      val isNormal : Boolean
){
      var name = _name
          get() = field.capitalize()
          private set(value){
              field = value.trim()
          }
}

次构造函数

有主就有次,对应主构造函数的是此构造函数,我们可以定义多个次构造函数来配置不同的参数组合

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

constructor(name : String) : this(name,
                                  age = 100,
                                  isNormal = false){
      this.name = name.toUpperCase()
}

默认参数

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

class Player3(
      _name : String,
      var age : Int = 20,
      private val isNormal : Boolean
){
      var name = _name
          get() = field.capitalize()
          private set(value){
               field = value.trim()
          }
}

初始化块

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

init {
     require(age > 0){"age must be positive"}
     require(name.isNotBlank()){"player must have a name"}
}

初始化顺序

主构造函数里声明的属性

类级别的属性赋值

init初始化里的属性赋值和函数调用

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

延时初始化lateinit

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

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

calss Player4{
      lateinit var equipmen : String
      fun ready(){
            equipmen = "sharp knife"
     }
     fun battle(){
           if (::equipment.isInitialized) println(equipment)
     }
}

惰性初始化by lazy

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

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

继承与重载的open关键字

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

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

open class Product(val name:String){
     fun description() = "Product: $name"
     open fun load() = "Nothing..."
}
class Luxuryproduct : Product("Luxury"){
      override fun load() = "LuxuryProduct loading..."
}
fun main(){
    val p : Product = LuxuryProduct()
    println(p.load())
}

类型转换

Kotlin的is运算符是个不错的工具,可以用来检查某个对象的类型

fun main(){
    val p = LuxuryProduct()
    println(p is LuxuryProduct)
    println(p is Product)
}

智能类型转换

kotlin编译器很聪明,只有能确认any is 父类条件检查属实,它就会将any当做子类型对待,因此,编译器允许你不经过类型转换直接使用

fun sale(p : Product){
    println(p.load())
}

fun main(){
    val p = LuxuryProduct()
    //sale(p as Product)
    sale(p)
}

Any超类

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

对象声明

object 关键字

使用object关键字,你可以定义一个只能产生一个实例的类,单例

使用object关键字有三种

对象声明

对象表达式

伴生对象

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

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

}

fun main(){
    ApplicationConfig.setSomething()
    println(ApplicationConfig)
    println(ApplicationConfig). 打印同一对象地址
}

对象表达式

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

open class Player{
     open fun load = "loading nothing"
}
fun main(){
    val p = object : Player(){
        override fun load() = "anonymous class load ...."
    }
    println(p.load())
}

伴生对象

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

open class ConfigMap{
    //只有初始化ConfigMap类或调用load函数时,伴生对象的内容才会载入
    //而且无论实例化ConfigMap类多少次,这个伴生类对象只有一个实例存在
    companion object{
         private const val PATH = "xxx"
         fun load() = File(PATH).readBytes()
   
    }
}

嵌套类

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

class Player2 {

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

    fun battle(){

    }

}

fun main() {
    Player2.Equipment("sharp knife").show()
}

数据类

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

data class Coordinate(var x: Int, var y: Int) {
    val isInBounds = x > 0 && y > 0
}

fun main() {
    println(Coordinate(10, 20))
    // == 比较的是内容,equals,Any 默认实现===,比较引用
    // === 比较的是引用
    println(Coordinate(10, 20) == Coordinate(10, 20))

    val (x, y) = Coordinate(10, 20)
    println("$x, $y")
}

copy函数

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

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


}

fun main() {
    val  s = Student("Jack")
    val copy = s.copy("Rose")
    println(s)
    println(copy)
}

解析声明

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

class PlayerScore(val experience: Int, val level: Int) {
    operator fun component1() = experience
    operator fun component2() = level
}

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

    println("$x, $y")
}

运算符重载

如 果 要 将 内 置 运 算 符 应 用 在 自 定 义 类 身 上 , 你 必 须 重 写 运 算 符 函 数 , 告 诉 编 译 器 该 如 何 挨 作 自 定 义 类 。

data class Coordinate2(var x: Int, var y: Int) {
    val isInBounds = x > 0 && y > 0

    operator fun plus(other: Coordinate2) = Coordinate2(x + other.x, y + other.y)
}

fun main() {

    val c1 = Coordinate2(10, 20)
    val c2 = Coordinate2(10, 20)

    println(c1 + c2)
}

枚举类

枚举类,用来定义常量集合的一种特殊类

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

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

枚举类定义函数

枚举类也可以定义函数

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

代数数据类型

可以用来表示一组子类型的闭集 枚举类就是一种简单的ADT

enum class LicenseStatus {

    UNQUALIFIED,
    LEARNING,
    QUALIFIED;

    var licenseId: String? = null

}

class Driver(var status: LicenseStatus) {
    fun checkLicense(): String {
        return when(status){
            LicenseStatus.UNQUALIFIED -> "没资格"
            LicenseStatus.LEARNING -> "在学"
            LicenseStatus.QUALIFIED -> "有资格"
        }
    }
}

fun main() {
    println(Driver(LicenseStatus.QUALIFIED).checkLicense())
}

密封类

对 于 更 复 杂 的 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("238239329")
    val driver = Driver2(status)
    println(driver.checkLicense())
}

数据类使用条件

数 据 类 , 是 专 门 设 计 用 来 存 储 数 据 的 类 ,

数 据 类 供 了 toString 的 个 性 化 实 现

==符 号 默 认 情 况 下 , 比 较 对 象 就 是 比 较 它 们 的 用 值 , 数 据 类 提 供 了 equals 和 hash Code 的 个 性 化 实 现

正 是 因 为 上 述 这 些 特 性 , 你 才 傾 向 于 用 数 据 类 来 表 示 存 储 数 据 的 简 单 对 象 , 对 于 那 些 经 常 需 要 比 较 , 复 制 或 打 印 自 身 内 容 的 类 , 数 据 类 尤 其 适 合 它 们 。 然 而 , 一 个 类 要 成 为 数 据 类 , 也 要 符 合 一 定 条 件 。 总 结 下 来 , 主 要 有 三 个 方 面 ·

数 据 类 必 须 有 至 少 带 一 个 参 数 的 主 构 造 函 数 ·

数 据 类 主 构 造 函 数 的 参 数 必 须 是val var

数 据 类 不 能 使 用 abstract 、 open 、 sealed 和 inner 修 饰 符

接口定义

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

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 implemented") //To change initializer of created properties use File | Settings | File Templates.
        set(value) {}

    override fun move(movable: Movable): String {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

}

接口的默认实现

只要你愿意,你可以在接口里提供默认属性的getter方法和函数实现

interface Movable {
    var maxSpeed: Int
        get() = (1..500).shuffled().last()
        set(value) {}
    var wheels: Int

    fun move(movable: Movable): String
}


class Car(_name: String, override var wheels: Int = 4) : Movable {

    override var maxSpeed: Int
        get() = super.maxSpeed
        set(value) {}

    override fun move(movable: Movable): String {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

}

抽象类

要 定 义 一 个 抽 象 类 , 你 需 要 在 定 义 之 前 加 上 abstract 关 键 字 , 除 了 具 体 的 函 数 实 现 , 抽 象 类 也 可 以 包 含抽 象 函 数 一 只 有 定 义 , 没 有 函 数 实 觋 。

abstract class Gun(val range: Int) {

    protected fun doSomething(){
        println("doSomething")
    }

    abstract fun pullTrigger(): String
}

//多重继承

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

定义泛型类

泛 型 类 的 构 造 函 数 可 以 接 受 任 何 类 型

MagicBox类 指 定 的 泛 型 参 数 由 放 在 一 对 < > 里 的 字 母 T 表 示 , T 是 个 代 表 item 类 型 的 占 位 符 。MagicBox 类 接 受 任 何 类 型 的 item 作 为 主 构 造 函 数 值 (item: T) , 并 将 item 值 赋 给 同 样 是 T 类 型 的 subject 私 有 属 性 .

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> = MagicBox(Boy("Jack",20))
    val box2:MagicBox<Dog> = MagicBox(Dog(20))
}

泛型函数

泛 型 参 数 也 可 以 用 于 函 数 。 定 义 一 个 函 数 甲 于 获 取 元 素 , 当 且 仅 当 MagicBox 可 用 时 , 才 能 获 取 元 素 。

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)

class Dog(val weight: Int)

fun main() {
    val box1:MagicBox<Boy> = MagicBox(Boy("Jack",20))
    val box2:MagicBox<Dog> = MagicBox(Dog(20))
    box1.available = true

    box1.fetch()?.run {
        println("you find $name")
    }
}

多泛型参数

泛型函数或泛型类也可以有多个泛型参数

class MagicBox<T>(item: T) {
    var available = false
    private var subject: T = item

    fun fetch(): T? {
        return subject.takeIf { available }
    }

    //业务,把元素进行修改
    //魔盒里面放的是男孩,取出来的时候,我给他改成了一个男人
    //return -> R
    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)

class Dog(val weight: Int)

fun main() {
    val box1: MagicBox<Boy> = MagicBox(Boy("Jack", 15))
    val box2: MagicBox<Dog> = MagicBox(Dog(20))
    box1.available = true

    box1.fetch()?.run {
        println("you find $name")
    }

    val man = box1.fetch {
        Man(it.name, it.age.plus(15))
    }
}

泛型类型约束

如果要确保MagicBox里面只能装指定类型的物品,如Human类型,怎么办?

class MagicBox<T : Human>(item: T) {
    var available = false
    private var subject: T = item

    fun fetch(): T? {
        return subject.takeIf { available }
    }

    //业务,把元素进行修改
    //魔盒里面放的是男孩,取出来的时候,我给他改成了一个男人
    //return -> R
    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> = MagicBox(Boy("Jack", 15))
    //val box2: MagicBox<Dog> = MagicBox(Dog(20))
    box1.available = true

    box1.fetch()?.run {
        println("you find $name")
    }

    val man = box1.fetch {
        Man(it.name, it.age.plus(15))
    }
}

vararg关键字与get函数

MagicBox 能存放任何类型的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 }
    }

    //业务,把元素进行修改
    //魔盒里面放的是男孩,取出来的时候,我给他改成了一个男人
    //return -> R
    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)

class Dog(val weight: Int)

fun main() {
    val box1: MagicBox<Boy> = MagicBox(
        Boy("Jack", 15),
        Boy("Jacky", 16),
        Boy("John", 26)
    )
    box1.available = true

    box1.fetch(1)?.run {
        println("you find $name")
    }

    val man = box1.fetch(2) {
        Man(it.name, it.age.plus(15))
    }
}

[]操作符

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

    //业务,把元素进行修改
    //魔盒里面放的是男孩,取出来的时候,我给他改成了一个男人
    //return -> R
    fun <R> fetch(index: Int, subjectModFunction: (T) -> R): R? {
        return subjectModFunction(subject[index]).takeIf { available }
    }

    operator fun get(index: Int): T? = 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)

class Dog(val weight: Int)

fun main() {
    val box1: MagicBox<Boy> = MagicBox(
        Boy("Jack", 15),
        Boy("Jacky", 16),
        Boy("John", 26)
    )
    box1.available = true

    box1.fetch(1)?.run {
        println("you find $name")
    }

    val man = box1.fetch(2) {
        Man(it.name, it.age.plus(15))
    }

    box1[0]
}

out协变   in逆变

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

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

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

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

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

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

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

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

//消费者
class Everybody : Consumer<Food>{
    override fun consume(item: Food) {
        println("Eat food.")
    }
}

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

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


fun main() {
    //赋值
    //子类泛型对象可以赋值给父类泛型对象,用 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()
    val consumer3: Consumer<Burger> = American()
}

reified关键字

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

class MagicBox<T : Human>() {

    //随机产生一个对象,如果不是指定类型的对象,就通过backup函数生成一个指定类型的对象
    /*fun <T> randomOrBackup(backup: () -> T): T {
        val items = listOf(
            Boy("Jack", 20),
            Man("John", 35)
        )
        val random = items.shuffled().first()
        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<Boy> = MagicBox()
    //又backup函数,推断出来T的类型
    val subject = box1.randomOrBackup {
        Boy("Jimmy", 38)
    }
    println(subject)
}

超类上定义扩展函数

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

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

fun Any.easyPrint() = println(this)

fun main() {
    println("abc".addExt(2))
    "abc".easyPrint()
    15.easyPrint()
}

infix关键字

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

infix fun String?.printWithDefault(default: String) = print(this ?: default)

fun main() {
    val nullableString: String? = null
    nullableString printWithDefault "abc"
    //"jack".to(18)
    //mapOf("jack" to 18)
}

定义扩展文件

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

package com.jason.kotlin.extension

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

重命名扩展

有 时 候 , 你 想 使 用 一 个 扩 展 或 一 个 类 , 但 它 的 名 字 不 和 你 的 意 。

import com.jason.kotlin.extension.randomTake as randomizer

fun main() {

    val list = listOf("Jason", "Jack", "Tom")
    val set = setOf("Jason", "Jack", "Tom")

    list.randomizer()
}

apply函数详解

 Kotlin 标 准 提 供 的 很 多 功 都 是 过 扩 展 函 数 和 扩 展 属 性 来 实 现 的 , 包 含 类 扩 展 的 标 准 库 文 件 通 帛 都 是 以 类 名 加 s后 缀 来 命 名 的 , 例 如 Sequences.kt , Ranges.kt, Maps.kt

apply函 数 是 如 何 做 到 支 持 接 收 者 对 象 的 隐式 调 用 的 。

import java.io.File

//扩展函数
fun String.addExt() = "!".repeat(count())

//泛型的扩展函数
fun <T> T.easyPrint(): Unit = println(this)

//为什么要传入扩展函数(泛型),而不是一个普通的匿名函数?
//T.() -> Unit
//扩展函数里自带了接收者对象的this隐式调用
//为什么是泛型的扩展函数?

//匿名函数,也可以是扩展函数
//普通的匿名函数
//() -> Unit
//匿名函数内部this指向一个File对象,隐式调用
//File.() -> Unit

/*public inline fun <T> T.apply(block: T.() -> Unit): T {
    block()
    return this
}*/

public inline fun File.apply(block: File.() -> Unit): File {
    block()
    return this
}

fun main() {
    val file = File("xx").apply {
        setReadable(true)
    }

    //这里分解一下
    //1.定义扩展函数
    fun File.ext(): Unit {
        setReadable(true)
    }
    //2.给block变量赋值
    val block = File::ext
    //3.传入apply函数
    File("xx").apply { block }

}

DSL

使 用 这 样 的 编 程 范 式 , 就 可 以 写 出 业 界 知 名 的 “ 领 域 特 定 语 言 " ( DSL) , 一 种 API 编 程 范 式 , 接 收 者 的 函 数 和 特 , 以 便 于 使 用 你 定 义 的 mbda 表 达 式 来 读 取 和 配 置 它 们 。

import java.io.File

//扩展函数
fun String.addExt() = "!".repeat(count())

//泛型的扩展函数
fun <T> T.easyPrint(): Unit = println(this)

//为什么要传入扩展函数(泛型),而不是一个普通的匿名函数?
//T.() -> Unit
//扩展函数里自带了接收者对象的this隐式调用
//为什么是泛型的扩展函数?

//匿名函数,也可以是扩展函数
//普通的匿名函数
//() -> Unit
//匿名函数内部this指向一个File对象,隐式调用
//File.() -> Unit

/*public inline fun <T> T.apply(block: T.() -> Unit): T {
    block()
    return this
}*/

public inline fun File.apply(block: File.() -> Unit): File {
    block()
    return this
}

fun main() {
    val file = File("xx").apply {
        setReadable(true)
    }

    //这里分解一下
    //1.定义扩展函数
    fun File.ext(): Unit {
        setReadable(true)
    }
    //2.给block变量赋值
    val block = File::ext
    //3.传入apply函数
    File("xx").apply { block }

}


函数类别

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

变换函数map

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

fun main() {
    val animals = listOf("zebra", "giraffe", "elephant", "rat")
    val babies = animals
        .map { animal -> "A baby $animal"}
        .map { baby -> "$baby,with the cutest little tail ever!" }
    println(animals)
    println(babies)

    val animalsLength = animals.map { it.length }
    println(animalsLength)

    //List<String>  List<List<String>>
}

变换函数flatMap

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

fun main() {
    val result = listOf(listOf(1,2,3), listOf(4,5,6)).flatMap { it }
    println(result)
}

过滤函数filter

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

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

fun main() {

    val result = listOf("Jack","Jimmy","Rose","Tom")
        .filter { it.contains("J") }

    println(result)


    val items = listOf(
        listOf("red apple", "green apple", "blue apple"),
        listOf("red fish", "blue fish"),
        listOf("yellow banana", "teal banana")
    )

    val redItems = items.flatMap { it.filter { it.contains("red") } }
    println(redItems)

}

组合使用filter和map找素数

 找 素 数 , 除 了 1 和 它 本 身 , 不 能 被 任 何 数 整 除 的 数 。 仅 使 用 了 几 个 简 单 函 数 , 我 们 就 解 决 了 找 素 数 这 个 比 较 复 杂 的 问 题 , 这 就 是 函 数 式 编 程 的 独 特 魅 力 : 每 个 函 数 做 一 点 , 组 合 起 来 就 能 干 大 事 。

fun main() {

    //除了1和它本身,不能被任何数整除的数
    //取模等于0,说明能够整除,如果没有一个是等于0的,说明是素数
    val numbers = listOf(7, 4, 8, 4, 3, 22, 18, 11)
    val primes = numbers.filter { number ->
        (2 until number).map { number % it }
            .none { it == 0 }
    }
    println(primes)
}

合并函数zip

合并函数folder

为什么要使用函数式编程

序列    使用序列查找素数

互操作性与可空性

类型映射

属性访问

@JvmName

 @JvmField

@JvmOverloads

@JvmStatic

@Throws

函数类型操作

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值