(一)、kotlin学习——基础篇

1、声明,变量:var,常量:val

        默认会自动根据上下文环境推导(隐式)变量常量类型(推荐不写类型);

        例如声明一个Int类型的变量a,默认值是1,可以是(推荐不带分号结尾,除非多条语句在同一行)

var a = 1

        也可以是(可以看到声明变量的方式是在变量名后,冒号+类型,推荐中间加一个空格)

var a: Int = 1

        (var声明变量的方式和Javascript的相同,但不同的地方是,kotlin声明的变量,会自动隐式或者显式定义类型,而且不能赋予其它类型的值的,否则会报编译错误。例如对上面的变量a,在声明之后赋值true,

a = true

这个时候就会报编译错误。而Javascript的数据类型是弱类型,也就是你给它什么值,它就是什么类型。)

        val声明的是运行期常量,是在运行时初始化的(类似Java中的final);

        const val声音的是编译器常量,是在编译时初始化的,只能用在顶层常量的声明,且只能是String或基本数据类型的声明(类似Java中的public final static)。

2、8种基本数据类型

        整数类型:Byte、Short、Int、Long

        浮点类型:Float、Double(浮点类型默认)

        字符类型:Char

        布尔类型:Boolean

        (Java的基本数据类型则是首字母小写)

3、特殊的运算符(部分还没学到,学到了再补充)

        1)、可空类型“?”(值可以为null,避免了Java中的空指针异常):

var a: Int = 1
a = null // 编译错误

var b: Int? = 1
b = null // b = null

        2)、安全调用运算符“?.”(值如果为null,则不执行后面的方法):

var a: Int? = 1
println(a?.plus(100)) // 101

a = null
println(a.plus(100)) // a = null

       3)、非空断言“!!.”(确定值是不为空的,如果值是空的,则会抛出异常):

var a: Int? = 1
println(a!!.plus(100)) // 101

a = null
println(a!!.plus(100)) // 抛出空指针异常

       4)、Elvis(取自猫王的名字,kotlin这是一座岛的名字,有点意思)运算符“?:”(类似三目运算符 “条件 ? 条件成立执行的代码块 : 条件不成立执行的代码块”):

var a: Int? = 1
println(a?.plus(100) ?: 0) // 101

a = null
println(a?.plus(100) ?: 0) // 0

4、字符串

       普通字符串,用英文双引号引起来(""),一般的转义字符在打印会后都会被解析对应的表现;

       原始字符串,用英文三个双引号引起来("""  """),原始字符串会原样打印出引号内的内容,即它的输入是怎么样的,输出就是怎么样的;

       不可变字符串,默认的字符串类型都是不可变字符串String,任何改变字符串的操作,都会创建新的字符串对象。

       可变字符串,通过StringBuilder创建,任何改变字符串的操作,都不会创建新的字符串对象。

        字符串模板,一种:$变量或常量,另一种:${表达式} // 这里表达式指任意表达式或者变量、常量(是否使用第一种,取决于变量后面的字符与变量拼接起来是否有意义):

var a = "变量a"
println("我拿到的是$a") // 我拿到的是变量a

var b = 1
var c = 2
println("b + c = ${b + c}") // b + c = 3
println("b + c = $b + $c") // b + c = 1 + 2

5、when多分支结构(类似于其它语言的switch,但用法更自由):

        1)、比较的case可以是整数、浮点数、字符、字符串等任意可以比较的类型表达式,而且比较的数据,可以是离散的或者连续的范围;

        2)、每个分支不需要加break,分支执行完直接退出when;

        3)、可以将分支的结果赋值给变量或者其它表达式。

var good = 2
when (good) {
    1 -> println("好")
    2 -> println("很好")
    3 -> println("非常好")
}
// 很好
fun cases(obj: Any) {
    when(obj) {
        1           ->  println("第一项")
        "hello"     ->  println("这个是字符串hello")
        is Long     ->  println("这是一个Long类型的数据")
        !is String  ->  println("这不是String类型的数据")
        else        -> println("else类似于Java中的default")
    }
}
cases(1) // 第一项
cases("light") // else类似于Java中的default
cases(1.0) //这不是String类型的数据

6、跳转语句

        break:不带标签,同其它语言一样

        break@label:带标签,跳出多层(也包括单层)循环中,某个循环的前面指定了“label@”;

        continue:不带标签,同其它语言一样;

        continue@label:带标签,在多层(也包括单层)循环中,继续某个循环,该循环的前面指定了“label@”;

        (标签名只要不是关键字,跟起变量名差不多)

label1@ for (i in 0..5) {
    if (i == 3)
        break  // 普通的break(不带标签)
     for (j in 6..10) {
         println("$i    $j")
        if (j == 8)
            break@label1 // 带标签的break
    }
}

label2@ for (i in 0..5) {
    if (i == 3)
        continue  // 普通的continue(不带标签)
    for (j in 6..10) {
        println("$i    $j")
        if (j == 8)
            continue@label2 // 带标签的break
    }
}

7、区间

        上述第6点的“0..5”->“[0, 1, 2, 3, 4, 5]”和“6..10”->“[6, 7, 8, 9, 10]”都是区间,且指的是两变闭合区间。还有一种半闭半开区间“0 until 5”-> “[0, 1, 2, 3, 4]”。

        通过关键字“in”或者“!in”,可以判断一个数值是否在集合或者数组中(有点类似sql中的筛选“in”或者“not in”):

println(2 in 0..5) // true
println(2 !in 0..5) // false

8、函数(其它语言叫做方法,其实两者概念上差不多,这里及后面提到的都以函数为准,毕竟fun开头,有种顾名思义的感觉)

        这里引用《kotlin 从小白到大牛》的解释吧

/**
 * 语法格式:
 * fun 函数名(参数列表) : 返回值类型{
 *     函数体
 *     return 返回值
 * }
 * 在kotlin中声明函数时,关键字是fun,函数名需要符合标识符命名规范;
 * 多个参数列表之间可以用逗号(,)分隔,当然也可以没有参数。
 * 在参数列表后“: 返回值类型”指明函数的返回值类型,如果函数没有需要
 * 返回的数据,则“: 返回值类型”部分可以省略。对应地,如果函数有返回
 * 数据,就需要在函数体最后使用return语句将计算的数据返回;如果没有返
 * 回数据,则函数体中可以省略return语句。
 */
fun rectArea(width: Double, height: Double): Double {
    return width * height
}

        1)、返回特殊数据

        类似于Java中的void,kotlin对应的是Unit,当然,在kotlin中没有返回数据,是可以不写Unit的;

        kotlin还提供一种只会抛出异常的返回值类型Nothing,严格来说,该返回值类型是要让函数没有返回值,而直接抛出异常;(目前没用到,用到再来补充吧)

        2)、可变参数

        可变参数是通过关键字“vararg”来声明的。例如:“vararg  a: Double”表示声明了可变长度的Double类型的数组a。(注:将数据名传进来的时候,可以通过在数据名前加“*”号,将数据进行展开)

        3)、函数的内部也可以定义函数,称为局部函数

        4)、匿名函数

        即不需要函数名,需要有参数列表和返回类型声明,且必须包含return语句。

9、类

        1)、属性

        属性本身不存储数据,数据存储在支持字段中,如果需要,也可以直接在后面直接重写属性访问器,即setter(只读属性没有setter访问器)和getter方法;支持字段只能在访问器中用,通过field变量访问。

 

class Employee {
    var no: Int = 0 // 员工编号属性
    var job: String? = null // 工作属性
    var firstName: String = "Tony"
    var lastName: String = "Guan"

    // 定义一个属性,并重写getter和setter访问器,只读的属性(即val定义)没有setter访问器
    // 属性访问器紧跟在属性后面
    // 这是一个没有进行初始化的属性,它没有支持字段field,因为它没有做初始化,它的值来自于getter访问器中计算来的
    var fullName: String
        get() {
            return firstName + "." + lastName
        }
        set(value) {
            val name = value.split(".")
            firstName = name[0]
            lastName = name[1]
        }

    // 这是一个初始化为0.0的属性,且只重写了setter访问器,通过field访问值
    var salary: Double = 0.0 // 薪资
        set(value) {
            if (value >= 0.0)
                field = value
        }

}

        2)、延迟初始化属性

        一般用在类里面初始化类的情况,假设类A中有一个属性,该属性也是一个对象B,那么就需要进行初始化,当初始化A的时候,就会自动初始化B,但有时候我们希望在用到的时候才初始化B,这个时候,我们可以在A中定义属性对象B的时候,可以在var/val前面添加关键字“lateinit”,这样,就可以在我们需要的时候才初始化B,(注:不能是可空类型,只能使用var声明,且lateinit放在var前面)例如:

class B {
    var depar: String = "Main"
    var no: Int = 1
}

class A {
    // 一般情况下
    var b1 = B()

    // 添加延迟属性
    lateinit var b2: B
}

fun main(args: Array<String>) {
    var a = A()
    // 一般情况下
    println(a.b1.depar) // Main

    // 延迟属性
    a.b2 = B()
    println(a.b2.depar) // Main
}

        3)、委托属性???使用关键字“by”,看不懂,学完回过来看吧。

        4)、惰性加载属性(委托属性的一种)

        有点类似延迟加载属性,只有第一次访问该属性时才进行初始化,(注,只能是val声明,在类型后面通过关键字“by”和函数名lazy,以及尾随的Lambda表达式组成)具体看例子吧:

open class Employee {
    var no: Int = 0
    var firstName: String = "Tony"
    var lastName: String = "Guan"

    // 注意这里,lazy是函数,不是关键字
    val fullName: String by lazy {
        firstName + "." + lastName
    }
}

fun main(args: Array<String>) {
    val emp = Employee()
    println(emp.fullName) // Tony.Guan
}

        5)、可观察属性(委托属性的一种)

        同样,和委托属性用到了Delegates看不太懂,未免浪费时间,先跳过。

10、扩展

        kotlin可以扩展原始类型的函数和属性,原始类型也叫做“接收类型”,“接收类型”包括基本数据类型和引用类型。

        1)、扩展函数:不过是在函数名前面加“接收类型.”而已;

        2)、扩展属性:类似扩展函数,在属性名前加“接收类型”,同样“接收类型”包括基本数据类型和引用类型。扩展属性没有支持字段field,所以不能初始化,(注:由于不能初始化,对应的val声明的属性,可以只重写getter访问器,但是var声明的属性,需要重写getter和setter访问器);

        3)、如果类中已经有对应的函数(这里指函数名和参数列表都相同的情况)和属性了,那么优先调用的是接收类型的函数和属性 

// 注意这里this的作用域是接收类型

// 这里扩展基本数据类型Double的函数,函数名为multi,具体使用看main函数
fun Double.multi(num: Double): Double {
    return this * num
}

class Arith {
    var a: Double = 1.2
    var b: String = "这是属性b"
    var d: String  = "这是属性d"
}
// 扩展类Arith的函数,函数名也为multi,具体使用看main函数
fun Arith.multi(num: Double): Double {
    return this.a * num
}
// 扩展Arith的属性,属性名c,具体使用看main函数
var Arith.c: String
    get() {
        return "这是属性c"
    }
    set(value) {
        println(value)
        // println(field) // 编译错误
    }

var Arith.d: String
    get() {
        return "这是扩展属性d"
    }
    set(value) {
        println(value)
    }

val Int.msg: String
    get() {
        return "这是基本数据类型Int的扩展属性"
    }

fun main(args: Array<String>) {
    val m1 = 1.2.multi(10.0)
    println("m1=$m1") // m1=12

    val arith = Arith()
    val m2 = arith.multi(20.0)
    println("m2=$m2") // m2=24.0

    val m3 = arith.a.multi(10.0)
    println("m3-$m3") // m3=12

    val m4 = arith.c
    println("m4=$m4") // m4=这是属性c

    val m5 = arith.d
    println("m5=$m5") // m5=这是属性d

    val m6 = 2.msg
    println("m6=$m6") // m6=这是基本数据类型Int的扩展属性
}

