Kotlin知识点

858 阅读17分钟

Kotlin

零、Kotlin是什么?

Kotlin就是一门可以运行在Java虚拟机、Android、浏览器上的静态语言,它与Java100%兼容,如果你对Java非常熟悉,那么你就会发现Kotlin除了自己的标准库之外,大多仍然使用经典的Java集合框架。

一、基本数据类型

1. byte类型定义

// 定义格式:val / var 变量名: 类型 = 值
val byte: Byte = 1 // 8位,最大存储数据量是255,存放的数据范围是-128~127之间

// 定义格式:val / var 变量名 = 值
val byte = 1 // 自动推导类型

2. short类型定义

val short: Short = 2 // 16位,最大数据存储量是65536,数据范围是-32768~32767之间

val short = 2 // 自动推导类型

3. char类型定义

val char: Char = 'A' // 16位,存储Unicode码,用单引号赋值

val char = 'A' // 自动推导类型

4. int类型定义

val number: Int = 668 // 32位,最大数据存储容量是2的32次方减1,数据范围是负的2的31次方到正的2的31次方减1

val number = 668 // 自动推导类型

5. float类型定义

val float: Float = 0.5f // 32位,数据范围在3.4e-45~1.4e38,直接赋值时必须在数字后加上f或F

val float = 0.5f // 自动推导类型

6. double类型定义

val double: Double = 0.5 // 64位,数据范围在4.9e-324~1.8e308,赋值时可以加d或D也可以不加

val double = 0.5 // 自动推导类型

7. long类型定义

val long: Long = 123456 // 64位,最大数据存储容量是2的64次方减1,数据范围为负的2的63次方到正的2的63次方减1

val long = 123456 // 自动推导类型

8. boolean类型定义

val bool: Boolean = true // 只有true和false两个取值

val bool = true // 自动推导类型

二、引用数据类型

// 定义格式:val / var 变量名: 类型 = 值
val str: String = "Kotlin Day01"

// 定义格式:val / var 变量名 = 值
val str = "Kotlin Day01" // 自动推导类型

三、常量和变量

  • val:定义常量,不可修改的
  • var:定义变量,可以修改的

四、编译时常量和运行时常量

  • val:运行时常量(类似于Java中的final)
  • const val:编译时常量

Java的final:不可修改,编译时会把引用的值进行复制(编译时能确切知道赋值的具体内容)

Kotlin的val:不可修改,编译时不会把引用的值进行复制,属于运行时常量(因为编译时不能确切知道赋值的具体内容,故而不能进行赋值);而在val前面加const,就相当于Java中的final,编译时会把引用的值进行复制,属于编译时常量(编译时能确切知道赋值的具体内容)

// 在class之外定义
const val TAG = "MainActivity"

五、函数的定义

/**
 * 定义格式:fun 函数名(参数1: 数据类型,...,参数N: 数据类型): 返回值数据类型 {}
 */
fun add(number1:Int, number2:Int):Int {
    return number1 + number2
}

等价于:
fun add(number1:Int, number2:Int) = number1 + number2

六、函数变量(匿名函数)

在Kotlin中函数可以作为变量,也可以作为参数传递。

/**
 * 定义格式:val/var = fun(参数1: 数据类型,...,参数N: 数据类型): 返回数据类型 {}
 */
val calc = fun(number1:Int, number2:Int):Int {
    return number1 - number2
}

等价于:
val calc = fun(number1:Int, number2:Int) = number1 - number2

七、Lambda初探

Lambda表达式就是一个匿名函数

/**
 * Lambda表达式:{ 函数入口参数 -> 返回值 }
 */
val calcLambda = { number1:Int, number2:Int -> number1 - number2 }

// 如果没有入参也没有返回值,则可如下所示:
val printHello = {
    println("Hello World")
}

// Lambda返回值是以函数体的最后一行代码作为返回值
val sum = { arg1:Int, arg2:Int ->
    println("$arg1 + $arg2 = ${arg1 + arg2}")
    arg1 + arg2 // 作为Lambda的返回值返回
}

// Lambda表达式的调用
sum(1,3)
sum.invoke(1,3) // 等价于sum(1,3),这就是运算符重载

注意:在Lambda表达式return,是return整个方法体。

fun main(args: Array<String>) {
    args.forEach {
        // 如果满足条件,则会返回整个函数体,也就是main函数,从而“The End”将不会打印
        if(it == "1") return
        println(it)
    }
    println("The End")
}

// 如果不想返回整个方法体,可以加ForEach@
fun main(args: Array<String>) {
    args.forEach ForEach@{
        // 如果满足条件,则只会返回当前函数体,“The End”将会被打印
        if(it == "1") return@ForEach
        println(it)
    }
    println("The End")
}

Lambda表达式的简化

  • 函数参数调用时最后一个Lambda可以移出去
  • 函数参数只有一个Lambda,调用时小括号可省略
  • Lambda只有一个参数可默认为it
  • 入参、返回值与形参一致的函数可以用函数引用的方式作为实参传入,也就是使用 ::

八、字符串模板

  • 取值

    /**
     * 定义格式:"$取值的变量名"
     */
    val str = "number = $number"
    
  • 无缝拼接

    /**
     * 定义格式:"${ 取值的变量名 }需要拼接的内容"
     */
    val strAppend = "number = ${ number }12121"
    
  • 转义

    /**
     * 定义格式:"\需要转义的内容"
     */
    val strTransfer = "\$number"
    

九、类型转换

var numberStr = "12"
val numberInt = numberStr.toInt() // toFloat(), toDouble等等

十、equals 和 == 以及 ===

  • 字符串比较,equals 可以用 == 替代,它们都是比较的内容是否相等

  • 对象比较,equals 也可以用 == 替代,使用 == 比较,最后还是调用对象里的equals()方法进行比较;如果比较的是对象的引用值,可以使用 === 进行比较

十一、空安全

任何对象都可分为可空和不可空。

/**
 * 可空
 */
fun token():String? { // 在返回数据类型后面加?,表示可空
    return null
}

val token = token()
if(token != null) { // 但在使用过程中,必修判断是否为空,否则token.length将编译不通过
    val length = token.length
}f

或者
val length = token()!!.length // 加!!,表示信任,知道token()返回的数据永远不会为空

亦或者
val length = token()?.length // 加?,表示如果token()返回为null,则直接返回null,不会报错;否者调用length返回具体长度

还可以
token()?: return // 如果为空,则直接返回
/**
 * 不可空
 */
