Kotlin学习记录

Kotlin的生态

  • Android开发
  • 服务端开发
  • 前端开发
  • 原生环境开发

设计哲学

  • 静态类型与类型推导 所有表达式的类型在编译期已经确定了,而编译器就能验证对象是否包含了你想访问的方法或者宇段。 与Java相比(JDK8↓),拥有型推导,不需要你在源代码中显式地声明每个变量,变量类型可 以根据上下文来自动判断的类型。

  • null安全 Kotlin 的类型系统跟踪那些可以或不可以为 null 的 值,并且禁止那些运行时可能导致 NullPointerException 的操作。(把类型标记为可空的只要在类型尾部增加一个字符?)

  • 互操作性 与Java,现成库拥有极高互操作性,Kotlin的类和方法可以像常规的Java 类和方法一样被调用。(Kotlin 没有自己的集合库,它完全依赖 Java 标准库中的类)

  • 没有checked exception 要求checked exception可以提高开发人员的工作效率,又可以提高代码质量,但是大型软件项目的经验表明了不同的结果-checked exception降低了生产力,代码质量很少或根本没有增加。

参考:https://kotlinlang.org/docs/reference/exceptions.html

  • 面向表达式编程 Kotlin中的流程控制不仅是普通语句,它们可以返回值,如if表达式、when表达式、try表达式等,而且还有范围表达式(1..100),中缀表达式(a to b)。一切皆表达式的设计让开发者在设计业务时,促进了避免创造副作用的逻辑设计,从而让程序变得更加安全;而且表达式通常也具有更好的表达能力,典型的一个例子就是表达式更容易进行组合。

Kotlin与Java

  • 关键宇 fun 用来声明一个函数,参数的类型写在它 的名称后面
  • 数组就是类,和 Java 不同, Kotlin 没有声明数组类型的特殊语法
  • 在 Kotlin 中,除了循环( for, do 和 do/while)以外大多数控制结构( if,When )都是表达式
//表达式与语句:表达式有值,并且能作为另一个表达式的一部分使用;而语句总是包围着它的代码块中的顶层元素,并且没有自己的值。

fun max(a : Int , b: Int) = if (a > b) a else b
  • 以关键字开始,然后是变量名称。(var age:Int = 20)
  • val-不可变引用,var-可变引用
  • 值对象简单声明:class Person(val name: String)
  • 头等属性,省略getter/setter
  • 智能转换:使用 is 检查来判断一个变量是否是某种类型,Kotlin编译器检查过一个变量是某种类型,后面就不再需要转换它,可以就把它当作你检查过的类型使用。(对比Java instanceOf 之后需要显示转换)
  • when:when对比switch:switch只能使用枚举常量,字符串或者数字字面值;when 允许使用任何对象并且不需要在每个分支都写上break 语句。when是表达式且可以与if连用….
//when
fun evalWhen(e: Expr): Int =
when (e) {
    is Num ->
        e.value //智能转换
    is Sum ->
        evalWhen(e.right) + evalWhen(e.left)
    else ->
        throw RuntimeException("类型错误")
}
  • for:Kotlin没有Java的for-i概念,多出来区间与数列

    • 区间
     for (i in 1..100) {
         println("current:$i")
     }
    
    • downTo step until
    //从100 到 1 打印偶数 步长2 向下到1
    for (i in 100 downTo 1 step 2) {
        println("current:$i")
    }
    
  • 异常

    Kotlin 中 throw 结构是一个表达式,不必使用 new 关键字来创建异常实例。(try也是) Kotlin 并不区分受检异常和未受检异常。不用指定函数抛出的异常 , 而且可以处理也可以不处理异常。

函数

  • 命名参数 当调用一个 Kotlin定义的函数时,可以显式地标明一些参数的名称,避免混淆
joinToString(collectio, separator ="", prefix ="", postfix ="·")
  • 默认参数值 对比Java一些类的重载函数很多, Kotlin中可以在声明函数的时候,指定参数的默认值,这样就可以避免创建重载的函数。