11、中缀运算符

          定义中缀运算符,就是声明带关键字“infix”的函数,且函数只能有一个参数,只能是成员函数或扩展函数,作用类似于将这种形式“a.方法名(b)”转换成像自然语言的表示“a 方法名 b”:

// 中缀表达式的定义
infix fun Int.multi(num: Int): Int = this * num

fun main(args: Array<String>) {
    // 一般扩展函数写法
    println(2.multi(4)) // 8
    
    // 中缀表达式的写法
    println(2 multi 4) // 8
}

12、构造函数

        1)主构造函数:紧跟在类名后面,通过关键字 “constructor”创建,主构造函数如果没有初始化属性,则需要在类体内,通过 “init{}” 代码块进行初始化,主构造函数的关键字 “constructor” 在没有注解或可见性修饰符时可以省略:

// 默认下,一个非抽象类中,如果看不到任何构造函数,则会自动生成一个默认,无参的public的主构造函数
// 例如一般常用的写法
class User {
    var username: String? = null
    var password: String? = null
}
// 调用
val user = User()

// 一般的主构造函数
// 在类名后面加constructor,在类里面的函数init{}里面进行初始化
class Rectangle constructor (w: Int, h: Int) {
    var width: Int
    var height: Int
    var area: Int
    init { // 构造函数初始化代码块
        width = w
        height = h
        area = w * h
    }
}

// 如果主构造函数没有注解(Annotation)或可见性修饰符,constructor才可以省略
// 将类属性和构造函数的参数合并(此时需要用val或var声明),并且此时将关键字constructor省略也是可以的
class Rectangle21 (var width: Int, var height: Int) {
    var area: Int
    init {
        area = width * height
    }
}
// 这里的constructor不能省略
class Rectangle22 private constructor(var width: Int, var height: Int) {
    var area: Int
    init {
        area = width * height
    }
}

// 如果所有的属性和构造函数的参数都一样,可以省略类体,同时可以对参数(属性)进行初始化,有点类似函数,或者对构造函数的重载了
class Rectangle3 constructor (var width: Int = 10, var height: Int, var area: Int = width * height)

       2)、次构造函数:写在类体,需要有关键字 “constructor”,且需要调用主构造函数的属性:

// 次构造函数,同Java一样,kotlin也支持多个构造函数,
// 次构造函数也是使用关键字constructor声明,但是写在函数体(这里称类体应该更合适)里面
class Rectangle4 (var width: Int, var height: Int) {
    var area: Int
    init {
        area = width * height
    }

    // 次构造函数需要调用主构造函数初始化部分属性
    // 这里this(width, height)表示调用当前对象的主构造函数
    constructor (width: Int, height: Int, area: Int): this(width, height) {
        this.area = area
    }

    // 这里this(200, 100)表示调用当前对象的主构造函数
    constructor(area: Int): this(200, 100) {
        this.area = area
    }
}

 

   13、可见性修饰符

        kotlin中的可见性范围主要有三个:模块、源文件和类,其中,源文件和类主要是对应了Java中的private、protected和public,而模块可见性,则是一个比较特殊的存在;在kotlin中,使用关键字 “internal”, 一个项目是一个模块(module),或者可以在项目中新建模块,同一个Maven项目、一个Gradle源代码也是一个模块。例如下面我的学习工程中,本身 “kotlinModule”是一个模块,而它下面还有两个子模块 “InheritAndPolymorphism” 和 “module1” ,

在 “module1” 模块中,定义了一个Person类,其中分别定义有private、public和internal三种可见性修饰符修饰的属性,Test类则对Person类进行调用,项目中的Test类也对Person类进行了调用:

// module1
package pakge

import java.util.*

// 默认public,可省略,
// private,类内部可见,
// internal,模块内可见,
// protected,子类可见
class Person(val name: String,
             private val birthDate: Date,
             internal val age: Int) {
    internal fun display() {
        println("[name: $name, birthDate: $birthDate, age: $age]")
    }
}
// module1
package pub

import  pakge.Person
import java.util.Date

fun main(args: Array<String>) {
    // 模块可见性internal
    val now = Date()
    val person = Person("Tony", now, 18)

    println(person.name)
    println(person.age)
    // println(person.birthDate) // 不能访问birthDate属性
    person.display()
}
// kotlinModule
package pub

import pakge.*
import java.util.*

fun main(args: Array<String>) {
    // 公共可见性public
    val now = Date()
    val person = Person("Tony", now , 18)

    println(person.name)

    // println(person.birthDate) // 不能访问birthDate属性
    // println(person.age) // 不能访问age属性
    // person.display() // 不能访问display()函数
}

14、数据类

        1)只需要一些属性,在各个组件容器内传递数据用;重写了Kotlin的根类Any的三个函数equals、hasCode、toString,并提供了copy函数,用于复制数据类,且复制的时候能够修改对应的属性值,如果没有修改属性,则equals下应该是返回true的;声明的时候在class前面加关键字 “data“, 并且各个属性只能通过val或var声明,不能省略:

// kotlinModule
package pakge

// 数据类
data class User (val name: String, var password: String)
// kotlinModule
package pub

import pakge.*
import java.util.*

fun main(args: Array<String>) {

    // 数据类: 在普通类的前面加data关键字,并且val/var不能省略
    // 默认重写了“==”(即equals()、toString()、hashCode()),并且提供了copy()方法
    val user1 = User("Tony", "123")
    val user2 = User("Tony", "123")
    val user3 = user1.copy(name = "Tom") // 这时候已经改变了属性name,比较是否相等时false
    val user4 = user1.copy() // 完全复制,比较时true

    println(user1 == user2)    // true
    println(user1.toString()) // User(name=Tony, password=123)
    println(user2.toString()) // User(name=Tony, password=123)
    println(user1.hashCode()) // 81040716
    println(user2.hashCode()) // 81040716

}

      2)解构数据类:个人理解就是一次性声明多个变量,变量的值来自于数据类

// kotlinModule

package pub

import pakge.*
import java.util.*

fun main(args: Array<String>) {

    // 解构数据类
    // 将数据类的内部属性赋值给不同的变量,“_”表示不解构对应位置的属性
    val user1 = User("Tony", "123")
    // 解构,var(User数据类内的对应的属性变量名)
    var(name1, pwd1) = user1
    println(name1) // Tony
    println(pwd1) // 123
    // val(User数据类内的对应的属性变量名)
    val(name2, _) = user1
    println(name2) // Tony

}

15、枚举类

       1)同Java的定义差不多,都是一组常量组成,在kotling中叫做 “枚举常量列表”;枚举类的每一个常量是枚举类的一个实例,且是私有的,是单例的,它可以有自己的构造函数,通过构造函数初始化属性;如果枚举类中还含有其它属性或函数,则第一行的枚举常量列表需要以分号结尾:

// kotlinModule
package pakge

// 枚举类:“枚举常量列表”是枚举的核心,由一组相关常量组成,每一个常量就是枚举类的一个“实例”
// 无自定义构造函数形式,注意枚举常量列表没有带分号结尾
enum class WeekDays1 {
    // 枚举常量列表
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY
}

// 有构造函数,且初始化属性,注意这里是带分号结尾的
enum class WeekDays2(private val wname: String,
                     private val index: Int) {
    // 枚举常量列表
    MONDAY("星期一", 0),
    TUESDAY("星期二", 1),
    WEDNESDAY("星期三", 2),
    THURSDAY("星期四", 3),
    FRIDAY("星期五", 4);

    // 重写父类中的toString()函数
    override fun toString(): String {
        return "$wname - $index"
    }
}

         2)常用属性和函数:ordinal属性(返回枚举常量序号,从0开始),values()函数(返回一个包含全部枚举常量的数组),valueOf(value:String)函数(value是类中对应枚举常量字符串,返回一个字符串对应的实例):

// kotlinModule
package pub

import pakge.*
import java.util.*

fun main(args: Array<String>) {
    
    // 枚举类:
    val day1 = WeekDays1.FRIDAY
    // day1,调用toString(),对比下面的day2,重写的toString()
    println(day1) // FRIDAY
    when(day1) {
        WeekDays1.MONDAY -> println("星期一")
        WeekDays1.TUESDAY -> println("星期二")
        WeekDays1.WEDNESDAY -> println("星期三")
        WeekDays1.THURSDAY -> println("星期四")
        else -> println("星期五")
    } // 星期五


    // 在枚举类中,如果有其它属性或函数等成员时,枚举常量列表必须是类体中的第一行,而且语句结束一定不能省略分号
    // 枚举类中构造函数只能是私有的,这也说明了枚举类对象不允许在外部通过构造函数创建。枚举类的构造函数只是为了
    // 在枚举类内部创建枚举常量使用,所以一旦添加了有参数的构造函数,那么“枚举常量列表”也需要修改,每一个枚举
    // 常量就是一个实例,都会调用构造函数,其中("星期一",0)就是调用构造函数
    val day2 = WeekDays2.FRIDAY
    // 打印day2,默认调用枚举类中toString()函数
    println(day2) // 星期五 - 4

    // 枚举的属性和函数
    // ordinal属性,返回枚举常量的索引,默认从0开始
    // values()函数,返回一个包含全部枚举常量的数组
    // valueOf(value: String)函数,value是枚举常量对应的字符串,返回一个包含枚举类型实例
    val allValues = WeekDays2.values()
    for (value in allValues) {
        println("${value.ordinal} - $value")
    }
    /**
     *  0 - 星期一 - 0
        1 - 星期二 - 1
        2 - 星期三 - 2
        3 - 星期四 - 3
        4 - 星期五 - 4
     */

    val day3 = WeekDays2.FRIDAY
    val day4 = WeekDays2.valueOf("FRIDAY")
    // 枚举类中每个枚举常量无论何时都只有一个实例,即单例的
    println(day3 === WeekDays2.FRIDAY) // true
    println(day4 == WeekDays2.FRIDAY) // true
    println(day3 == day4) // true

}

16、继承与构造函数重写

        与Java一样,kotlin只支持单继承,但可以实现多接口。如果在类的声明中,如果没有明确指明父类,则默认父类是Any类,kotlin.Any类是kotlin的根类。kotlin的继承中,父类需在class前添加关键字声明“open”,表示可以被继承。

         子类继承父类时,当子类声明主构造函数,那么子类的次构造函数不能直接调用父类构造函数,只能调用自己的主构造函数。

         子类重写成员属性和函数(函数名,参数列表,返回值类型都相同),父类的成员属性和函数必须添加关键字 open,而子类则需要添加关键字 override,表示重写,子类调用父类,一样通过 “super.属性/函数” 调用。

// InheritAndPolymorphism
package pakge

import java.util.*

// open 标识可以被继承
// Person1继承Any根类,且具有默认构造函数
open class Person1 {
    val name: String? = null
    val age: Int = 0
    val birthDate: Date? = null

    open val info: String
        get() = ("Person[name=$name, age=$age, birthDate=$birthDate]")
}

// kotlin中继承只能是单继承,但是可以实现多个接口
class Student1: Person1() {
    val school: String? = null
    override val info: String
        get() = ("Person [name=$name,age=$age,birthDate=$birthDate]")
}

// 具有主次构造函数的Person2类
open class Person2 (val name: String, val age: Int, val birthDate: Date) {
    constructor(name: String, age: Int): this(name, age, Date())

    override fun toString(): String {
        return ("Person [name=$name, age=$age, birthDate=$birthDate]")
    }
}
/**
 * 子类继承父类时,子类中一旦声明了主构造函数,那么子类的次构造函数不能直接调用
 * 父类的构造函数,只能调用自己的主构造函数。
 */