fun token():String {
    return ""
}

val length = token().length

十二、数组(Array)

val intArr = intArrayOf(1,2,3,4,5) // floatArrayOf(), doubleArrayOf()...
val strArr = arrayOf("str1", "str2", "str3")

1. 遍历数组

val strArr = arrayOf("str1", "str2", "str3")
for(str in strArr) {
    System.out.println(str)
}

2.角标遍历数组

val strArr = arrayOf("str1", "str2", "str3")
for(index in strArr.indices) {
    System.out.println(strArr[index])
}

或者
for(index in IntRange(0, strArr.size-1)) {
    System.out.println(strArr[index])
}

或者
for(index in 0..strArr.size-1) {
    System.out.println(strArr[index])
}

或者
for(index in 0 until strArr.size) {
    System.out.println(strArr[index])
}

十三、区间(Range)

  • 一个数学上的概念,表示范围
  • ClosedRange的子类,IntRange最常用
  • 基本写法:
    • 0..100 表示[0, 100]
    • 0 until 100 表示[0, 100)
    • index in 0..100 判断index是否在[0, 100]中
val intRange = IntRange(0, strArr.size) //[0,3] 0,1,2,3
for(index in intRange) {
    Log.e(TAG, "index=" + index)
}

或者
val intRange = 0..strArr.size //[0,3] 0,1,2,3
for(index in intRange) {
    Log.e(TAG, "index=" + index)
}

或者
val intRange = 0 until strArr.size //[0,2] 0,1,2
for(index in intRange) {
    Log.e(TAG, "index=" + index)
}

十四、Lambda再探

// 需求:过滤掉strArr中的""空字符串
// 步骤:1. 创建一个新的数组用来存放空字符串,2. 遍历数组判断字符串是否为空,3. 遍历数组
// 演变过程:
strArr.filter ({ element -> element.isNotEmpty() }).forEach {
	e(it)
}

strArr.filter (){ element -> element.isNotEmpty() }.forEach {
    e(it)
}

strArr.filter { element -> element.isNotEmpty() }.forEach {
    e(it)
}

strArr.filter { it.isNotEmpty() }.forEach {
    e(it)
}

strArr.filter { it.isNotEmpty() }.forEach (::e) // 参数类型能够匹配的情况下可以用 :: 来代替

/**
 * 打印日志
 */
fun e(message: String) {
    Log.e(TAG, message)
}

十五、when表达式

相当于Java版的switch case

val num = 1
when(num) {
    1 -> Log.e(TAG, "is 1")
    is Int -> Log.e(TAG, "is Int")
    in 1..3 -> Log.e(TAG, "in 1..3")
    !in 1..3 -> Log.e(TAG, "not in 1..3")
}

val numStr = when(num) {
    1 -> "1"
    2 -> "2"
    else -> ""
}
Log.e(TAG, "numberStr = $numStr")

十六、类的创建

所有类都最终继承自Any

// 创建类 Person;相当于Java类,并定义了两个属性:name 和 age,而且还定义了两个(get)方法:getName 和 getAge
class Person(val name:String, val age:Int)

val person = Person("Zane", 26)

// 怎样让其拥有get和set方法
class Person(var name:String, var age:Int) // 将val改成var

十七、构造函数重载

class Person(val name:String?, val age:Int)

// 构造函数重载
{
    constructor(name:String):this(name, 0)

    constructor():this(null, 0)
}

val person1 = Person()
val person2 = Person("Hello World")
val person = Person("Zane", 26)

// 改进
class Person {

    var name:String? = null
    var age:Int = 0

    constructor()
    
    constructor(name:String) {
        this.name =name
    }
    
    constructor(name:String, age:Int) {
        this.name = name
        this.age = age
    }
}

val person1 = Person()
val person2 = Person("Hello World")
val person = Person("Zane", 26)

十八、类的成员

class Person {
    var age:Int = 0
    var name:String? = null
}

定义的属性,默认的情况下会拥有get和set方法,如果想私有化属性的get和set方法:

// 定义name属性,默认的情况下就会拥有get和set方法
// 默认情况下,属性是用protected修饰的
private var name:String? = null
    private set //私有化set方法
    private get //私有化get方法,但私有化get方法,须将属性定义为private

示例:判断传参的年龄是否大于0,如果小于0,返回0,否则直接返回

var age:Int = 0
    get() { // 覆写该属性的get方法
        return if(field < 0) 0 else field
    }

属性初始化:

  • 属性的初始化尽量在构造方法中完成

  • 无法在构造方法中初始化,尝试降级为局部变量

  • var 用 lateinit 延迟初始化,val 用 lazy

    class T
    
    class A {
        lateinit var c:String
        lateinit var d:T
        val e:T by lazy { // 在使用该属性的的时候才会初始化
            T()
        }
    }
    
  • 可空类型谨慎用null直接初始化

十九、类的继承

  • 父类需要open才可以被继承
  • 父类方法、属性需要open才可以被覆写
  • 接口、接口方法、抽象类默认就为open
  • 覆写父类(接口)成员需要override关键字
  • 子类(实现类)继承(实现)父类(接口),使用 :
  • 继承类时实际上调用了父类构造方法
  • 类只能单继承,接口可以多实现
// 创建Person类,且须使用open进行修饰
open class Person {
    var age:Int = 0
    var name:String? = null

    constructor(name:String) {
        this.name =name
    }
}

// 创建Student类,继承Person类
class Student(name:String) : Person(name) {

}

val student = Student("Zane")
student.name = "hello world"
student.age = 26
class QQStepView : View {

    constructor(context: Context):this(context, null)

    constructor(context: Context, attributes: AttributeSet?):this(context, attributes, 0)

    constructor(context: Context, attributes: AttributeSet?, defStyle: Int):super(context, attributes, defStyle) {
        // 写代码,获取自定义属性的内容...
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        Log.e("QQStepView", "onDraw")
    }
}

<io.github.kotlin.day01.QQStepView
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
/**
 * 成员属性继承或覆写
 */

abstract class PersonKt(open val age:Int) { // 要想让子类继承或覆写该字段,就需要使用open关键字来修饰
    abstract fun work()
}

class Programmer(age: Int):PersonKt(age) {
    // 除了方法可以被继承或覆写,成员属性也可以被继承或覆写,但在子类中需要加override关键字来修饰,而且父类需要使用open关键字来修饰
    override val age: Int
        get() { return 0}