fun <T> joinToString(
    collection: Collection<T>,
    separator: String =",",//默认参数值
    prefix: String ="",
    postfix: String = ""
    ) : String{
            
}
  • 顶层函数|参数 在Java中,类处于顶层,类包含属性和方法,在Kotlin中,函数处于顶层,我们可以直接把函数放在代码文件的顶层,让它不从属于任何类,任何类都可以调用。 目的,消除了Java中常见的静态工具类,使我们的代码更加整洁。
@file:JvmName("StrUtil")
package util

fun joinToStr(collection: Collection<String>): String{
    //....
    return "";
}
import util.joinToStr

fun main(args: Array<String>){
    joinToStr(collection = listOf("123", "456"))
}

因为在Java中,类还是必须要存在的,所以编译器将Str.kt文件里的代码放在了一个自动生成的类中,然后把我们定义的Kotlin的函数作为静态方法放在其中,在Java中是先通过import导入这个类,然后通过类名.方法名来调用。可以通过@file:JvmName注解来自定义类名。

  • 扩展函数|参数
/**
 * 扩展函数
 *
 *  String-接收者类型           get(this.length -1) 对接收者对象(字符串)进行操作并返回
 */
fun String.lastChar() : Char = get(this.length -1)

fun main(args: Array<String>) {
    System.out.println("Kotlin".lastChar())
}

导入扩展函数(Kotiin 允许用和导入类一样的语法来导入)

import strings.lastChar
  • to 中缀调用 infix 修饰符标识可以使用中缀调用,to 函数会返回一个 Pair 类型的对象用来表示一对元素(解构声明)
fun <K, V> mapOf(vararg values: Pair<K, V>): Map<K , V>
//使用
mapOf(1 to "One",2 to "Two")

类-对象

Kotlin 在类名后面使用冒号来代替了 Java 中的 extends 和 implements 关键字。

  • Java的类和方法默认是open的,而Kotlin中默认都是final的。

目的:避免脆弱的基类问题(类被重写方法的风险)。Kotlin采用了open,final 和 abstract 修饰符

编译期强制使用override修饰符

修饰符 相关成员 使用
final 不能被重写 类中成员默认使用
open 可以被重写 需要明确地表明
abstract 必须被重写 只能在抽象类中使用:抽象成员不能有实现
override 重写父类或接口中的成员 如果没有使用 final 表明,亟写的成员默认是开放的
  • 可见性修饰符:默认为 public ,模块修饰符internal

internal:只在模块内部可见,一个模块就是一组一起编译的 Kotiin 文件(一个Maven或Gradle项目,IDEA模块…)

  • Kotlin编译器生成:数据类,委托类
//重写toString,equals,hashCode
override fun toString() = "..."
/**
 * 数据类,编译器重写toString 、equals 和 hashCode 。 
 * equals 用来比较实例
 * hashCode 用来作为例如 HashMap 这种基于哈希容器的键
 * toString 用来为类生成按声明顺序排列的所有字段的字符串表达形式
 *  copy 方法: copy对象的同时修改某些属性的值
 */
data class Client(val name: String, val postalCode: Int)
/**
 * 类委托:可以使用 by 关键字将接口的实现委托到另一个对象(避免装饰器模式带来的重复模板代码)
 *  
 *类相当于innerList的包装类,拥有innerList的实现,并可以覆盖原有方法提供不同实现
 */
 class DelegatingCollection<T>(innerList: Collection<T> = ArrayList<T>()) : Collection<T> by innerList {}
  • object关键字与伴生对象

“伴生”是相较于一个类而言的,意为伴随某个类的对象,它属于这个类所有,因此伴生对象跟Java中static修饰效果性质一样,全局只有一个单例。它需要声明在类的内部,在类被装载时会被初始化。

/**
 * object关键字 对象声明:创建单例类
 */
object Payroll {
 ...
 }
 
/**
 * 伴生对象companion:工厂方法
 */