// Person2(name, age, birthDate)表达式是调用父类构造函数
// this(name, age, Date(), school)表达式是调用自己的主构造函数进行初始化
// this(name, 18, school)表达式是调用前一个次构造函数来进行初始化,
class Student2_1(name: String,
                age: Int,
                birthDate: Date,
                val school: String): Person2(name, age, birthDate) { // 主构造函数

    constructor(name: String,
                age: Int,
                school: String): this(name, age, Date(), school) // 次构造函数
    // this(name, age, Date(), school)换成super(name, age, Date())会发生编译错误,
    // super(name, age, Date())是次构造函数调用父类构造函数

    constructor(name: String,
                school: String): this(name, 18, school) // 次构造函数
    // this(name, 18, school)换成super(name, 18, school)会发生编译错误,
    // super(name, 18, school)是次构造函数调用父类构造函数

}
// 在子类中可以不声明主构造函数,可以声明多个次构造函数,且子类的次构造函数能够直接调用父类构造函数
// super(name, age, birthDate)表达式调用父类的构造函数
// this(name, age, Date(), school)表达式调用前一个次构造函数
class Student2_2: Person2 {
    private var school: String? = null

    constructor(name: String,
                age: Int,
                birthDate: Date,
                school: String): super(name, age, birthDate) {
        this.school = school
    }

    constructor(name: String,
                age: Int,
                school: String): this(name, age, Date(), school) {
        this.school = school
    }
}
// 使用参数默认值调用构造函数,构成了构造函数的重载关系,语法上的重载
class Student2_3: Person2 {
    private var school: String? = null

    // 通过参数默认值的方式,这里实际上相当于提供了3个构造函数,具体看测试类中的调用,
    // 但是实际上,通过属性的默认值,应该是相当于声明了1+多的类型,即没有默认值的是一
    // 个,而分别于默认参数的可以有很多种组合
    constructor(name: String,
                age: Int = 18,
                birthDate: Date = Date(),
                school: String): super(name, age, birthDate) {
        this.school = school
    }
}

// 重写成员属性和函数(函数名,参数列表,返回值类型都相同)
// 父类的成员属性和函数必须添加关键字 open,而子类则需要添加关键字 override,表示重写,
// 子类调用父类,一样通过 super.属性/函数 调用
// 重写原则:可见性在重写后应该等于大于父类的
// 父类 protected -> 子类 public protected
// 父类 internal -> 子类 public internal
// 父类 public -> 子类 public
open class ParentClass {
    open var x: Int = 0

    open protected fun setValue() {
        x = 10
    }
}
class SubClass: ParentClass() {
    override var x: Int = 0

    public override fun setValue() {
        // 这里x是子类SubClass的
        x = 20
        // 调用父类的setValue()函数
        super.setValue()
    }

    fun display() {
        println("x = $x")
        // 调用父类的属性 x
        println("super.x = " + super.x)
    }
}


/*************************华丽丽的分割线****************************/
package pub

import pakge.Student2_3
import java.util.*

fun main(args: Array<String>) {

    // 参数默认值调用构造函数
    val stu1 = Student2_3("Tony", 20, Date(), "清华大学")
    val stu2 = Student2_3("Tony", birthDate = Date(98765432), school = "清华大学")
    val stu3 = Student2_3("Tony", school = "清华大学")

}

17、特殊类、object关键字

         1)、嵌套类,写法和普通类一样,需要注意的是,嵌套类不能调用外部类的属性和函数,但外部类可以访问嵌套类;引用嵌套类时,不需要实例化外部类“外部类.嵌套类()”

// kotlinModule

package pakge

// 嵌套类:在类的内部写类,写法和外部类一样
// 外部类View
class View {

    // 外部类属性
    val x = 20

    // 嵌套类:不能引用外部类及其成员,但是外部类可以访问嵌套类
    class Button {
        // 嵌套类函数
        fun onClick() {
            println("onClick...")
            // 不能访问外部类属性
            // println(x) // 编译错误
        }
    }

    // 测试调用嵌套类
    fun test1() {
        val btn = Button()
        btn.onClick()
    }

}


package pub

import pakge.*
import java.util.*

fun main(args: Array<String>) {
		// 嵌套类
    // 引用嵌套类时,不需要实例化外部类,外部类.嵌套类()
    val button = View.Button()
    button.onClick() // onClick...

    // 测试调用嵌套类
    val view1 = View()
    view1.test1() // onClick...
}

             2)、内部类,特殊的嵌套类,在class前面使用关键字“inner”来声明,内部类中的this指当前内部类对象;要引用外部类对象需要使用“this@外部类类名”;另外,在外部类和内部类成员命名没有冲突情况下,在引用外部类成员时,可以不用加“this@外部类类名”;引用内部类时,需要先实例化外部类“外部类().嵌套类()”:

// kotlinModule

package pakge

// 嵌套类:在类的内部写类,写法和外部类一样
// 外部类View
class View {

    // 外部类属性
    val x = 20

    // 外部类函数
    fun printOuter() {
        println("调用外部函数...")
    }

    // 测试调用内部类
    fun test2() {
        val inner = Inner()
        inner.display()
    }

    // 内部类是一种特殊的嵌套类,嵌套类不能访问外部类及其成员,而内部类可以
    // 在class前面使用关键字inner来声明内部类,内部类中的this指当前内部类对
    // 象;要引用外部类对象需要使用“this@外部类类名”;另外,在外部类和内部
    // 类成员命名没有冲突情况下,在引用外部类成员时,可以不用加“this@外部类类名”
    inner class Inner {
        // 内部类属性
        private val x = 5

        // 内部类函数
        fun display() {
            // 访问外部类属性x
            println("外部类属性 x = " + this@View.x)

            // 访问内部类的属性x
            println("内部类属性 x = " + this.x)

            // 内部类的属性x
            println("内部类属性 x = " + x)

            // 调用外部类函数
            this@View.printOuter()
            // 没有命名冲突
            printOuter()
        }
    }

}

package pub

import pakge.*
import java.util.*

fun main(args: Array<String>) {

 	// 内部类
    val view2 = View()
    view2.test2()
    /**
     *  外部类属性 x = 20
        内部类属性 x = 5
        内部类属性 x = 5
        调用外部函数...
        调用外部函数...
     */

    // 直接访问内部类
    // 引用内部类时,需要实例化外部类,外部类().嵌套类()
    val inner = View().Inner()
    inner.display()
    /**
     *  外部类属性 x = 20
        内部类属性 x = 5
        内部类属性 x = 5
        调用外部函数...
        调用外部函数...
     */

}

            3)、密封类,是一个抽象类,子类个数有限,与枚举类类似,区别是枚举类的每个常量实例只有一个,而密封类的子类实例可以有多个;声明时在class前加“sealed”关键字;因为本身是抽象类,所以不需要abstract修饰,一定是open,且不能实例化:

// InheritAndPolymorphism
sealed class Result
class Success(val message: String): Result()
class Failure(val error: String): Result()

fun onResult(result: Result) = when (result) {
    // 这里进行了向上转型,子类Success或者Failure转父类Result
    is Success -> println("${result}输出成功消息:${result.message}")
    is Failure -> println("${result}输出成功消息:${result.error}")
}


package pub

fun main(args: Array<String>) {

    val result1 = Success("数据更新成功")
    onResult(result1) // pub.Success@266474c2输出成功消息:数据更新成功
    val result2 = Failure("数据更新失败")
    onResult(result2) // pub.Failure@6f94fa3e输出成功消息:数据更新失败
}

            4)、object关键字,主要用于声明一个类的同时创建这个类的对象,三个方面的应用:对象表达式、对象声明和伴生对象;

// kotlinModule
package pub

import pakge.*
import java.util.*

// 1)、对象表达式用:
class WebView {
    fun handler (listener: OnClickListener) {
        listener.onClick()
    }
}
interface OnClickListener {
    fun onClick()
}
open class Person(val name: String, val age: Int)


// 2)、对象声明用:
interface DAOInterface {
    fun create(): Int
    fun findAll(): Array<Any>?
}
// 对象声明,看起来像是Java中类实现接口:
object UserDAO: DAOInterface {
    private var datas: Array<Any>? = null

    override fun findAll(): Array<Any>? {
        return datas
    }

    override fun create(): Int {
        // 对象声明不能嵌套在其他函数中,但是可以嵌套在其他类中或其他对象声明中
//        override Singleton {
//            val x = 10
//        }
        return 0
    }
    // 对象声明不能嵌套在其他函数中,但是可以嵌套在其他类中或其他对象声明中
    object Singleton {
        val x = 10
    }
}

// 3)、声明伴生对象用:
class Account {
    var account = 0.0
    var owner: String? = null

    fun messageWith(amt: Double): String {
        // 实例函数可以访问实例属性、实例函数、静态属性和静态函数
        val interest = Account.interestBy(amt)
        return "${owner}的利息是$interest"
    }

    // 声明伴生对象,这里看到,是在对象声明的前面添加companion,且在容器类内部声明
    // 声明的是一个静态的对象
    // 伴生对象函数可以访问伴生对象自己的属性和函数,但不能访问容器类中的成员属性和函数,容器类可以访问伴生对象的函数和属性
    /*companion object {
        // 静态属性
        var interestRate: Double = 0.0
        // 静态函数
        fun interestBy(amt: Double): Double {
            // 静态函数可以访问静态属性和其它静态函数
            return interestRate * amt
        }

        // 静态代码块,初始化函数,在容器类Account第一次被访问时调用,例如上面调用中初始化Account对象
        init {
            println("静态代码块被调用...")
            // 初始化静态属性
            interestRate = 0.0668
        }
    }*/

    // 伴生对象非省略形式,即不省略类名,用于继承类和实现接口
    companion object Factory: Date(), OnClickListener {
        override fun onClick() {
        }

        // 静态属性
        var interestRate: Double = 0.0
        // 静态函数
        fun interestBy(amt: Double): Double {
            // 静态函数可以访问静态属性和其它静态函数
            return interestRate * amt
        }

        // 静态代码块,初始化函数,在容器类Account第一次被访问时调用,例如上面调用中初始化Account对象
        init {
            println("静态代码块被调用...")
            // 初始化静态属性
            interestRate = 0.0668
        }
    }
}

fun main(args: Array<String>) {

    // (1)对象表达式:用来替代Java中的匿名内部类,就是声明一个匿名类的同时,创建其对象
    // 1、这里对象表达式实现的是接口
    // 在对象表达式中,可以访问外部变量
    var i = 10
    val v = WebView()
    v.handler(object: OnClickListener {
        override fun onClick() {
            println("对象表达式作为函数参数...") // 对象表达式作为函数参数...
            println(++i) // 11
        }
    })
    // 2、对象表达式可以继承具体类或抽象类
    // 这里继承Person类和OnClickListener接口
    var person = object: Person("Tony", 18), OnClickListener {
        //重写接口函数
        override fun onClick() {
            println("实现接口onClick函数...")
        }

        override fun toString(): String {
            return "Person[name=$name, age=$age]" // 因为继承了Person类,所以可以直接调用Person类中的name和age属性
        }
    }
    println(person) // Person[name=Tony, age=18]
    // 3、有时候没有具体父类的时候一可以使用对象表达式
    // 无具体父类对象表达式,注意,表达式在这个时候object后面是不带冒号的
    var rectangle = object {
        var width: Int = 200
        var height: Int = 300

        override fun toString(): String {
            return "[width=$width, height=$height]"
        }
    }
    println(rectangle) // [width=200, height=300]

    // (2)对象声明:声明一个单例实例
    // 注意,对象声明一个对象的同时,会实例化该对象,所以下面可以看到UserDAO后面并不需要括号进行实例化
    UserDAO.create()
    var data = UserDAO.findAll()

    // (3)伴生对象:kotlin通过声明伴生对象,实现Java静态成员访问方式,即静态的类及其属性和函数
    val myAccount = Account() // 静态代码块被调用...
    // 访问伴生对象属性,注意,这里直接通过容器类名访问静态属性和函数,也可以同过伴生对象名访问
    // 调用的方式类似调用类中的属性和函数
    println(Account.interestRate) // 0.0668
    println(Account.Factory.interestRate) // 0.0668
    // 访问伴生对象函数
    println(Account.interestBy(1000.0)) // 66.8
    println(Account.Factory.interestBy(1000.0)) // 66.8
    // 伴生对象可以扩展属性和函数,和扩展函数是一样的写法和访问方式
    fun Account.Factory.display() {
        println(interestRate)
    }
    Account.Factory.display() // 0.0668

}