    override fun work() {
        println("我是码农,我在搬砖...")
    }
}

class Doctor(age: Int): PersonKt(age) {
    override fun work() {
        println("我是医生,我在动手术...")
    }
}

fun main(args: Array<String>) {
    val programmer:PersonKt = Programmer(23)
    programmer.work()
    println(programmer.age)

    val doctor:PersonKt = Doctor(24)
    doctor.work()
    println(doctor.age)
}

接口代理:

  • 接口方法实现交给代理类实现
/**
  * 接口代理
  */

interface Driver {
    fun driver()
}

interface Writer {
    fun writer()
}

class CarDriver: Driver {
    override fun driver() {
        println("开车中...")
    }
}

class PPTWriter: Writer {
    override fun writer() {
        println("写PPT中...")
    }
}

// 接口代理:Driver by driver, Writer by writer,使用 by 关键字
class SeniorManger(val driver: Driver, val writer: Writer): Driver by driver, Writer by writer

fun main(args: Array<String>) {
    val carDriver = CarDriver()
    val pptWriter = PPTWriter()
    val seniorManger = SeniorManger(carDriver, pptWriter)
    seniorManger.driver()
    seniorManger.writer()
}

接口方法冲突:

  • 接口方法可以有默认实现

  • 解决签名一致且返回值相同的冲突,需要子类(实现类)必须覆写冲突方法;签名不一致的冲突,无解!

    super<[父类(接口)名]>.[方法名]([参数列表])
    
interface B {
    fun x():Int = 0
}

interface C {
    fun x():Int = 1
}

abstract class D {
    open fun x() = 2
}

class E(val y:Int = 0): B,C,D() {

    override fun x(): Int {
        println("call x():Int in E")
        if(y > 0) {
            return y
        }else if(y < -200) {
            return super<C>.x()
        }else if(y < -100) {
            return super<B>.x()
        }else {
            return super<D>.x()
        }
    }
}

fun main(args: Array<String>) {
    println(E(3).x())
    println(E(-10).x())
    println(E(-110).x())
    println(E(-10000).x())
}

二十、接口与方法重载

  • 接口,直观理解就是一种约定
  • 不能有状态
  • 必须由类对其进行实现后使用
// 定义接口类,使用interface关键字
interface Callback {

    fun onError(e: IOException)

    fun onSuccess(resultJson: String) {
        // 接口方法可以有默认实现,但该方法子类将不会主动去重载
        Log.e("Callback:", "onSuccess")
    }
}

// 接口类实现
class HttpCallback : Callback {

    // 方法重载,使用override关键字
    override fun onSuccess(resultJson: String) {
        
    }

    // 方法重载,使用override关键字
    override fun onError(e: IOException) {

    }
}

二十一、抽象类

  • 实现了一部分协议的半成品
  • 可以有状态,可以有方法实现
  • 必须由子类继承后使用
// 定义抽象类,使用abstract关键字
abstract class HttpCallback : Callback {

    override fun onSuccess(resultJson: String) {
        // 伪代码:获取类上的泛型,然后使用gson去转成可以直接使用的对象

    }

    abstract fun onSuccess()
}

抽象类和接口的共性:

  • 比较抽象,不能直接实例化
  • 有需要子类(实现类)实现的方法
  • 父类(接口)变量可以接受子类(实现类)的实例赋值

抽象类和接口的区别:

  • 抽象类有状态,接口没有状态
  • 抽象类有方法实现,接口只能有无状态的默认实现
  • 抽象类只能单继承,接口可以多实现
  • 抽象类反映本质,接口体现能力

二十二、匿名内部类

  • Kotlin里的匿名内部类,可以继承一个类,实现多个接口
  • 没有定义名称的内部类,但类名编译时会生成,类似 Outter$1.class
// 定义一个类
open class Test

// 定义接口类
interface Callback {

    fun onError(e: IOException)

    fun onSuccess(resultJson: String)
}

// 定义抽象类
abstract class HttpCallback : Callback {

    override fun onSuccess(resultJson: String) {
        // 伪代码:获取类上的泛型,然后使用gson去转成可以直接使用的对象
        // ...
        
        onSuccess()
    }

    abstract fun onSuccess()
}

class HttpUtil {

    fun get(callback: Callback) {
        callback.onSuccess("成功")
    }
}

val httpUtil = HttpUtil()
// 使用 object 关键字,定义匿名内部类
httpUtil.get(object: HttpCallback(){
    override fun onSuccess() {
		Log.e(TAG, "onSuccess")
    }

    override fun onError(e: IOException) {

    }
})

// Kotlin里的匿名内部类,可以继承一个类,实现多个接口
httpUtil.get(object: Test(), HttpCallback(){
    override fun onSuccess() {
		Log.e(TAG, "onSuccess")
    }

    override fun onError(e: IOException) {

    }
})

二十三、默认参数

  • 为函数参数指定默认值
  • 可以为任意位置的参数指定默认值,但传参时出现歧义,需要使用具名参数
class HttpUtil {
    // cache:Boolean = false,设置默认参数false
    fun get(callback: Callback, url:String, cache:Boolean = false) {
        callback.onSuccess("成功")
    }
}

val httpUtil = HttpUtil()
// 匿名内部类
httpUtil.get(object: HttpCallback(){
    override fun onSuccess() {
        Log.e(TAG, "onSuccess")
    }

    override fun onError(e: IOException) {

    }
},"https://www.baidu.com") // 因为设置了默认参数,所以调用get方法时,cache参数可传可不传

温馨提示:如果默认参数指定得靠前的话,后面的参数就必须使用具名参数进行传值!

fun hello(double: Double = 3.0, vararg ints:Int, string: String) {
    println(double)
    ints.forEach( ::println )
    println(string)
}

val array = intArrayOf(1,2,3,4)
hello(ints=*array,string="1") // 必须使用具名参数进行传值

二十四、具名参数

 class HttpUtil {

    fun get(callback: Callback, url:String = "https://www.baidu.com", cache:Boolean) {
        callback.onSuccess("成功")
    }
}

val httpUtil = HttpUtil()
// 匿名内部类
httpUtil.get(object: HttpCallback(){
    override fun onSuccess() {
        Log.e(TAG, "onSuccess")
    }

    override fun onError(e: IOException) {

    }
},cache = true) // cache = true,就是具名参数

二十五、可变参数

  • 某个参数可以接收多个值
  • 可以不为最后一个参数
  • 如果传参时有歧义,需要使用具名参数