//构造方法私有化
class User private constructor(val nickname: String) {
    //声明伴生对象:工厂方法创建对象
    companion object {
        fun newSubscribingUser(email: String) =
                User(email.substringBefore("@"))

        fun newFacebookUser(accountld: Int) =
                User(accountld.toString())
    }
}

/**
 * 普通类的伴生对象:伴生对象是一个声明在类中的普通对象,有名字(默认Companion ),可以实现一个接口或者有扩展函数或属性 。 伴生对象(与包级别函数和属性一起)替代了 Java 静态方法和字段定义
 */
class Prize(val name: String, val count: Int, val type: Int) {
	companion object {
		val TYPE_REDPACK = 0
		val TYPE_COUPON = 1
		fun isRedpack(prize: Prize): Boolean {
			return prize.type == TYPE_REDPACK
		}
    }
 }
fun main(args: Array<String>) {
	val prize = Prize("红包", 10, Prize.TYPE_REDPACK)
    print(Prize.isRedpack(prize))
}

/**
 * 对象表达式:(匿名内部类)
 */
val Listener = object: MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { .. . }
override fun mouseEntered(e: MouseEvent) { ... }
}
  • 延迟初始化:by lazy与lateinit
/**
* by lazy : val变量的初始化
* lazy的背后是接受一个lambda并返回一个Lazy <T>实例的函数,第一次访问该属性时,会执行lazy对应的Lambda表达式并记录* 结果。
* lazy属性默认LazyThreadSafetyMode.SYNCHRON IZED(同步锁)
*/
class Bird(val weight: Double, val age: Int, val color: String) {
    val sex: String by lazy {
         if (color == "yellow") "male" else "female"
	}
}

/**
* lateinit : var变量的初始化
*/
lateinit var sex: String 
fun printSex() {
     this.sex = if (this.color == "yellow") "male" else "female"
}

/**
 * Delegates.notNull<T> :基本数据类型变量初始化
 */
var test by Delegates.notNull<Int>()
fun doSomething() {
	test = 1
}

Lamdba

  • Kotlin与Java Lamdba区别

    1. 在Kotiin中Lamdba不会仅限于访问 final 变量,在 lambda 内部也可以修改这些变量(lambda捕捉)
    2. 部分操作符增强:[kotlin]count = [java]filter + count
  • 惰性集合操作Sequence 例如 map 和 filter操作符,每一步的中间结果都被存储在一个临时列表,Sequence可以避免创建这些临时中间对象(序列中的元素求值是惰性的)。 注意: Sequence 只提供了 一个方法, iterator。

  • 关于惰性求值:

    1. 惰性求值是逐个处理元素
    2. 延期操作,调用末端操作(count,sum…)的时候才会求值

iterator 与 Sequence 区别:

/**
 * 处理序列的中间操作函数是不进行任何计算的。相反,它们会返回上一个中间操作处理后产生的新序列。
 * 所有这些一系列中间计算都将在终端操作执行中被确定,例如常见的终端操作toList或count.在另一方面,处理Iterable的每个中间操作函数都是会返回一个新的集合。
 *
 */
interface Iterable<out T> {
    operator fun iterator(): Iterator<T>
}
interface Sequence<out T> {
    operator fun iterator(): Iterator<T>
}
//惰性求值,避免创建临时中间对象
 val list = persons.asSequence()
            .map(Person::name)
            .filter { it.startsWith("l") }
            .toList()
//惰性求值:给定序列中 的前一个元素,这个函数会计算出下一个元素。
val naturalNumbers = generateSequence(0) { it + 1 }
println("求100 以内的和:${naturalNumbers.takeWhile { it <= 100 }.sum()}")
  • with 和 apply
/**
 * 使用给定的[receiver]作为接收器调用指定的函数[block]并返回其结果。
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

val returnAToZ = with(StringBuilder()) {
        for (letter in 'A'..'Z') {
            append(letter)
        }
        toString()
}

/**
 * 使用`this`值作为接收器调用指定的函数[block]并返回`this`值。
 */
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