18、多态及特殊运算符

        1)、发生多态的三个前提条件:继承;重写,子类重写了父类的函数;声明对象是父类类型,实例化的是子类。

// InheritAndPolymorphism
package pakge

open class Figure {
    open fun onDraw() {
        println("draw Figure")
    }
}

class Ellipse: Figure() {
    override fun onDraw() {
        println("draw Ellipse")
    }
}

class Triangle: Figure() {
    override fun onDraw() {
        println("draw Triangle")
    }
}


package pub

import pakge.Ellipse
import pakge.Figure
import pakge.Triangle

fun main(args: Array<String>) {

    // 多态
    // 三个条件:继承;重写,子类重写了父类的函数;声明对象是父类类型,实例化的是子类。
    // f1类型和实例都是父类,没有发生多态
    var f1 = Figure()
    f1.onDraw() // draw Figure

    // f2、f3是父类类型,指向子类实例,发生多态
    var f2: Figure = Ellipse()
    f2.onDraw() // draw Ellipse
    var f3: Figure = Triangle()
    f3.onDraw() // draw Triangle

    // f4类型和实例都是子类,没有发生多态
    var f4 = Triangle()
    f4.onDraw() // draw Triangle
}

            2)、特殊运算符——类型检查运算符 “is” 和 “!is”,类似Java的instanceof或Javascript中的typeof

    println("(f4 is Figure)=${f4 is Figure},(f4 !is Figure)=${f4 !is Figure}") // (f4 is Figure)=true,(f4 !is Figure)=false
    println("(f4 is Triangle)=${f4 is Triangle},(f4 !is Triangle)=${f4 !is Triangle}") // (f4 is Triangle)=true,(f4 !is Triangle)=false
    println("(1 is Int)=${1 is Int}") // (1 is Int)=true

            3)、特殊运算符——类型转换运算符 “as” 和 “as?”。数值类型可以相互转换,引用类型也可以进行转换,但并不是所有的引用类型都能互相转换,只有属于同一棵继承层次树(变量的类型是父类和实例对象是子类,或者变量类型和实例类型都是子类)中的引用类型才可以转换。

       引用类型转换有两个方向,向下转型——将父类引用类型变量转换为子类型,向上转型——子类引用类型变量转换为父类类型。向上转型是自动的,而向下转型则需要使用运算符 “as” 和 “as?”,其中,使用“as”在类型不兼容时会抛出类型转换错误异常,而 “as?”在类型不兼容则返回空值,不会抛出异常。

 // 向下转型,f3是父类Figure类型,指向的是子类实例Triangle,所以可以进行向下转型
    var f51 = f3 as Triangle
    f51.onDraw() // draw Triangle
    // var f52 = f1 as Triangle // f1是父类类型,实例也是父类,不能转成其它类型
    // 向上转型,向上转型是自动的,也可以使用as
    var f6: Figure = f4
    f6.onDraw() // draw Triangle
    var f7 = f4 as Figure
    f7.onDraw() // draw Triangle

19、抽象类

         在kotlin中,具有抽象函数的类成为抽象类。抽象类、抽象函数和抽象属性的修饰符是“abstract”,默认是“open”。抽象函数没有函数体(由子类实现),抽象属性没有初始值、getter和setter访问器。注意,一个成员属性或者函数被声明为抽象的,那么这个类也必须声明为抽象的,而一个抽象类中,可以有0~n个抽象函数或属性,以及0~n个具体函数或属性(有初始值,或者有getter或setter访问器)。抽象类设计的目的就是为了让子类来实现。

// abstractAndInterface
package pakge
// 抽象类
abstract class Figure {
    // 抽象函数
    abstract fun onDraw()
    // 抽象属性
    abstract val name: String
    // 具体属性
    val cname: String = "几何图形"
    // 具体函数
    fun display() {
        println(name)
    }
}

class Ellipse: Figure() {
    override val name: String
        get() = "椭圆形"

    override fun onDraw() {
        println("绘制椭圆形...")
    }
}

class Triangle(override val name: String): Figure() {
    override fun onDraw() {
        println("绘制三角形...")
    }
}

package pub

import pakge.Figure
import pakge.Triangle
import pakge.Ellipse

fun main(args: Array<String>) {

    // f1、f2变量类型是父类Figure,实例是子类Triangle和Ellipse,发生多态
    val f1: Figure = Triangle("三角形")
    f1.onDraw() // 绘制三角形...
    f1.display() // 三角形

    val f2: Figure = Ellipse()
    f2.onDraw() // 绘制椭圆形...
    f2.display() // 椭圆形

}

20、接口

       接口比抽象类更加抽象,但与抽象类相似,区别在于接口不能维持一个对象状态,而抽象类可以,因为维护一个对象状态需要支持字段,而接口中无论是具体属性还是抽象属性,后面都没有支持字段(说明没有初始值,只有getter访问器)。声明接口的关键字是“interface”。kotlin中,类只能单继承,而接口可以多继承,也可以继承接口,也能够作为类型。例如将“19、抽象类”中的抽象类简单修改下就变成了接口了:

// abstractAndInterface
package pakge

interface Figure2 {
    // 抽象函数
    fun onDraw()
    // 抽象属性
    val name: String
    // 具体属性,只有getter访问器
    val cname: String
        get() = "几何图形"
    // 具体函数
    fun display() {
        println(name)
    }
}

interface InterfaceA {
    fun methodA()
    fun methodB()
    fun methodD(): Int {
        return 0
    }
    fun methodE(): String {
        return "这是具体函数methodD"
    }
}
interface InterfaceB {
    fun methodB()
    fun methodC()
}
// 父接口中,函数相同,子类会同时重写所有相同的接口函数
class AB: Any(), InterfaceA, InterfaceB {
    override fun methodA() {}
    override fun methodB() {}
    override fun methodC() {}

    // 子类可以选择性重写具体函数和属性
    // 这里只重写了接口InterfaceA的具体函数
    override fun methodD(): Int {
        return 100
    }
}
// 这里接口InterfaceC继承了接口InterfaceA,并且只重写了methodB函数,由于函数methodB是一个抽象函数,所以子接口的函数methodB最终还是在子类中实现
interface InterfaceC: InterfaceA {
    override fun methodB()
    fun methodC()
}
class ABC: InterfaceC {
    override fun methodA() {}
    override fun methodB() {}
    override fun methodC() {}
}

package pub

import pakge.*

fun main(args: Array<String>) {
    // 接口作为类似抽象类的存在,也可以作为类型
    val abc: InterfaceA = ABC()
    println(abc.methodD()) // 0
}

21、函数式编程(functional programming)

        1)、函数是“一等公民”:是指函数与其它数据类型是一样的,处于平等的地位。函数可以作为其它函数的参数或返回值。

        2)、使用表达式,不用语句:函数式编程关心的输入(参数)和输出(返回值)。在程序中使用表达式可以有返回值,而语句没有,例如:控制结构中的if和when结构都属于表达式。

        3)、高阶函数:函数式编程支持高阶函数,所谓高阶函数就是一个函数可以作为另外一个函数的参数或返回值。

        4)、无副作用:是指函数执行过程会返回一个结果,不会修改外部变量,这就是“纯函数”,同样的输入参数一定会有同样的输出结果。

22、高阶函数

        kotlin中每一个函数都有一个类型,称为“函数类型”,在声明一个函数的时候,“函数类型”也就定了下来,“函数类型”,就是把函数列表中的参数类型保留下来,再加上箭头符号和返回类型,形式:“参数列表中的参数类型 -> 返回类型”:

// HigherOrderFunctionAndLambda
package pub

// 1、函数类型,就是把函数列表中的参数类型保留下来,再加上箭头符号和返回类型,形式:“参数列表中的参数类型 -> 返回类型”
// 函数类型 (Double, Double) -> Double
fun rectangleArea(width: Double, height: Double): Double {
    return width * height
}
// 函数类型 (Double, Double) -> Double
fun triangleArea(bottom: Double, height: Double): Double {
    return 0.5 * bottom * height
}
// 函数类型 () -> Unit
fun sayHello() {
    println("Hello,Kotlin!")
}

fun main(args: Array<String>) {

    // “双冒号加函数名”即引用函数的形式
    val getArea: (Double, Double) -> Double = :: triangleArea
    val area = getArea(50.0, 40.0)
    println(area) // 1000

    val hello: () -> Unit = :: sayHello
    hello() // Hello,Kotlin!
}

               函数字面量(函数类型变量接收的数据)有三种表示方式:

               a)、函数引用。引用一个已经定义好的,有名字的函数。

               b)、匿名函数。没有名字的函数。

               c)、Lambda表达式。也是一种匿名函数。

// HigherOrderFunctionAndLambda
package pub

// 2、函数字面量
// 通过输入一个操作符,返回一个函数类型(Int, Int) -> Int的变量,说明calculate是一个高阶函数
fun calculate(opr: Char): (Int, Int) -> Int {

    fun add(a: Int, b: Int): Int {
        return a + b
    }

    fun sub(a: Int, b: Int): Int {
        return a - b
    }

    val result: (Int, Int) -> Int =
            when (opr) {
                '+' -> ::add // 引用函数名
                '-' -> ::sub // 引用函数名
                '*' -> { // 引用匿名函数
                    fun(a: Int, b: Int): Int {
                        return a * b
                    }
                }
                else -> { a, b -> (a / b) } // 引用Lambda表达式
            }
    return result

}
// 函数作为参数使用
fun getAreaByFunc(funcName: (Double, Double) -> Double,
                  a: Double, b: Double): Double {
    return funcName(a, b)
}

fun main(args: Array<String>) {
    // 2、函数字面量
    // 根据kotlin的智能推断,由于calculate的返回值是一个函数类型,那么c1的数据类型是(Int, Int) -> Int,那么c1可以当函数使用
    val c1 = calculate('-')
    println(c1(8,4)) // 4

    // 调用   函数作为参数的例子
    var result = getAreaByFunc(::triangleArea, 10.0 , 15.0)
    println("底10,高15的三角形面积:$result") // 底10,高15的三角形面积:75.0

    result = getAreaByFunc(::rectangleArea, 10.0, 15.0)
    println("宽10,高15的三角形面积:$result") // 宽10,高15的三角形面积:150.0
}

23、Lambda表达式

           Lambda表达式,是一种匿名函数,运算的结果是一个函数,是高阶函数,从“22、高阶函数”的例子中已经可以看到它的身影了。Lambda表达式的标准语法是“{参数列表  ->  Lambda体}”,其中,Lambda表达式的参数列表和函数的参数列表形式相似,但是Lambda表达式参数列表前后没有小括号。箭头符号将参数列表和Lambda体分开,Lambda表达式不需要声明返回类型。Lambda可以有返回值,如果没有return语句,那么Lambda体的最后一个表达式就是Lambda表达式的返回值,如果有,就是跟在return后面的表达式。Lambda是函数类型的,但是从定义上看不出来其返回类型声明的,因为它可以通过上下文推导出来。

// HigherOrderFunctionAndLambda
package pub

// 3、Lambda表达式
fun calculate2 (opr: Char): (Int, Int) -> Int {
    return when(opr) {
        '+' -> { a, b -> a + b }
        '-' -> { a, b -> a - b }
        '*' -> { a, b -> a * b }
        else -> { a, b -> a / b }
    }
}
/*
* 简写:
* fun calculate2 (opr: Char): (Int, Int) -> Int = when(opr) {
        '+' -> { a, b -> a + b }
        '-' -> { a, b -> a - b }
        '*' -> { a, b -> a * b }
        else -> { a, b -> a / b }
    }
* */
// Lambda表达式作为参数使用
fun calculatePrint(n1: Int,
                   n2: Int,
                   opr: Char,
                   funcName: (Int, Int) -> Int) {
    println("$n1 $opr $n2 = ${funcName(n1, n2)}")
}