class CalcUtil {
    // vararg表示可变参数
    fun add(vararg nums:Int):Int {
        var sum = 0

        // 方法一
        /*for (num in nums) {
            sum += num
        }*/

        // 方法二
        nums.forEach { sum += it }
        return sum
    }
}

val calcUtil = CalcUtil()
val total = calcUtil.add(1,2,3,4)

Spread Operator:

  • 只支持展开Array

  • 只用于可变参数列表的实参

  • 不能重载

  • 虽然也叫Operator,但不算一般意义上的运算符

    class Calc {
        // vararg表示可变参数
        fun add(vararg nums:Int):Int {
            var sum = 0
    
            // 方法一
            /*for (num in nums) {
                sum += num
            }*/
    
            // 方法二
            nums.forEach { sum += it }
            return sum
        }
    }
    
    val calc = Calc()
    val array = intArrayOf(1,2,3,4)
    val total = calc.add(*array) // Spread Operator
    

二十六、运算符重载

参考文档:kotlinlang.org/docs/operat…

class Counter(val dayIndex: Int) {

    // 加法运算符重载 +
    operator fun plus(increment: Int): Counter {
        return Counter(dayIndex + increment)
    }

    // 减法运算符重载 -
    operator fun minus(reduce: Int): Counter {
        return Counter(dayIndex - reduce)
    }
}
val counter1 = Counter(2)
val counter2 = Counter(6)
val counter = counter1 + counter2.dayIndex

val counterReduce = counter1 - counter2.dayIndex
  • 任何类可以定义或者重载父类的基本运算符
  • 通过运算符对应的具名函数来定义
  • 对参数个数做要求,对参数和返回值类型不做要求
  • 不能像Scala一样定义任意运算符

二十七、伴生对象与静态成员

  • 每个类可以对应一个伴生对象
  • 伴生对象的成员全局独一份
  • 伴生对象的成员类似 Java 的静态成员
  • 静态成员考虑用包级函数、包级变量替代
class Util {
    // 静态伴生对象,仅此一份,相当于java中的static
    companion object {
        // 静态属性
        val baseUrl = "www.baidu.com"

        // 静态方法
        fun add(vararg nums:Int):Int {
            var sum = 0

            // 方法一
            /*for (num in nums) {
                sum += num
            }*/

            // 方法二
            nums.forEach { sum += it }
            return sum
        }
    }
}

// val total = Util.Companion.add(1,2,3) // 在Java类中使用时,需要调用Companion才能调用其中的静态方法
val total = Util.add(1,2,3)

val url = Util.baseUrl

如何在Java类中不调用Companion,也能直接调用静态成员呢?

class Util {
    companion object {
        // 使用@JvmField注解,在Java类中就可以直接调用该静态属性了,而不需要先调用Companion,再调用该静态属性
        @JvmField
        val baseUrl = "www.baidu.com"
        
        // 使用@JvmStatic注解,在Java类中就可以直接调用该静态方法了,而不需要先调用Companion,再调用该静态方法
        @JvmStatic
        fun add(vararg nums:Int):Int {
            var sum = 0
            nums.forEach { sum += it }
            return sum
        }
    }
}

public class StaticJava {
    public static void main(String[] args) {
        int sum = Util.add(1,2,3);
        System.out.println(Util.baseUrl);
    }
}

二十八、类成员扩展

val arrs = arrayOf(1,2,3,4)
// 对类方法进行扩展,因为Array类中并没有isNotEmpty方法
arrs.isNotEmpty()
// 案例:abc字符串进行叠加,如传3,就叠加3次
strContent.mulit(3)
// val strContent = "abc"*3 // 运算符重载以及扩展

// 类方法扩展
fun String.mulit(number: Int):String {
    val sb = StringBuilder()
    for(num in 1..number) {
        sb.append(this) // this表示调用者
    }
    return sb.toString()
}

// “*”运算符重载以及扩展
operator fun String.times(number: Int):String {
    val sb = StringBuilder()
    for(num in 1..number) {
        sb.append(this)
    }
    return sb.toString()
}

// 类属性扩展
val String.str: String
    get() = "abc"

// 类属性扩展的使用
"aaa".str

二十九、内部类

  • 定义在类内部的类

  • 默认是静态内部类,非静态用 inner 关键字

  • 非静态内部类是持有外部类的状态的,静态内部类是不持有外部类的状态的

  • 内部类实例必须依赖外部类的实例,则使用非静态内部类;如果内部类只是逻辑上与外部类有关联,但并不依赖外部类,则可以使用静态内部类

class Outer {
    val name = "Zane_Outer"

    // 默认使用static关键字修饰,相当于Java中的静态类
    /*class Inner {

    }*/
    // 使用inner关键字修饰,表示该类为内部类
    inner class Inner {
        val name = "Zane_Inner"
         fun printName() {
             // 内部类访问外部类成员属性,使用this@外部类名称
             Log.e("Inner", "name = ${this@Outer.name}")
         }
    }
}

// 使用
// Outer.Inner() //静态内部类的调用
Outer().Inner().printName() // 打印“Zane_Outer”

三十、枚举

  • 实例可数的类,注意枚举也是类
  • 可以修改构造,添加成员
  • 可以提升代码的表现力,也有一定的性能开销
enum class LogLevel  {
    VERBOSE,DEBUG,INFO,WARN,ERROR,ASSERT
}

// 等价于
class LogLevel2 protected constructor() {
    companion object {
        val VERBOSE= LogLevel2()
        val DEBUG= LogLevel2()
        val INFO= LogLevel2()
        val WARN= LogLevel2()
        val ERROR= LogLevel2()
        val ASSERT= LogLevel2()
    }
}
enum class LogLevel(val tag:Int)  {
    VERBOSE(0),DEBUG(1),INFO(2),WARN(3),ERROR(4),ASSERT(5);

    // 枚举里也可以定义方法,但枚举列最后必须加;
    fun getTag():String {
        return "$tag"
    }

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

fun main(args: Array<String>) {
    println(LogLevel.DEBUG.getTag())

    println(LogLevel.DEBUG.ordinal)

    LogLevel.values().map(::println)

    println(LogLevel.valueOf("ERROR"))
}

三十一、智能类型转换

val person: Person = Student("zane")
if(person is Student) {
    person.getStudentName()
}
val str1:String? = "zane"
if(str1 != null) {
    System.out.println(str1.length)
}
val person: Person = Person("zane")
// val student:Student = person as Student // 将person强制转换为Student,此时会报类型转换异常
val student:Student? = person as? Student // 将person强制转换为Student,如果不能转换则返回null,但不报类型转换异常

三十二、中缀表达式