val returnAToZ = StringBuilder().apply {
        for (letter in 'A'..'Z') {
            append(letter)
        }
}

高阶Lamdba

  • 高阶函数

一个函数返回另一个函数作为结果

        fun foo(x: Int): (Int) -> Int {
            return { y: Int -> x + y }
        }

执行foo函数之后,会返回另一个类型为(Int)-> Int的函数

  • 柯里化(Currying)

柯里化指的是把接收多个参数的函数变换成一系列仅接收单一参数函数的过程,在返回最终结果值之前,前面的函数依次接收单个参数,然后返回下一个新的函数;柯里化是为了简化Lambda演算理论中函数接收多参数而出现的,它简化了理论,将多元函数变成了一元;

        fun sum(x: Int) = { y: Int ->
            { z: Int -> x + y + z }
        }
        sum(1)(2)(3)

在kotlin中,如果参数不止一个,且最后一个参数为函数类型时,就可以采用类似柯里化风格的调用

        fun curryingLike(content: String, block: (String) -> Unit) {
            block(content)
        }
        curryingLike("looks like currying style") {
            content ->
            println(content)
        }
        // 运行结果
        looks like currying style
        //也可以等值为
        curryingLike("looks like currying style",{
            content ->
            println(content)
        }) 

  • 内联函数(消除lambda运行时开销)

开销:每调用 一次 lambda 表达式,一个额外的类就会被创建。并且如果 lambda 捕捉了某个变量,那么每次调用的时候都会创建一个新的对象 。 这会带来运行时的额外开销,导致使用 lambda 比使用 一个直接执行相同代码的函数效率更低 。

inline

使用 inline 修饰符标记一个函数,在函数被使用的时候编译器并不会生成函数调用的代码,而是使用函数实现的真实代码替换每一次的函数调用。

@kotlin.internal.InlineOnly
public inline fun <T> Lock.withLock(action: () -> T): T {
    lock()
    try {
        return action()
    } finally {
        unlock()
    }
}

内联函数的限制

作为参数的 lambda表达式的函数体会被直接替换到最终生成的代码中。如果(lambda)参数在某个地方被保存起来,lambda 表达式的代码将不能被内联。如果(lambda)参数被调用,这 样的代码能被容易地内联。

JVM优化 对于普通的函数调用,JVM己经提供了强大的内联支持。在将宇节码转换成机器代码时自动完成内联。使用inline 关键字只能提高带有 lambda 参数的函数的性能。将带有 lambda 参数的函数内联,节约了为 lambda 创建匿名类,以及创建 lambda 实例对象的开销。

  • 函数中的控制流

lambda 中使用 return 关键字,是非局部返回(从调用 lambda 的函数中返回,并不只是从 lambda 中返回 )。只有在以 lambda 作为 参数的函数是内联函数的时候才能从更外层的函数返回。

标签返回

/**
* tag@标签:return 会跳转到引用的标签lamdba 而不会跳出整个函数
*/
fun lookForAlice(s:List<String>){
   s.forEach tag@{
       if (it == "Alice") return@tag
   }
   println("I find Alice")
}

匿名函数:默认使用局部返回

/**
 * 使用匿名函数lambda
 */
fun lookForAlice2(s:List<String>){
    s.forEach(fun (s){
        //return指向最近的匿名函数
        if (s == "Alice") return
    })
    println("I find Alice")
}

字符串

  • 原生字符串

Kotlin中支持原生字符串的语法,用这种3个引号定义的字符串,最终的打印格式与在代码中所呈现的格式一致,而不会解释转化转义字符

        val html = """<html>
                        <body>
                            <p>Hello World.</p>
                        </body>
                    </html>
                """
  • 字符串模板
fun message(name: String, lang: String) = "Hi ${name}, welcome to ${lang}! "
>>> message("Shaw", "Kotlin")
Hi Shaw, welcome to Kotlin!