fun main(args: Array<String>) {
    // 3、标准Lambda表达式
    val c2_1 = calculate2('+')
    println(c2_1(10, 5)) // 15
    val c2_2 = calculate2('-')
    println(c2_2(10, 5)) // 5
    val c2_3 = calculate2('*')
    println(c2_3(10, 5)) // 50
    val c2_4 = calculate2('/')
    println(c2_4(10, 5)) // 2

    // Lambda表达式作为参数使用
    calculatePrint(10, 5, '+', {a:Int, b: Int -> a + b}) // 10 + 5 = 15
    calculatePrint(10, 5, '-', funcName = {a, b -> a - b}) // 10 - 5 = 5
}

              Lambda表达式可以简化写法,如上的例子中{a:Int, b: Int -> a + b}的简写就是{a, b -> a + b},这时因为Kotlin能够根据上下文环境进行类型的推导,例如,对于{a:Int, b:Int -> a + b}和}{a:Double, b: Double -> a + b}, 都可以统一成使用{a, b -> a + b},让Kotlin进行类型推导,从而简化参数类型的声明。

              Lambda表达式还支持“尾随”的形式简化写法,如果一个函数的最后一个参数是Lambda表达式,那么这个Lambda表达式可以放在函数括号之后:

// HigherOrderFunctionAndLambda
package pub

fun calculatePrint1(funcName: (Int, Int) -> Int) {
    println("${funcName(10, 5)}")
}

fun main(args: Array<String>) {
    // 标准形式
    calculatePrint1({a, b -> a - b}) // 5
    // 尾随Lambda表达式
    calculatePrint1() {a, b -> a - b} // 5
    // 尾随Lambda表达式,如果没有其它参数了,可以去掉小括号
    calculatePrint1 {a, b -> a - b} // 5
}

             如果Lambda表达式的参数只有一个,并且能够根据上下文环境推导出它的数据类型,那么这个参数声明可以省略,在Lambda体中使用隐式参数it替代Lambda表达式的参数:

// HigherOrderFunctionAndLambda
package pub

fun reverseAndPrint(str: String, funcName: (String) -> String) {
    println(funcName(str))
}

fun main(args: Array<String>) {
    // 标准形式
    reverseAndPrint("hello", { s -> s.reversed() }) // olleh
    // 省略参数,使用隐式参数it
    reverseAndPrint("hello", { it.reversed() }) // olleh
    // 唯一参数不能省略,因为result1没有指定类型,Kotlin无法根据上下文推导出来,所以无法使用it
    val result1 = { a: Int -> println(a) }
    result1(5) // 5
    // result2指定了只有一个参数的函数类型,所以可以用it,返回一个函数类型
    val result2: (Int) -> Unit = { println(it) }
    result2(5) // 5
}

               Lambda表达式可以使用return表达式,使程序跳出Lambda表达式体:

// HigherOrderFunctionAndLambda
package pub

fun sum(vararg num: Int): Int {
    var total = 0
    num.forEach {
        // if ( it == 10) return -1 // 终止forEach,不会执行forEach后面的程序,直接跳出sum
        if (it == 10) return@forEach
        total += it
    }
    return total
}

fun main(args: Array<String>) {
    // 如果使用“return -1”,因为forEach后面的程序,所以直接返回了-1
    /*val result3 = sum(1, 2, 10, 3)
    println(result3) // -1*/
    // 如果使用“return@forEach”,则返回的是6
    val result4 = sum(1, 2, 10, 3)
    println(result4) // 6

    val add = labal@ {
        val a = 1
        val b = 2
        return@labal 10
        a + b
    }
    println(add()) // 10
}

24、闭包与捕获变量

        闭包是一种特殊的函数,它可以访问函数体之外的变量(这个过程就叫做捕获变量),这个变量和函数一同存在,即使已经离开了它的原始作用域。这种特殊函数一般是局部函数、匿名函数和Lambda表达式。

// HigherOrderFunctionAndLambda
package pub

// 4、闭包
// 全局变量
var value = 10
// 局部函数例子
fun makeArray(): (Int) -> Int {
    var ary = 0
    // 局部函数捕获变量
    /*fun add(element: Int): Int {
        ary += element
        return ary
    }
    return :: add*/
    // 匿名函数写法
    /*return fun(element: Int): Int {
        ary += element
        return ary
    }*/
    // Lambda表达式写法
    return {
        element ->
        ary += element
        ary
    }
}

fun main(args: Array<String>) {
    // 4、闭包
    // 闭包捕获变量后,这些变量会被保存在一个特殊的容器中被存储起来,即便是声明这些变量的原始作用域已经不存在,闭包体中仍然可以访问这些变量
    // 局部变量
    var localValue = 20
    val result = {
        a: Int ->
        value++
        localValue++
        val c = a + value + localValue
        println(c)
    }
    result(30) // 62
    println("localValue = $localValue") // localValue = 21
    println("value = $value") // value = 11

    val f1 = makeArray()
    // f1每次调用时,ary作用域已经不存在,但是值能够被存起来
    println(f1(10)) // 累加ary,10
    println(f1(20)) // 累加ary,30
    println(f1(30)) // 累加ary,60
}

25、内联函数

           Lambda表达式在编译时被编译成为一个匿名类,每次调用函数时都会创建一个对象,多次调用就会多次创建对象,这会带来较多的开销。于是可以使用内联函数,内联函数在编译时不会生成函数调用代码,而是用函数体中实际代码替换每次调用函数。

            如果函数参数不是函数类型,不能接受Lambda表达式,那么这种函数一般不声明为内联函数。声明内联函数需要使用关键字“inline”:

// HigherOrderFunctionAndLambda
package pub

// 5、内联函数
inline fun calculatePrint2(funcName: (Int, Int) -> Int) {
    println("${funcName(10, 5)}")
}

fun main(args: Array<String>) {
     // 5、内联函数
    calculatePrint2( { a, b -> a + b } ) // 标准形式  15
    calculatePrint2() { a, b -> a - b } // 尾随形式   5
    calculatePrint2 { a, b -> a * b } // 只有一个参数时的尾随形式   50
}

26、三个函数

          let函数:在Kotlin中任何对象都有一个let函数,let函数后面尾随一个Lambda表达式,在对象非空时执行Lambda表达式中的代码,为空时则不执行:

// HigherOrderFunctionAndLambda
package pub

// 6.1、let函数
fun  square(num: Int): Int = num * num

fun main(args: Array<String>) {
    // 6.1、let函数
    var n1: Int? = 10
    n1?. let {
        n -> println(square(n))
    }
    n1?. let {
        println(square(it))
    }
}

             with或apply函数:当需要对一个对象设置多个属性,或者调用多个函数时使用:

// HigherOrderFunctionAndLambda
package pub

// 6.2、with、apply函数
class MyFrame(title: String) : JFrame(title) {
    init {
        // 创建标签
        val label = JLabel("Label")

        // 创建button1
        val button1 = JButton()
        // 传统设置属性和配置监听方法
        button1.text = "Button1"
        button1.toolTipText = "Button1"
        button1.addActionListener { label.text = "单击Button1" }

        // 创建button2
        // 使用apply同时设置多个属性和函数,且this指向了button2,apply是有返回值的,返回值是当前对象
        val button2 = JButton().apply {
            text = "Button2"
            toolTipText = "Button2"
            addActionListener { label.text = "单击Button2" }
            // 添加button2到内容面板
            contentPane.add( this, BorderLayout.SOUTH )
        }

        // with没有返回值,引用当前对象也是用this
        with(contentPane) {
            // 添加标签到内容面板
            add( label, BorderLayout.NORTH )
            // 添加button1到内容面板
            add( button1, BorderLayout.CENTER )
            println(height)
            println(width)
        }

        // 设置窗口大小
        setSize( 350, 120 )
        // 设置窗口可见
        isVisible = true
    }
}

fun main(args: Array<String>) {
    // 6.2、with、apply函数
    MyFrame("MyFrame")
}

27、泛型

          泛型可以最大限度地重用代码、保护类型的安全以及提高性能。泛型特性对Kotlin影响最大是在集合中使用泛型。

          1)、泛型函数:在函数名前添加型如“<T, U>”这样的类型参数的声明,对应的参数类型也被声明为“T、U”,在实际使用中会被替换成实际的类型。泛型中类型参数可以是任何大写或小写的英文字母,一般情况下使用“T、E、K、U”等大写英文字母。

           泛型函数可以声明单一类型参数,可以声明多类型参数;如果需要,可以对类型参数进行可输入类型的限定,限定只能输入什么类型;对于希望具有可比性的对象都能够进行输入,这可以将类型参数限定为Comparable<T>;函数返回值也可以使用类型参数;默认情况下,所有的类型参数的限定类型是Any?,Any?是所有可空类型或者非空类型的父类,如果希望输入的是非空类型,可以将类型参数限定为Any,Any是Kotlin中所有非空类型的父类:

// Generic
package pub

import java.util.*

// 1、泛型函数
// 在函数名前添加型如“<T, U>”这样的类型参数的声明,对应的参数类型也被声明为“T、U”,在实际使用中会被替换成实际的类型。
// 泛型中类型参数可以是任何大写或小写的英文字母,一般情况下使用“T、E、K、U”等大写英文字母。
// 1.1、单一类型参数
fun <T> isEqual(a: T, b: T):Boolean {
    return a == b
}
// 1.2、多类型参数
fun <T, U> addRectangle(a: T, b: U): Boolean {
    // do something
    return true
}
// 1.3、类型参数声明返回值类型
fun <T, U> rectangleEquals(a: T, b: U): U {
    // do something
    return b
}
// 1.4、泛型约束
// 由于不是所有的类型参数都具有“可比性”,所以可以对类型参数进行限定。
// 泛型约束主要应用于泛型函数和泛型类,声明形式“类型参数: 限定参数类型”
// 1.4.1、限定Number类型
fun <T: Number> isEqual2(a: T, b: T): Boolean {
    return a == b
}
// 1.4.2、所有可比较的对象都实现“Comparable<T>”接口,所以也可以限定类型参数为“Comparable<T>”,其本身也是泛型。
fun <T: Comparable<T>> isEqual3(a: T, b: T): Boolean {
    return a == b
}
// 1.5、可空类型参数
// 在泛型函数声明中,类型参数没有泛型约束,函数可以接收任何类型的数据,包括可空和非空数据。
// 所有没有泛型约束的类型参数,事实上其限定类型是Any?,Any?是任何可空类型的根类,也兼容非空类型。
// 如果不想接收任何可空类型的数据,可以采用Any作为约束,Any是任何非空类型的父类。
fun <T: Any> isEqual4(a: T, b: T): Boolean {
    return a == b
}


fun main(args: Array<String>) {

    // 1.1、单一类型参数
    println(isEqual(1, 5)) // false
    println(isEqual(1.0, 5.0)) // false

    // 1.4.1、限定Number类型
    println(isEqual2(1, 1.0)) // false
    println(isEqual2(1, 1)) // true
    // 1.4.2、限定Comparable<T>类型
    println(isEqual3(1.0, 5.0)) // false
    println(isEqual3(1, 1)) // true
    println(isEqual3("a", "a")) // true
    println(isEqual3(Date(), Date())) // true

    // 1.5、可空类型参数
    // 接收可空类型:
    println(isEqual(null, null)) // true
    println(isEqual4(1, 2)) // false
    // println(isEqual4(null, null)) // 直接报错
}

          2)、泛型属性:只有扩展属性可以声明泛型,普通属性不能声明泛型:

// Generic
package pub

import kotlin.collections.ArrayList

// 2、泛型属性:只有扩展属性可以声明泛型,普通属性不能声明泛型
// 这里T?表示有返回空值的情况
val <T> ArrayList<T>.first: T?
    get() = if (this.size > 1) this[0] else null
val <T> ArrayList<T>.second: T?
    get() = if (this.size > 2) this[1] else null