  • 只有一个参数,且用infix修饰的函数
  • 可以允许不用 "对象.xx()" 去调用方法
class Book {
    infix fun on(place: String){...}
}

// on是Book类中的方法,但使用了infix关键字修饰,则表示中缀表达式,可不用 "对象.xx()" 去调用该方法
Book() on "My Desk"

温馨提示:如果想自定义运算符,可采用中缀表达式,但不可乱用哟~

三十三、分支表达式(if)

private const val DEBUG = 1
private const val USER = 0
fun main(args: Array<String>) {
    // if表达式是有值返回的,返回的值就是if表达式的各分支的最后一个表达式的值
    val mode = if(args.isNotEmpty() && args[0] == "1") {
        DEBUG
    }else {
        USER
    }
}

三十四、循环语句

  • for循环

    for(arg in args) {
        println(arg)
    }
    
    for((index, value) in args.withIndex()) {
        println("$index -> $value")
    }
    
    for(indexAndValue in args.withIndex()) {
        println("${indexAndValue.index} -> ${indexAndValue.value}")
    }
    
    class MyIterator(val iterator: Iterator<Int>) {
        operator fun next():Int {
            return iterator.next()
        }
    
        operator fun hasNext():Boolean {
            return iterator.hasNext()
        }
    }
    
    class MyIntList() {
        private val list = ArrayList<Int>()
    
        fun add(value:Int) {
            list.add(value)
        }
    
        fun remove(value:Int) {
            list.remove(value)
        }
    
        operator fun iterator():MyIterator {
            return MyIterator(list.iterator())
        }
    }
    
    fun main(args: Array<String>) {
        val list = MyIntList()
        list.add(1)
        list.add(2)
        list.add(3)
    
        for (i in list) {
            println(i)
        }
    }
    
  • while循环

    var x = 15
    // 先判断条件是否满足,后执行执行语句
    while (x > 0) {
        println(x)
        x--
    }
    
    // 先执行执行语句,后判断条件是否满足
    do {
        println(x)
        x--
    }while (x > 0)
    
  • continue和break

    for(arg in args) {
        if(arg == "5") continue // 跳过当前循环体,继续执行下一个循环体
        if(arg == "10") break // 终止整个循环体,不再继续执行
    }
    
    // 多层循环嵌套的终止结合标签使用
    Outter@for(...){
        Inner@while(i<0){ if(...)break@Outter }
    }
    

三十五、异常捕获(try、catch、finally)

try {
    val arg0 = args[0].toInt()
    val arg1 = args[1].toInt()
    println("$arg0 + $arg1 = ${arg0 + arg1}")
}catch (e: NumberFormatException) {
    println("您确定输入的参数为整数吗?")
}catch (e: ArrayIndexOutOfBoundsException) {
    println("您确定输入的是两个整数吗?")
}catch (e: Exception) {
    println("未知错误!")
}finally {
    // 不管程序是否捕获异常与否,都会执行这里
    println("感谢您使用加法计算器!")
}

try、catch也是一个表达式:

val result = try{
    args[0].toInt() / args[1].toInt()
}catch(e:Exception) {
    e.printStackTrace()
    0
}
println(result)

小结(计算器示例)

class Operator(op:String) {
    val onFun:(left: Double, right: Double) -> Double

    init {
        onFun = when(op) {
            "+" -> {l,r -> l + r}
            "-" -> {l,r -> l - r}
            "*" -> {l,r -> l * r}
            "/" -> {l,r -> l / r}
            "%" -> {l,r -> l % r}
            else -> {
                throw UnsupportedOperationException(op)
            }
        }
    }

    // 运算符重载
    operator fun invoke(left: Double, right: Double): Double {
        return onFun(left, right)
    }
}

fun main(args: Array<String>) {
    while (true) {
        println("请输入需要计算的内容,如3 + 4,运算符之间使用空格分隔哟~")
        try {
            val input = readLine()?:break
            // 避免输入内容前后为空时报错
            val splits = input.trim().split(" ")
            if(splits.size != 3) {
                throw IllegalArgumentException("参数个数不对")
            }

            val arg1 = splits[0].toDouble()
            val op = splits[1]
            val arg2 = splits[2].toDouble()

            println("$arg1 $op $arg2 = ${Operator(op)(arg1, arg2)}")

        }catch (e: IllegalArgumentException) {
            println("应输入三个参数的数字,且用空格分割!")
        }catch (e: UnsupportedOperationException) {
            println("暂不支持该运算:${e.message}")
        }catch (e: NumberFormatException) {
            println("您确定输入的是数字?")
        }catch (e: Exception) {
            println("未知异常!")
        }

        println("是否继续?[Y]")
        val cmd = readLine()
        if(cmd == null || cmd.toLowerCase() != "y") {
            break
        }
    }
    println("欢迎再次使用!")
}

三十六、导出可执行程序

在项目build.gradle文件中,添加如下代码:

apply plugin:'application'
mainClassName = "io.github.kotlin.day01.CalcDemoKt" // 包名路径+类名+Kt

然后重新build一下,打开右侧Gradle控制板!我TM不想说了...继续下一个知识点!

三十七、类及其成员的可见性(private、protected、internal、public)

KotlinJava
privateprivate
protectedprotected
-default(包内可见)
internal(模块内可见)-
publicpublic

三十八、Object关键字

  • 只有一个实例的类
  • 不能自定义构造方法
  • 可以实现接口、继承类
  • 本质上就是单例模式最基本的实现
interface onExternalDriverMountListener {
    fun onMount(driver: Driver)
    fun onUnMount(driver:Driver)
}
abstract class Player

object MusicPlayer:onExternalDriverMountListener, Player() {
    val state = 0
    fun player(url: String) {

    }
    fun stop() {

    }
    override fun onMount(driver: Driver) {

    }

    override fun onUnMount(driver: Driver) {

    }
}

三十九、包级函数

  • Kotlin允许不在类中定义的成员属性和方法,称为包级函数/对象
val packStr = "包级函数"

fun packFun():String {
    return packStr
}

class Util {
    
}

Java 怎么调用 Kotlin 的包级对象呢?

// 使用@file:JvmName("自定义类名"),必须定义在导包之前
@file:JvmName("PackKotlin")
package io.github.kotlin.day01

val packStr = "包级函数"

fun packFun():String {
    return packStr
}

class Util {
    
}
public class StaticJava {
    public static void main(String[] args) {
        // Java中调用Kotlin的包级对象
        PackKotlin.getPackStr();
        PackKotlin.packFun();
    }
}

四十、方法重载(Overloads)与默认参数

  • 方法重载:方法名相同、参数类型和个数不同的方法
  • Jvm函数签名的概念:与函数名、参数列表有关,与返回值类型无关
  • 返回值类型不能作为签名的一部分,因此不能定义方法名相同但返回值类型不同的方法
  • 方法重载与默认参数,二者可相互转换
class OverLoad {
    fun a():Int {
        return 0
    }

    fun a(int: Int):Int {
        return int
    }
    
    fun a(int1: Int, int2:Int):Int {
        return int1 + int2
    }
    
    fun a(string: String):Int {
        return string.length
    }
}
class OverLoad {
    // 默认参数
    fun a(int: Int = 0):Int {
        return int
    }
}

fun main(args: Array<String>) {
    val overLoad = OverLoad()
    overLoad.a() // 因为方法里有了默认参数的形参,所以不传实参也是可以的
}

但如果想在Java类中使用Kotlin的默认参数,则需要添加 @JvmOverloads 注解
class OverLoad {
    // 使用@JvmOverloads注解,则可以在Java类中使用Kotlin的默认参数
    @JvmOverloads
    fun a(int: Int = 0):Int {
        return int
    }
}

OverLoad overLoad = new OverLoad();
overLoad.a();

四十一、属性代理

  • 定义方法

    val/var <property name>:<Type> by <expression>
    
  • 代理者需要实现相应的 setValue/getValue方法,具体取决于是代理的 val 还是 var

class Delegates {
    val hello1 by lazy {
        "Hello1"
    }

    // val定义的属性,使用代理时,只需要实现getValue()
    val hello2 by X()

    // var定义的属性,使用代理时,需要实现getValue()和setValue()
    var hello3 by X()
}

class X {
    private var value:String? = null

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("getValue:$thisRef -> $property")
        return value?:""
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("setValue:$thisRef -> $property -> $value")
        this.value = value
    }
}

fun main(args: Array<String>) {
    val delegates = Delegates()
    println(delegates.hello1)
    println(delegates.hello2)
    println(delegates.hello3)
    delegates.hello3 = "hello3"
    println(delegates.hello3)
}

四十二、数据类(data class)

  • 默认实现了 copy、toString等方法

  • 默认实现了componentN方法

    val/var (参数1...参数N) = 实例对象
    
data class Country(val code:Int, val name:String)

class ComponentX() {
    operator fun component1():String {
        return "您好,我是"
    }

    operator fun component2():Int {
        return 1
    }

    operator fun component3():Int {
        return 1
    }

    operator fun component4():Int {
        return 0
    }
}

fun main(args: Array<String>) {
    val country = Country(84, "中国")
    println(country)
    println(country.component1())
    println(country.component2())

    val (code, name) = country
    println(code)
    println(name)

    val componentX = ComponentX()
    val (a,b,c,d) = componentX
    println("$a $b$c$d")
}

在Kotlin中,一般都将data class当作Java中的java bean,但是data class有两个问题:

1、定义一个data class的类,但它默认就是通过final关键字修饰的,这将造成data class类无法被子类继承

2、data class 的类,它默认就没有无参的构造方法

为了解决上述两个问题,官方推出了两款插件:allOpen,noArg插件

如何使用这两款插件:

1、首先在项目级的build.gradle文件中,添加如下代码:

buildscript {
    ...
    
    dependencies {
        ...
        
        classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
        classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
    }
}

apply plugin:'kotlin-noarg'
apply plugin:'kotlin-allopen'

noArg{
    annotation("io.github.kotlin.day01.annotation.Poko")
}
allOpen{
    annotation("io.github.kotlin.day01.annotation.Poko")
}

2、在项目中,新建annotation包,并创建Poko类(可自定义类名)

annotation class Poko

3、重新Rebuild Project

四十三、密封类(sealed class)

  • 密封类是子类可数(有限)的类
  • kotlin v1.1版本之前,子类必须定义为密封类的内部类;v1.1版本之后,子类只需要与密封类在同一个文件中
sealed class PlayerCmd {
    class Play(val url:String, val position:Long): PlayerCmd()

    class Seek(val position: Long): PlayerCmd()

    object Pause: PlayerCmd()

    object Resume: PlayerCmd()

    object Stop: PlayerCmd()
}

或者

sealed class PlayerCmd {
    
}

class Play(val url:String, val position:Long): PlayerCmd()

class Seek(val position: Long): PlayerCmd()

object Pause: PlayerCmd()

object Resume: PlayerCmd()

object Stop: PlayerCmd()

四十四、高阶函数

  • 把函数作为参数传入或者将函数作为返回值的函数