fun main(args: Array<String>) {
   // 2、泛型属性
    // 声明Int类型的集合
    val array1 = ArrayList<Int>() // 等同于arrayListOf<Int>()
    println(array1.first) // null
    println(array1.second) // null
    val array2 = arrayListOf("A", "B", "C")
    println(array2.first) // A
    println(array2.second) // B
}

          3)、泛型类:在类名后添加类型参数,同样,可以使用多个不同类型参数,也可以限定类型:

// Generic
package pub

import kotlin.collections.ArrayList
// 3、泛型类:在类名后添加类型参数,同样,可以使用多个不同类型参数,也可以限定类型
// 按照《kotlin从小白到大牛》以队列来介绍
class Queue<T> {
    private var items: MutableList<T>

    // 初始化属性
    init {
        this.items = ArrayList<T>()
    }

    // 入列
    fun queue(item: T) {
        this.items.add(item)
    }
    // 出列
    fun dequeue(): T? {
        return if (this.items.size == 0)
            null
        else
            this.items.removeAt(0)
    }

    override fun toString(): String {
        return this.items.toString()
    }
}
// 限定类型
class Queue2<T: String>

fun main(args: Array<String>) {
   // 3、泛型类
    val stringQueue = Queue<String>()
    stringQueue.queue("A")
    stringQueue.queue("B")
    stringQueue.queue("C")
    stringQueue.queue("D")
    println(stringQueue) // [A, B, C, D]
    println(stringQueue.dequeue()) // A
    println(stringQueue) // [B, C, D]
}

          4)、泛型接口:和声明泛型类方式一样,需要注意的是,其具体实现类也需要声明成泛型的:

// Generic
package pub

import kotlin.collections.ArrayList


// 4、泛型接口:和声明泛型类方式一样
interface IQueue<T> {
    // 入列
    fun queue(item: T)
    // 出列
    fun dequeue(): T?
}
// 实现接口的具体类也需要声明为泛型
class ListQueue<T>: IQueue<T> {
    private val items: MutableList<T>

    init {
        this.items = ArrayList()
    }

    override fun queue(item: T) {
        this.items.add(item)
    }

    override fun dequeue(): T? {
        return if (this.items.size == 0)
            null
        else this.items.removeAt(0)
    }

    override fun toString(): String {
        return this.items.toString()
    }
}

28、数组

          三个基本特性:

          a、一致性。数组只能保存相同数据类型元素,元素的数据类型可以是任何相同的数据类型。

          b、有序性。数组中的元素是有序的,通过下标访问,数组下标从零开始。

          c、不可变性。数组一旦初始化,则长度(数组中元素的个数)不可变。

          Kotlin中数组分为对象数组和基本数据类型数组。

          1)、对象数组:Array<T>,其中保存的是Java中的“对象”,编译后是Java包装类数组,而不是Java基本数据类型数组,如下表所示:

kotlin对象数组Java包装类数组
Array<Byte>java.lang.Byte[]
Array<Short>java.lang.Short[]
Array<Integer>java.lang.Integer[]
Array<Long>java.lang.Long[]
Array<Float>java.lang.Float[]
Array<Double>java.lang.Double[]
Array<Char>java.lang.Char[]
Array<Boolean>java.lang.Boolean[]

三种创建对象数组方式:

A、arrayOf(vararg elements: T)工厂函数。指定数组元素列表创建元素类型为T的数组,vararg表名参数个数是可变的。

B、arrayOfNulls<T>(size: Int)函数。size参数指定数组大小,创建元素类型为T的数组,数组中的元素为空值。

C、Array(size: Int, init: (Int) -> T)构造函数。通过size参数指定数组大小,init参数指定一个用于初始化元素的函数(用于给数组元素赋值),实际使用时经常是Lambda表达式。

// ArrayAndTest.Array
package Array

fun main(args: Array<String>) {

    // 1、对象数组
    // 静态初始化
    val intArray1 = arrayOf(21, 32, 43, 45)
    val strArray1 = arrayOf("张三", "李四", "王五", "董六")
    // 动态初始化
    val strArray2 = arrayOfNulls<String>(4)
    strArray2[0] = "张三"
    strArray2[1] = "李四"
    strArray2[2] = "王五"
    strArray2[3] = "董六"
    val intArray2 = Array<Int>(10) { i -> i * i} // 可用 { it * it } 代替
    val intArray3 = Array<Int?>(10) { it * it * it } // 可用 { i -> i * i * i } 代替

    for (item in intArray2) {
        print(item)
        print("  ")
    } // 0  1  4  9  16  25  36  49  64  81
    println()
    for (idx in strArray1.indices) { // indices属性返回数组下标索引
        print(strArray1[idx])
        print("  ")
    } // 张三  李四  王五  董六   
    println()

}

          2)、基本数据类型数组:Kotlin编译器将元素是基本类型的Kotlin对象数组编译成为Java包装类数组,包装类数组相比基本数据类型数组,数据存储空间占用大,运算效率差。为此,Kotlin提供了8中基本数据类型数组,可编译成Java基本数据类型数组,如下表所示:

Kotlin基本数据类型数组Java基本数据类型数组对应的三种创建方式
ByteArraybyte[]

1、byteArrayOf(vararg elements: Byte);

2、ByteArray(size: Int);

3、ByteArray(size: Int, init: (  ) -> Byte);

ShortArrayshort[]

1、shortArrayOf(vararg elements: Short);

2、ShortArray(size: Int);

3、ShortArray(size: Int, init: (Int) -> Short);

IntArrayint[]

1、intArrayOf(vararg elements: Int);

2、IntArray(size: Int);

3、IntArray(size: Int, init: (Int) -> Int);

LongArraylong[]

1、longArrayOf(vararg elements: Long);

2、LongArray(size: Int);

3、LongArray(size: Int, init: (Int) -> Long);

FloatArrayfloat[]

1、floatArrayOf(vararg elements: Float);

2、FloatArray(size: Int);

3、FloatArray(size: Int, init: (Int) -> Float);

DoubleArraydouble[]

1、doubleArrayOf(vararg elements: Double);

2、DoubleArray(size: Int);

3、DoubleArray(size: Int, init: (Int) -> Double);

CharArraychar[]

1、charArrayOf(vararg elements: Char);

2、CharArray(size: Int);

3、CharArray(size: Int, init: (Int) -> Char);

BooleanArrayboolean[]

1、booleanArrayOf(vararg elements: Boolean);

2、BooleanArray(size: Int);

3、BooleanArray(size: Int, init: (Int) -> Boolean);

每一种基本类型数组创建都有三种方式,下面以Int为例

A、intArrayOf(vararg elements: Int)工厂函数。通过对应的工厂函数,vararg表名参数是可变参数,是Int数组列表。

B、IntArray(size: Int)构造函数。size参数指定数组大小创建元素类型为Int的数组,数组中的元素为该类型默认,Int的默认值是0。

C、IntArrat(size: Int, init(Int) -> Int)构造函数。通过size参数指定数组大小,init参数指定一个用于初始化元素的函数,参数经常使用Lambda表达式。

// ArrayAndTest.Array
package Array

fun main(args: Array<String>) {
    // 2、基本数据类型数组
    // 静态初始化
    val array1 = shortArrayOf(20, 10, 50, 40, 30)
    // 动态初始化
    val array2 = CharArray(3)
    array2[0] = 'C'
    array2[1] = 'B'
    array2[2] = 'D'
    val array3 = IntArray(10) { it * it }
    for (item in array3) {
        print(item)
        print("  ")
    } // 0  1  4  9  16  25  36  49  64  81
    println()
    for (idx in array2.indices) {
        print(array2[idx])
        print("  ")
    } // C  B  D
    println()
}

29、集合

    Kotlin集合的主要接口和类如下图所示:

 Kotlin的集合主要分为:Collection和Map,MutableCollection是Collection的可变子接口,MutableMap是Map的可变子接口。其中可变子接口是以Mutable开头的可变集合,可变集合包含了add、remove和clear等共同的方法。上图是接口以及实现类的继承关系图,可以看到,Collection、Set、List、Map都是不可变的接口,即用它们声明集合的时候,集合的内容(长度和数据)也就固定下来了。而子接口MutableCollection、MutableSet、MutableList、MutableMap和实现类HashSet、ArrayList、HashMap则是可变的,集合的内容是变化的。由于可变和不可变集合都存在共性,所以下会以Set集合以及HashSet实现类作为例子,其它的或多或少都是一样的创建或者相同的方法调用:

// ArrayAndSet.Set
package Set

fun main(args: Array<String>) {

    // 1、Set集合
    // Set集合是由一串无序的、不能重复的相同类型构成的集合。
    // 创建Set集合的三种方式:
    //      A、setOf()。创建空的不可变Set集合;
    //      B、setOf(element: T)。创建单个元素的不可变Set集合;
    //      C、setOf(vararg elements: T)。创建多个元素的不可变Set集合
    // 不可变集合的一些常用的函数和属性(继承自Collection):
    //      A、isEmpty()。判断集合中是否有元素(相反函数是isNotEmpty());
    //      B、contains(element: E)函数。判断集合中是否有指定元素;
    //      C、iterator()。返回迭代器对象,用于集合遍历;
    //      D、size属性。返回集合中元素个数
    val set1 = setOf("ABC") // [ABC]
    val set2 = setOf<Long?>() // []
    val set3 = setOf(1, 2, 3, 4, 5) // [1, 2, 3, 4, 5]

    println(set1.size) // 1
    println(set2.isEmpty()) // true
    println(set3.contains(3)) // true

    // 1、使用for循环
    print("for循环读取集合元素:")
    for (item in set3) {
        print(item)
        print("  ")
    } // for循环读取集合元素:1  2  3  4  5
    println()
    // 2、使用迭代器遍历
    val it = set3.iterator()
    print("迭代器iterator循环读取集合元素:")
    while(it.hasNext()) {
        print(it.next())
        print("  ")
    } // 迭代器iterator循环读取集合元素:1  2  3  4  5
    println()

    // 2、可变Set集合
    // 可变集合创建的两种方式(由于可变,所以相比父集合的创建方式少一个单一元素的创建函数):
    //      A、mutableSetOf()。创建空的可变Set集合,集合类型是MutableSet接口;
    //      B、mutableSetOf(vararg elements: T)。创建多个元素的可变Set集合,集合类型是MutableSet接口。
    // 实现类创建的两种方式:
    //      A、hashSetOf()。创建空的可变Set集合,集合类型是HashSet类;
    //      B、hashSetOf(vararg elements: T)。创建多个元素的可变Set集合,集合类型是HashSet类。
    // 可变集合除了继承Collection的一些基本函数和属性,还继承了MutableCollection接口的一些修改集合内容的函数:
    //      A、add(element: E)。在Set集合的尾部添加指定的元素;
    //      B、remove(element: E)。如果Set集合中存在指定元素,则从Set集合中移除该元素;
    //      C、clear()。清楚Set集合中的所有元素。
    val set4 = mutableSetOf(1, 2, 3, 4, 5)
    val set5 = mutableSetOf<String?>()
    val set6 = hashSetOf<Long?>()
    val set7 = hashSetOf("B", "D", "F")

    val b = "B"
    set5.add("A")
    set5.add(b)
    set5.add("C")
    set5.add(b)
    set5.add("D")
    set5.add("E")

    // 获取长度
    println("集合set5=${set5},长度size=${set5.size}") // 集合set5=[A, B, C, D, E],长度size=5

    // 删除一个元素B
    set5.remove("B")
    println("""集合set5是否包含"B":${set5.contains("B")}""") // 集合set5是否包含"B":false
    println("集合set5=${set5}") // 集合set5=[A, C, D, E]
    // 判断是否为空
    println("集合set5是否为空:${set5.isEmpty()}") // 集合set5是否为空:false
    // 清空集合
    set5.clear()
    println("集合set5是否为空:${set5.isEmpty()}") // 集合set5是否为空:true

    set6.add(1)
    set6.add(2)
    set6.add(3)
    // 1、使用for循环
    print("for循环读取集合set5元素:")
    for (item in set5) { // 因为前面清空了,所以最终结果没有数据打印出来
        print(item)
        print("  ")
    } // for循环读取集合set5元素:
    println()
    // 2、使用迭代器遍历
    val it2 = set6.iterator()
    print("迭代器iterator循环读取集合set6元素:")
    while(it2.hasNext()) {
        print(it2.next())
        print("  ")
    } // 迭代器iterator循环读取集合set6元素:1  2  3
    println()

}