fun main(args: Array<String>) {
    // forEach(无返回结果)
    val list = listOf(1,3,4,5,10,6,2)

    val newList = ArrayList<Int>()

    list.forEach {
        val newElement = it * 2 + 3
        newList.add(newElement)
    }

    newList.forEach(::println)
    
    // map(返回值可为任意类型)
    val list = listOf(1,3,4,5,10,6,2)

    val newList = list.map {
        it * 2 + 3
    }

    val newList2 = list.map(Int::toDouble)

    list.map(::println)
    
    newList.forEach(::println)
    newList2.forEach(::println)
    
    // 需求:将整型集合的集合打平成集合
    // flatMap(返回集合类型)
    val list = listOf(
        1..20,
        2..5,
        100..300)
    
    val flatList = list.flatMap{
        it // 此处的 it 为IntRange类型的区间集合
    }

    flatList.forEach(::println)
    
    // 需求:将打平的集合里的元素进行类型转换
    // flatMap + map
    val list = listOf(
        1..20,
        2..5,
        100..300)

    val flatList = list.flatMap{
        // 此处的 it 为IntRange区间集合
        it.map {
            // 此处的 it 为区间集合里的int类型的元素
            "No:$it"
        }
    }
	// flatList等价于flatList2
    val flatList2 = list.flatMap { intRange ->
        intRange.map {intElement ->
            "No:$intElement"
        }
    }

    flatList.forEach(::println)
    flatList2.forEach(::println)
    
    // 需求:将打平的集合里的元素进行求和
    // reduce
    val list = listOf(
        1..20,
        2..5,
        100..322)

    val flatList = list.flatMap { it }

    println(flatList.reduce{acc, i -> acc + i}) // acc表示运算的结果
    
    // 需求:求阶乘
    // map + reduce
    (0..6).map{it -> factorial(it)}.forEach{it -> println(it)}
    // 等价于
    (0..6).map(::factorial).forEach(::println)
    
    // 需求:获取到阶乘集合之后在求和
    // map + reduce + reduce
    (0..6).map(::factorial).reduce { acc, i -> acc + i }
    
    // 需求:获取到阶乘集合之后,再设置初始值,然后在求和
    // map + reduce + fold
    (0..6).map(::factorial).fold(5){ acc, i -> acc + i}
    
    // 需求:获取到阶乘集合之后,拼接成字符串
    // map + reduce + fold
    (0..6).map(::factorial).fold(StringBuilder()){
        acc, i -> acc.append(i).append(",")
    }
    // 拼接字符串还可以:
    (0..6).joinToString(",")
    
    // 需求:获取到阶乘集合之后,将元素倒序拼接成字符串
  	// map + reduce + foldRight
    (0..6).map(::factorial).foldRight(StringBuilder()) {
        i, acc -> acc.append(i).append(",")
    }
    
    // 需求:获取到阶乘集合之后,过滤出基数元素
    // map + reduce + filter
    (0..6).map(::factorial).filter { i -> i % 2 ==1 }
    
    // 需求:获取到阶乘集合之后,过滤出基数位置上的元素
    // map + reduce + filterIndexed
    (0..6).map(::factorial).filterIndexed{ index,i -> index % 2 == 1 }
    
    // 需求:获取到阶乘集合之后,如果遇到第一个不符合基数的条件,则结束取数据
    (0..60).map(::factorial).takeWhile { it % 2 == 1 }
}

// 求阶乘
fun factorial(n: Int):Int {
    if(n == 0) return 1
    return (1..n).reduce{acc, i -> acc * i }
}
// 需求:判断函数返回的对象是否为null,如果对象不为null,则将对象里的成员属性一一打印出来
// data class + let
data class PersonData(val name:String, val age:Int)

fun findPerson(): PersonData? {
    return null
}

fun main(args: Array<String>) {
    findPerson()?.let { (name, age) ->
        println(name)
        println(age)
    }
}

// 需求:判断函数返回的对象是否为null,如果对象不为null,则直接调用对象里的方法,并直接将对象里的成员属性一一打印出来
// data class + apply
data class PersonData(val name:String, val age:Int) {
    fun work() {
        println("$name is working!")
    }
}

fun findPerson(): PersonData? {
    return PersonData("zane", 27)
}

fun main(args: Array<String>) {
    findPerson()?.apply {
        work()
        println(name)
        println(age)
    }
}

// 需求:获取本地文件内容,并打印出来
// with
val br = BufferedReader(FileReader("hello.txt"))
with(br){ // 持有BufferedReader对象,with作用域里就不需要显示声明使用对象去调用成员属性或方法
    var line:String?
    while (true) {
        line = readLine()?:break
        println(line)
    }
    close()
}

// 等价于
val br = BufferedReader(FileReader("hello.txt"))
var line:String?
while (true) {
    line = br.readLine()?:break
    println(line)
}

// 需求:获取本地文件内容,并打印出来(简化版)
// use(使用use可以省略close)
版本一:val br = BufferedReader(FileReader("hello.txt")).readText()
版本二:val br = BufferedReader(FileReader("hello.txt")).readLines() // 读取文件中的所有行,并返回List集合
版本三:
BufferedReader(FileReader("hello.txt")).use {
    var line:String?
    while (true){
        line = it.readLine()?:break
        println(line)
    }
}

四十五、尾递归优化

  • 尾递归:函数在调用自身之后,无其它操作
  • tailrec 关键字提示编译器尾递归优化
// 尾递归
data class ListNode(val value:Int, var next:ListNode? = null)

tailrec fun findListNode(headNode:ListNode?, value: Int): ListNode? {
    // 如果头节点为null,则直接返回null
    headNode?: return null
    // 如果当前节点的value与之匹配,则返回当前节点
    if(headNode.value == value) return headNode
    // 否则尾递归继续查找下一个节点
    return findListNode(headNode.next,value) // 调用自身之后,无其它操作,这就是尾递归
}

fun main(args: Array<String>) {
    val MAX_NODE_COUNT = 100000
    val headNode = ListNode(0)
    var p = headNode
    for (i in 1..MAX_NODE_COUNT) {
        p.next = ListNode(i)
        p = p.next!!
    }

    println(findListNode(headNode, MAX_NODE_COUNT -2)?.value)
}
// 不是尾递归
fun factorial(n:Long):Long {
    return n * factorial(n-1) //这不是尾递归,因为在调用自身时,还与 n 相乘了
}

// 不是尾递归
data class TreeNode(val value:Int) {
    val left:TreeNode? = null
    val right:TreeNode? = null
}

fun findTreeNode(root:TreeNode?, value:Int):TreeNode? {
    root?:return null
    if(root.value == value) return root
    return findTreeNode(root.left,value)?: return findTreeNode(root.right,value) // 这也不是尾递归,因为调用自身之后,满足条件时还会去调用自身
}

四十六、闭包

  • 函数运行的环境
  • 持有函数运行状态
  • 函数内部可以定义函数
  • 函数内部也可以定义类
fun makeFun():() -> Unit {
    var count = 0
    return fun(){
        println(++count)
    }
}

fun main(args: Array<String>) {
    val x = makeFun()
    x()
    x()
    x()
    x()
}
fun fibonacci():() -> Long {
    var first = 0L
    var second = 1L
    return fun():Long {
        val result = second
        second += first
        first = second - first
        return result
    }
}

fun main(args: Array<String>) {
    val x = fibonacci()
    println(x())
    println(x())
    println(x())
    println(x())
    println(x())
    println(x())
}
fun fibonacci(): Iterable<Long> {
    var first = 0L
    var second = 1L
    return Iterable {
        object : LongIterator() {
            override fun hasNext() = true

            override fun nextLong(): Long {
                val result = second
                second += first
                first = second - first
                return result
            }
        }
    }
}

fun main(args: Array<String>) {
    for (i in fibonacci()) {
        if(i > 100) break
        println(i)
    }
}
fun add(x:Int) = fun(y:Int)= x + y