Set集合关心的重点在于不可重复,不考虑顺序;而List集合关系的是顺序,而不考虑重复性,且由于List集合有是有顺序的,所以也会有特殊的一些函数;Map集合由于存放的是键值对,所以创建的时候,参数列表的属性则是“Pair<K, V>”类型,对应的写法是“Key to Value”,由于一个参数是包含键和值的,所以也会有自己的一些属性和函数,具体见下表:

集合接口或类类型公共的函数公共的属性接口类型私有的函数或属性
Set接口不可变的

isEmpty();

contain(element: E);

 size 
List接口不可变的

indexOf(element: E);

lastIndexOf(element: E);

subList(fromIndex: Int, toIndex: Int)。

MutableSet接口可变(父接口是Set接口)

add(element: E);

remove(element: E);

clear()

 

 
HashSet类可变(父接口是MutableSet接口) 
MutableList接口可变(父接口是List接口)继承父接口的私有函数或属性
HashList类可变(父接口是MutableList接口)继承父接口的私有函数或属性
Map接口不可变的

isEmpty();

containsKey(key: K);

containsValue(value: V);

 

size;

keys;

values;

 
MutableMap接口可变

put(key: K, value: V);

remove(key: K);

clear()

 

HashMap类可变 

(说明:

List集合的indexOf()函数和lastIndexOf()用法同Java一样,返回的是元素从前或者从后找在集合中第一次出现的位置,不存在则返回-1;

List集合的subList(F, L)类似Java中的substring(F, L),只不过返回的是List集合,注意L是包含在返回值中的;

Map集合是属性keys返回的是Map中所有的键集合,返回值是Set类型;

Map集合是属性values返回的是Map中所有的值集合,返回值是Collection类型;

30、forEach函数和forEachIndexed函数

// FunctionalProgrammingAPI.Traversing
// 遍历
package Traversing

fun main(args: Array<String>) {

    // 1、forEach函数:适用于Collection和Map集合,以及数据,函数只有一个函数类型的参数,实参往往使用尾随形式的Lambda表达式。
    //             在执行时,会把集合或数组中的每一个元素传递给Lambda表达式(或其它的函数引用)以便去执行。无法返回元素的
    //             索引。
    val strArray = arrayOf("张三", "李四", "王五", "董六")
    val set = setOf(1, 3, 34, 54, 75)
    val map = mapOf(102 to "张三", 105 to "李四", 109 to "王五")
    // 遍历数组
    strArray.forEach {
        print(it + "  ")
    } // 张三  李四  王五  董六
    println()
    // 遍历Set集合
    set.forEach {
        print(it)
        print("  ")
    } // 1  3  34  54  75
    println()
    // 遍历Map集合
    map.forEach { k, V ->
        print("$k=$V;")
    } // 102=张三;105=李四;109=王五;
    println()
    // 遍历Map集合的Entry键值对对象,这里的it就是Entry
    map.forEach {
        print("${it.key}=${it.value};")
    } // 102=张三;105=李四;109=王五;
    println()

    // 2、forEachIndexed函数:既能返回集合元素,又可以返回元素索引。使用Collection集合和数组
    strArray.forEachIndexed { index, value ->
        print("$index=$value;")
    } // 0=张三;1=李四;2=王五;3=董六;
    println()
    set.forEachIndexed { index, value ->
        print("$index=$value;")
    } // 0=1;1=3;2=34;3=54;4=75;
    println()
}

31、三种基本函数——过滤、映射、聚合

// FunctionalProgrammingAPI.FilterMapReduce
// 过滤、映射、聚合
package FilterMapReduce

fun main(args: Array<String>) {

    // 数据类,测试用
    data class User(val name: String, var password: String)

    val users = listOf(
            User("Tony", "12%^3"),
            User("Tom", "23##4"),
            User("Ben", "1332%#4"),
            User("Alex", "ac133")
    )

    // 1、过滤filter函数:可以对Collection集合、Map集合、数组元素进行过滤,
    //                Collection集合和数组返回的是一个List集合,Map返回的还是一个Map集合
    // 这里第一次用了链式调用,对users集合先进行过滤,符合前缀是“t”,忽略大小写的元素,生成一个新的List集合
    // (也就是说过滤器是把符合条件的筛选出来),然后传递给forEach进行遍历
    users.filter {
        it.name.startsWith("t", ignoreCase = true)
    }.forEach {
        print("${it.name}=${it.password};")
    } // Tony=12%^3;Tom=23##4;
    println()

    // 2、映射map函数:可以对Collection集合、Map集合、数组进行变换返回一个List集合
    users.filter {
        it.name.startsWith('t', ignoreCase = true)
    }.map {
        // 将过滤后的List集合中的每个User对象的属性“name”,取出来生成一个新的List集合
        it.name
    }.forEach {
        print(it + "  ")
    } // Tony  Tom
    println()

    // 3、聚合reduce函数:将Collection集合或数组中的数据聚合起来输出单个数据,聚合操作中最基础的是归纳函数reduce,
    //                    reduce函数会将集合或数组的元素按照指定的算法之类叠加起来,最后输出一个数据
    // pwd是叠加的参数,在聚合过程中会保留上次的结果,i是当前元素
    var password = users.map {
        it.password
    }.reduce { pwd, i ->
        pwd + "  " + i
    }
    println(password) // 12%^3  23##4  1332%#4  ac133


    data class Song(val title: String, val durationInSeconds: Int)

    val songs = listOf(
            Song("Speak to Me", 90),
            Song("Breathe", 163),
            Song("On he Run", 216),
            Song("Time", 421),
            Song("The Great Gig in the Sky", 276),
            Song("Money", 382),
            Song("Us and Them", 462),
            Song("Any Color You Like", 205),
            Song("Brain Damage", 228),
            Song("Eclipse", 123)
    )
    // 其它的常用聚合函数:
    // 1、any函数:适用Collection集合、Map集合、数组;返回布尔值;如果至少有一个元素与指定条件相符,返回true,否则返回false;
    // 2、all函数:适用Collection集合、Map集合、数组;返回布尔值;如果所有元素与指定条件相符,返回true,否则返回false;
    // 3、count函数:适用Collection集合、Map集合、数组;返回Int类型;返回与指定条件相符的元素个数;
    // 4、max函数:适用Collection集合、数组;元素自身类型;返回最大元素,如果没有元素则返回空值;
    // 5、maxBy函数:适用Collection集合、Map集合、数组;返回元素自身类型;返回使指定函数产生最大值的第一个元素。如果没有元素,则返回空值;
    // 6、min函数:适用Collection集合、数组;元素自身类型;返回最小元素,如果没有元素则返回空值;
    // 7、minBy函数:适用Collection集合、Map集合、数组;返回元素自身类型;返回使指定函数产生最小值的第一个元素。如果没有元素,则返回空值;
    // 8、sum函数:适用Collection集合、数组;元素自身类型;返回所有元素之和;
    // 9、sumBy函数:适用Collection集合、Map集合、数组;返回元素自身类型;返回使指定函数计算集合元素总和;
    // 10、average函数:适用Collection集合、数组;返回Double类型;返回所有元素的平均值;
    // 11、none 函数:适用Collection集合、Map集合、数组;返回布尔值;如果没有元素与指定条件相符,则返回true,否则返回false。
    val list = listOf(1, 3, 34, 54, 75)
    val map = mapOf(102 to "张三", 105 to "李四", 109 to "王五")
    println(list.any { it > 10 }) // true
    println(list.all { it > 0 }) // true
    println(list.count { it > 10 }) // 3

    println(list.max()) // 75
    println(list.maxBy { it > 50 }) // 54
    println(map.maxBy { it.key }) // 109="王五"

    println(list.min()) // 1
    println(map.minBy { it.key }) // 102="张三"

    println(list.sum()) // 167
    println(songs.sumBy { it.durationInSeconds }) // 2566

    println(list.average()) // 33.4

    println(list.none { it < -1 }) // true

    // 其它的常用过滤函数:
    ///1、drop函数:适用Collection集合、数组;返回List集合;返回不包含前n个元素的List集合;
    // 2、filterNot函数:适用Collection集合、Map集合、数组;返回元素自身类型;与filter相反,过滤出不符合条件的数据;
    // 3、filterNotNull函数:适用Collection集合、Array数组(对象数组);返回List集合;返回非空元素List集合;
    // 4、slice函数:适用Collection集合、数组;返回List集合;返回指定索引的元素List集合;
    // 5、take函数:适用Collection集合、数组;返回List集合;返回前n和元素List集合,与drop相反;
    // 6、takeLast函数:适用Collection集合、数组;返回List集合;返回后n个元素List集合;
    // 7、find函数:适用Collection集合、数组;返回元素自身类型;返回符合条件的第一个元素,如果没有符合条件的元素,则返回空值;
    // 8、findLast函数:适用Collection集合、数组;返回元素自身类型;返回符合条件的最后一个元素,如果没有符合条件的元素,则返回空值;
    // 9、first()函数:适用Collection集合、数组;返回元素自身类型;返回第一个元素,函数没有参数;
    // 10、last()函数:适用Collection集合、数组;返回元素自身类型;返回最后一个元素,函数没有参数;
    // 11、first{ }函数:适用Collection集合、数组;返回元素自身类型;返回符合条件的第一个元素,函数有一个参数,如果没有符合条件的元素,抛出异常;
    // 12、last{ }函数:适用Collection集合、数组;返回元素自身类型;返回符合条件的最后一个元素,函数有一个参数,如果没有符合条件的元素,抛出异常;
    // 13、firstOrNull{ }函数:适用Collection集合、数组;返回元素自身类型;返回符合条件的第一个元素,函数有一个参数,如果没有符合条件的元素,则返回空值;
    // 14、lastOrNull{ }函数:适用Collection集合、数组;返回元素自身类型;返回符合条件的最后一个元素,函数有一个参数,如果没有符合条件的元素,则返回空值。
    val map2 =  mapOf(102 to "张三", 105 to "李四", 109 to "王五")
    val array = intArrayOf(1, 3, 34, 54, 75)
    val charList = listOf("A", null, "B", "C")

    println(array.drop(2)) // [34, 54, 75]

    println(map2.filter { it.key > 102 }) // {105=李四, 109=王五}
    println(map2.filterNot { it.key > 102 }) // {102=张三}
    println(charList.filterNotNull()) // [A, B, C]

    println(array.slice(listOf(0, 2))) // [1, 34]

    println(array.take(3)) // [1, 3, 34]
    println(array.takeLast(3)) // [34, 54, 75]

    println(array.find { it > 10 }) // 34
    println(array.findLast { it < -1 }) // null

    println(array.first()) // 1
    println(array.last()) // 75
    println(array.first { it > 10 }) // 34
    println(array.last { it < 10 }) // 3
    println(array.firstOrNull { it > 100 }) // null
    println(array.lastOrNull { it > 10 }) // 75

    // 其它的常用映射函数:
    // 1、mapNotNull函数:适用Collection集合、Map集合、Array数组;返回List集合;返回一个List集合,该List集合包含对原始集合中非空元素进行转换后结果,注意Array数组是对象数组,不能是基本数据类型数组;
    // 2、mapIndexed函数:适用Collection集合、数组;返回List集合;返回一个List集合,该List集合包含对原始集合中每个元素进行转换后结果和它们的索引;
    // 3、flatMap函数:适用Collection集合、数组;返回List集合;扁平化映射,可以将多维数组或集合变换为一维集合。
    val set = setOf(1, 3, 34, 54, 75)
    val charList2 = listOf("A", null, "b", "C")
    println(charList2.mapNotNull { it }) // [A, b ,C]
    println(charList2.mapNotNull { it }.map { it.toLowerCase() }) // [a, b, c]

    println(set.mapIndexed { index, s -> index + s }) // [1, 4, 36, 57, 79]

    val datas = listOf( listOf(10, 20), listOf(20, 40) )
    val flatMapList = datas.flatMap {
        e -> e.map{ it * 10 }
    }
    println(flatMapList) // [100, 200, 200, 400]

}

32、排序

// FunctionalProgrammingAPI.Sort
// 排序
package Sort

fun main(args: Array<String>) {

    // 数据类,测试用
    data class User(val name: String, var password: String)
    val users = listOf(
            User("Tony", "12%^3"),
            User("Tom", "23##4"),
            User("Ben", "1332%#4"),
            User("Alex", "ac133")
    )

    // 5个常用的排序函数:
    // 1、sorted函数:元素是可排序的MutableList集合或者数组;返回集合或者数组自身类型;升序;
    // 2、sortedBy函数:元素是可排序的MutableList集合或者数组;返回集合或者数组自身类型;指定表达式计算之后再进行升序排序;
    // 3、sortedDescending函数:元素是可排序的MutableList集合或者数组;返回集合或者数组自身类型;降序;
    // 4、sortedByDescending函数:元素是可排序的MutableList集合或者数组;返回集合或者数组自身类型;指定表达式计算之后再进行降序排序;
    // 5、reversed函数:元素是可排序的MutableList集合或者数组;返回集合或者数组自身类型;将原始倒置;

    // sorted函数和sortedDescending函数要求集合或数组中元素是可比较的,也就是实现了Comparable接口。
    // 在下面的例子中,users中的User没有实现Comparable接口,所以不用使用sorted函数和sortedDescending函数,
    // 但是可以使用sortedBy函数和sortedByDescending函数,通过对基本数据类型的属性进行排序。

    val set = setOf(1, -3, 34, -54, 75)
    println(set.sorted())
    // [-54, -3, 1, 34, 75]
    println(users.sortedBy { it.name })
    // [User(name=Alex, password=ac133), User(name=Ben, password=1332%#4),
    // User(name=Tom, password=23##4), User(name=Tony, password=12%^3)]
    println(set.sortedDescending())
    // [75, 34, 1, -3, -54]
    println(users.sortedByDescending { it.name })
    // [User(name=Tony, password=12%^3), User(name=Tom, password=23##4),
    // User(name=Ben, password=1332%#4), User(name=Alex, password=ac133)]
    println(set.reversed())
    // [75, -54, 34, -3, 1]

}

33、异常

Kotlin处理机制基本继承了Java异常处理机制,但区别在于,Java异常分为受检查异常(要么用try-catch捕获,要么抛出,否则会发生编译错误)和运行时异常,而Kotlin只有运行时异常。

所有异常类都直接或间接地继承Kotlin.lang.Throwable类,重要的属性和函数(和Java相似):

message属性:获得发生错误或异常的详细信息;

printStackTrace函数:打印错误或异常堆栈跟踪信息;

toString函数:获得错误或异常对象的描述。

Throwable类有两个直接子类:Error和Exception,同Java,异常捕获同样使用try-catch。对于资源的释放,可以在finally{}代码块中释放,但是为了优化代码结构和提高程序可读性,也可以用Java 7之后提供的自动资源管理(Automatic Resource Management)技术:通过调用use函数进行资源管理,采用自动资源管理技术就可以不适用finally{}代码块了,释放资源的过程都交给Java虚拟机了。

Java中,如果需要显示抛出异常,则需要使用throws,而Kotlin没有throws关键字,也不需要显示声明抛出异常。

在自定义异常类时,一般需要提供两个构造函数,一个是无参的描述信息为空的构造函数,另外一个是提供字符串参数的构造函数。

// Throwable
package pub

import java.io.*
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*

class MyException : Exception {
    constructor() {

    }

    constructor(message: String) : super(message) {

    }
}

fun readDate(): Date? {
    // 自动资源管理
    try {
        FileInputStream("readme.txt").use { fileis ->
            InputStreamReader(fileis).use { isr ->
                BufferedReader(isr).use { br ->
                    val str = br.readLine() ?: return null
                    val df = SimpleDateFormat("yyyy-mm-dd")
                    return df.parse(str)
                }
            }
        }
    } catch (e: FileNotFoundException) {
        throw MyException()
    } catch (e: IOException) {
        throw Throwable()
    } catch(e: ParseException) {
        println("处理ParseException......")
        e.printStackTrace()
    }
    return null
}

fun main(args: Array<String>) {
    val date = readDate()
    println("读取的日期 = ${date}")
    /** 因为没有文件,所有抛出自定义的MyException异常类
     * Exception in thread "main" pub.MyException
            at pub.TestKt.readDate(Test.kt:32)
            at pub.TestKt.main(Test.kt:43)
     */

}

33、线程

1)、创建线程使用thread函数:

fun thread(
    start: Boolean = true,
    isDaemon: Boolean = false,
    contextClassLoader: ClassLoader? = null,
    name: String? = null,
    priority: Int = -1,
    block: () -> Unit
): Thread

/*
    thread函数的返回类型是Thread类,函数中的参数:
    1、start:是否创建完成线程马上启动,在Java中需要另外调用start函数;
    2、isDaemon:是否为守护线程,即在后台长期运行的线程,主要提供一些后台服务,生命周期和Java虚拟机一样长;
    3、contextClassLoader:类加载器,用来加载一些资源;
    4、name:指定线程名,如果不指定则系统会自动分配一个线程名;
    5、priority:设置线程优先级;
    6、block:参数线程体,是线程要执行的核心代码。
    
    tip:主线程中执行入口是main(args: Array<String>)函数,这里可以控制程序的流程,管理其他的子线程等。子线程执行入口是线程体,子线程相关代码都是在线程体中编写的。
*/
// Thread
package pub

import  java.lang.Thread.currentThread
import java.lang.Math.random
import java.lang.Thread.sleep
import kotlin.concurrent.thread

// 1、创建线程
// 编写执行线程代码
fun run() {
    for (i in 0..9) {
        // 打印次数和线程的名字
        println("第${i}次执行 - ${currentThread().name}")
        // 随机生成休眠时间
        val sleepTime = (1000 * random()).toLong()
        // 线程休眠
        sleep(sleepTime)
    }
    // 线程执行结束
    println("执行完成!" + currentThread().name)
}

fun main(args: Array<String>) {

    // 创建线程1
    thread {
        run()
    }

    // 创建线程2
    thread(name = "MyThread") {
        run()
    }

}

2)、线程状态:

  • 新建状态(New):实例化Thread对象,仅仅是一个空的线程对象;
  • 就绪状态(Runnable):主线程调用新建线程的start()函数后,就进入就绪状态,此时尚未真正执行线程体,须等CPU调度;
  • 运行状态(Running):CPU调度就绪状态的线程,线程就进入运行状态,处于运行状态的线程独占CPU,执行线程体;
  • 阻塞状态(Blocked):导致阻塞的原因:(1)当前线程调用sleep函数,进入休眠状态;(2)被其它线程调用了join函数,等待其他线程结束;(3)发出I/O请求,等待I/O操作完成;(4)当前线程调用wait函数。阻塞的线程可以回到就绪状态。
  • 死亡状态(Dead):执行完线程体或者发生异常。

3)、线程管理:

          3.1)、等待线程结束:当前线程调用t1线程的join函数,会阻塞当前线程等待线程t1结束,如果t1线程结束或等待超时,则当前线程回到就绪状态。定义如下:

  •            join():等待该线程结束。
  •            join(mills: Long):等待该线程结束的时间最长为mills毫秒。如果超时为0意味着要一直等下去。

           3.2)、线程让步:线程类Thread提供了一个yield函数,调用yield函数能够使当前线程给其他线程让步。类似于sleep函数,放弃CPU使用权,暂停片刻,然后重新回到就绪状态。与sleep函数不同的是,sleep函数是线程进行休眠,能够给其他线程运行的机会,无论线程优先级高低都有机会运行,而yield函数只能给相同优先级或更高优先级线程机会。(yiled函数在实际开发中很少用,大多使用sleep函数,sleep函数可以控制时间,而yield不能)。

           3.3)、线程停止:对于有死循环的线程,通过设置结束变量的方式来停止线程。

// Thread
package pub

import  java.lang.Thread.currentThread
import java.lang.Thread.yield
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import kotlin.concurrent.thread

// 3.1)、等待线程结束
/*
var value = 0

fun main(args: Array<String>) {

    println("主线程main函数开始...")

    val t1 = thread {
        println("子线程开始...")

        for (i in 0..1) {
            println("子线程执行...")
            value++
        }

        println("子线程结束...")
    }

    // 主线程被阻塞,等待t1线程结束
    t1.join()
    println("vlaue = $value")

    println("主线程main函数结束...")

}*/



// 3.2)、线程让步
/*
fun run2() {
    for (i in 0..9) {
        println("第${i}次执行 - ${currentThread().name}")
        yield()
    }
    println("执行完成!" + currentThread().name)
}

fun main(args: Array<String>) {

    thread {
        run()
    }

    thread(name = "MyThread") {
        run()
    }

}*/



// 3.3)、线程停止
var command = ""

fun main(args: Array<String>) {

    val t1 = thread {

        // 一直循环,直到满足条件再停止线程
        while(command != "exit") {
            println("下载中...")
            Thread.sleep(10000)
        }

        println("线程完成!")
    }

    // readLine()底层调用的是Java的System.in,接收从控制台输入的字符
    command = readLine()!!

}

34、协程

协程是一种轻量级的线程,协程提供了一种不阻塞线程,但是可以被挂起的计算过程。线程阻塞开销是巨大的,而协程挂起基本上没有开销。

在执行阻塞任务时,会将这种任务放到子线程中执行,执行完成再回调主线程,更新UI等操作,这就是异步编程。协程底层库也是异步处理阻塞任务,但是这些复杂的操作被底层封装起来,协程代码的程序流是顺序的,不再需要一堆的回调函数。

线程是抢占式的,操作系统级的。协程是协作式的,用于级的,与操作系统无关。

(先过滤掉协程的学习,因为目前是实验阶段)

35、Kotlin与Java混编(在实战中用就行了,不做总结)

36、Kotlin I/O流

Java输入流和输出流:

(输入流)类(先字节流,后字符流)描述(输出流)类(先字节流,后字符流)描述
FileInputStream文件输入流FileOutputStream文件输入流
ByteArrayInputStream面向字节数组的输入流ByteArrayOutputStream面向字节数据的输出流
PipedInputStream管道输入流,用于两个线程之间的数据传递PipedOutputStream管道输出流,用于两个线程之间的数据传递
FilterInputStream过滤输入流,它是一个装饰器,扩展其他输入流FilterOutputStream过滤输出流,它是一个装饰器,扩展其他输出流
BufferedInputStream缓冲区输入流,它是FilterInputStream的子类BufferedOutputStream缓冲区输出流,它是FilterOutpuStream的子类
DataInputStream面向基本数据类型的输入流DataOutputStream面向基本数据类型的输出流
    
FileReader文件输入流FileWriter文件输出流
CharArrayReader面向字符数据的输入流CharArrayWriter面向字符数组的输出流
PipedReader管道输入流,用于两个线程之间的数据传递PipedWriter管道输出流,用于两个线程之间的数据传递
FilterReader过滤输入流,它是一个装饰器,扩展其他输入流FilterWriter过滤输出流,它是一个装饰器,扩展其他输出流
BufferedReader缓冲区输入流,它也是装饰器,但不是FilterReader的子类BufferedWriter缓冲区输出流,它也是装饰器,但不是FilterWriter的子类
InputStreamReader把字节流转换为字符流,它是一个装饰器,是FilterReader的子类OutputStreamWriter把字节流转换为字符流,它也是一个装饰器,是FilterWriter的子类

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

(未完待续。。。)

 

 

 

 

 

 


 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

rising_chain

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

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

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

打赏作者

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

抵扣说明:

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

余额充值