// 等价于
fun add(x:Int):(Int)->Int {
    // 函数内部还可以定义类
    //data class Person(val name:String, val age: Int)
    
    return fun(y:Int):Int {
        return x + y
    }
}

fun main(args: Array<String>) {
    val sum = add(6)
    println(sum(2))
}

四十七、函数复合

// f(g(x)) m(x) = f(g(x))

val add = fun(i:Int):Int{
    return i + 5
}
// 等价于
val add = {i:Int -> i + 5} // g(x)

val multiply = fun(i:Int):Int{
    return i * 2
}
// 等价于
val multiply = { i:Int -> i * 2} // f(x)

fun main(args: Array<String>) {
    println(multiply(add(9))) // (5+9)*2
}
val add = {i:Int -> i + 5} // g(x)

val multiply = { i:Int -> i * 2} // f(x)

fun main(args: Array<String>) {
    val addMultiply = add andThen multiply
    println(addMultiply(9)) // m(x) = f(g(x))
    
    val addComposeMultiply = add compose multiply
    println(addComposeMultiply(9)) // m(x) = g(f(x))
}

/**
 * 知识点:
 * 1、扩展方法:Function1<P1, P2>.andThen
 * 2、中缀表达式:infix
 * 3、Function1:表示只能传入一个参数的方法
 * 4、Function1<P1, P2>:P1:表示参数类型,P2:表示返回值类型
 * 5、P1、P2、R:表示参数、参数、返回值
 */
infix fun<P1,P2, R> Function1<P1, P2>.andThen(function: Function1<P2, R>):Function1<P1, R> {
    return fun(p1: P1):R {
        return function.invoke(this.invoke(p1))
    }
}

// 反函数复合
infix fun<P1,P2,R> Function1<P2,R>.compose(function: Function1<P1,P2>):Function1<P1,R> {
    return fun(p1: P1):R {
        return this.invoke(function.invoke(p1))
    }
}

四十八、科理化(Currying)-函数调用链

  • 简单说就是多元函数变换成一元函数调用链
fun hello(x:String, y:Int, z:Double):Boolean {
    println("$x $y $z")
    return true
}

// 科理化
// 等价于
fun curriedHello(x:String):(y:Int) -> (z:Double) -> Boolean {
    return fun(y:Int):(z:Double) -> Boolean {
        return fun(z:Double):Boolean {
            println("$x $y $z")
            return true
        }
    }
}

// 等价于
fun curriedHello(x:String):(y:Int) -> (z:Double) -> Boolean
    = fun(y:Int):(z:Double) -> Boolean
    = fun(z:Double): Boolean{
        println("$x $y $z")
        return true
    }

// 等价于
fun curriedHello(x:String)
    = fun(y:Int)
    = fun(z:Double):Boolean {
        println("$x $y $z")
        return true
    }

fun main(args: Array<String>) {
    hello("TAG", 0, 2.0)
    curriedHello("TAG")(11)(3.0)
}
fun log(tag:String, target: OutputStream, message:Any?){
    target.write("[$tag]:$message\n".toByteArray())
}

// 科理化
// 等价于
fun logCurried(tag:String):(target:OutputStream)->(message:Any?) -> Unit{
    return fun(target:OutputStream):(message:Any?) ->Unit {
        return fun(message:Any?){
            target.write("[$tag]:$message\n".toByteArray())
        }
    }
}

// 等价于
fun logCurried(tag:String):(target:OutputStream) ->(message:Any?) -> Unit
    = fun(target:OutputStream):(message:Any?) -> Unit
    = fun(message:Any?) {
        target.write("[$tag]:$message\n".toByteArray())
    }

// 等价于
fun logCurried(tag:String)
    = fun(target:OutputStream)
    = fun(message:Any?) {
        target.write("[$tag]:$message\n".toByteArray())
    }

// 等价于
fun logCurried(tag:String)
    = fun(target:OutputStream)
    = fun(message:Any?)
    = target.write("[$tag]:$message\n".toByteArray())

fun main(args: Array<String>) {
    log("TAG", System.out, "Hello Log")
    logCurried("TAG")(System.out)("Hello LogCurried")
}
fun log(tag:String, target: OutputStream, message:Any?){
    target.write("[$tag]:$message\n".toByteArray())
}

// 科理化
// 扩展方法
fun <P1,P2,P3,R> Function3<P1,P2,P3,R>.curried()
    = fun(p1:P1) = fun(p2:P2) = fun(p3:P3) = this(p1,p2,p3)

fun main(args: Array<String>) {
    ::log.curried()("TAG")(System.out)("Hello curried") // ::log表示函数的引用
}

四十九、偏函数

  • 传入部分参数得到的新函数

需求:针对上面的科理化log函数的案例,我们发现了一个问题,tag和target这两个属性其实是不变的,如果每次调用的话都需要传一遍,这就不符合我们的代码风格了(简洁丫)

fun log(tag:String, target: OutputStream, message:Any?){
    target.write("[$tag]:$message\n".toByteArray())
}

// 科理化
// 扩展方法
fun <P1,P2,P3,R> Function3<P1,P2,P3,R>.curried()
    = fun(p1:P1) = fun(p2:P2) = fun(p3:P3) = this(p1,p2,p3)

fun main(args: Array<String>) {
    val consoleLogWithTag = ::log.curried()("TAG")(System.out)
    consoleLogWithTag("Hello 偏函数")
}
val makeString = fun(byteArray:ByteArray, charset:Charset): String {
    return String(byteArray, charset)
}

val makeStringFromGbkBytes = makeString.partial2(charset("GBK"))

// 扩展方法
fun <P1,P2,R> Function2<P1,P2,R>.partial2(p2:P2) = fun(p1:P1) = this(p1,p2)

fun main(args: Array<String>) {
    val bytes = "我是中国人".toByteArray(charset("GBK"))
    val stringFromGBK = makeStringFromGbkBytes(bytes)
    println(stringFromGBK)
}

小结:统计文件内字符串个数

fun main(args: Array<String>) {
    val map = HashMap<Char,Int>()
    File("build.gradle").readText().toCharArray().filterNot(Char::isWhitespace).forEach {
        val count = map[it]
        if(count == null) map[it] = 1
        else map[it] = count + 1
    }

    map.forEach(::println)
}

// 简化版
fun main(args: Array<String>) {
    File("build.gradle").readText().toCharArray().filterNot(Char::isWhitespace).groupBy {
        it
    }.map {
        it.key to it.value.size
    }.forEach(::println)
}