Kotlin初学

Posted by BY annie dong on October 15, 2018

kotlin学习


为什么要使用kotlin

  • 减少模板代码
  • 安全,避免空指针异常
  • 互操作性: 充分利用 JVM、Android 和浏览器的现有库
  • 工具友好: 可用任何 Java IDE 或者使用命令行构建

Kotlin安装

brew update
brew install kotlin

HelloWorld Kotlin

  • touch hello.kt //kotlin的文件用kt结尾并加入内容:
fun main(args: Array<String>) {
    println("Hello, World!")
}
  • 编译并运行hello.kt
kotlinc hello.kt -include-runtime -d hello.jar   //编译
//-d:用来设置编译输出的名称,可以是 class 或 .jar 文件,也可以是目录
//-include-runtime : 让 .jar 文件包含 Kotlin 运行库,从而可以直接运行。

java -jar hello.jar     //运行
kotlin -classpath hello.jar HelloKt //也可以运行,HelloKt为编译器为hello.kt文件生成的默认类名

Kotlin交互式解释器REPL

> kotlinc-jvm
Welcome to Kotlin version 1.2.71 (JRE 1.8.0_172-b11)
Type :help for help, :quit for quit
>>> 2+2
4
>>>

Kotlin作为脚本语言使用

Kotlin 也可以作为一个脚本语言使用,文件后缀名为 .kts

例如我们创建一个名为 list_folders.kts,代码如下:

import java.io.File

val folders = File(args[0]).listFiles { file -> file.isDirectory() }
folders?.forEach { folder -> println(folder) }

执行时通过 -script 选项设置相应的脚本文件。
kotlinc -script list_folders.kts <path_to_folder>

Kotlin语法

默认导入包

有多个包会默认导入到每个 Kotlin 文件中:

  • kotlin.*
  • kotlin.annotation.*
  • kotlin.collections.*
  • kotlin.comparisons.*
  • kotlin.io.*
  • kotlin.ranges.*
  • kotlin.sequences.*
  • kotlin.text.*

函数定义

函数定义使用关键字 fun,参数格式为 参数 : 类型

fun sum(a: Int, b: Int): Int {   // Int 参数,返回值 Int
    return a + b
}

表达式作为函数体

fun sum(a: Int, b: Int) = a + b

public fun sum(a: Int, b: Int): Int = a + b   // public 方法则必须明确写出返回类型

无返回值的函数(类似Java中的void)

fun printSum(a: Int, b: Int): Unit { 
    print(a + b)
}


// 如果是返回 Unit类型,则可以省略(对于public方法也是这样):
public fun printSum(a: Int, b: Int) { 
    print(a + b)
}

可变长参数函数

函数的变长参数可以用 vararg 关键字进行标识:

fun vars(vararg v:Int){
    for(vt in v){
        print(vt)
    }
}

// 测试
fun main(args: Array<String>) {
    vars(1,2,3,4,5)  // 输出12345
}

lambda(匿名函数)

// 测试
fun main(args: Array<String>) {
    val sumLambda: (Int, Int) -> Int = {x,y -> x+y}
    println(sumLambda(1,2))  // 输出 3
}

定义常量与变量

var <标识符> : <类型> = <初始化值>     //var 关键字,可变变量定义
val <标识符> : <类型> = <初始化值>     //val 关键字,不可变变量定义,类似Java final变量

val a: Int = 1
val b = 1       // 系统自动推断变量类型为Int
val c: Int      // 如果不在声明时初始化则必须提供变量类型
c = 1           // 明确赋值

var x = 5        // 系统自动推断变量类型为Int
x += 1           // 变量可修改

Kotlin注释

Kotlin 支持单行和多行注释,实例如下:

// 这是一个单行注释

/* 这是一个多行的
   块注释。 */

字符串模板

$ 表示一个变量名或者变量值
$varName 表示变量值
${varName.fun()} 表示变量的方法返回值

var a = 1
// 模板中的简单名称:
val s1 = "a is $a" 

a = 2
// 模板中的任意表达式:
val s2 = "${s1.replace("is", "was")}, but now is $a"

NULL检查机制

Kotlin的空安全设计对于声明可为空的参数,在使用时要进行空判断处理,有两种处理方式,字段后加!!像Java一样抛出空异常,另一种字段后加?可不做处理返回值为 null或配合?:做空判断处理

//类型后面加?表示可为空
var age: String? = "23" 
//抛出空指针异常
val ages = age!!.toInt()
//不做处理返回 null
val ages1 = age?.toInt()
//age为空返回-1
val ages2 = age?.toInt() ?: -1

当一个引用可能为 null 值时, 对应的类型声明必须明确地标记为可为 null。
当 str 中的字符串内容不是一个整数时, 返回 null:

fun parseInt(str: String): Int? {
  // ...
}

类型检测及自动类型转换

使用 is 运算符检测一个表达式是否某类型的一个实例(类似于Java中的instanceof关键字)

fun getStringLength(obj: Any): Int? {
  if (obj is String) {
    // 做过类型判断以后,obj会被系统自动转换为String类型
    return obj.length 
  }

  //在这里还有一种方法,与Java中instanceof不同,使用!is
  // if (obj !is String){
  //   // XXX
  // }

  // 这里的obj仍然是Any类型的引用
  return null
}


fun getStringLength(obj: Any): Int? {
  if (obj !is String)
    return null
  // 在这个分支中, `obj` 的类型会被自动转换为 `String`
  return obj.length
}

区间

for (i in 1..4) print(i) // 输出“1234”

for (i in 4..1) print(i) // 什么都不输出

if (i in 1..10) { // 等同于 1 <= i && i <= 10
    println(i)
}

// 使用 step 指定步长
for (i in 1..4 step 2) print(i) // 输出“13”

for (i in 4 downTo 1 step 2) print(i) // 输出“42”


// 使用 until 函数排除结束元素
for (i in 1 until 10) {   // i in [1, 10) 排除了 10
     println(i)
}

基本数据类型

基本数字类型

Kotlin的基本数字类型包括 Byte、Short、Int、Long、Float、Double等:

类型 类型
Double 64
Float 32
Long 64
Int 32
Short 16
Byte 8

下面是所有类型的字面常量:

  • 十进制:123
  • 长整型以大写的 L 结尾:123L
  • 16 进制以 0x 开头:0x0F
  • 2 进制以 0b 开头:0b00001011
    注意:8进制不支持
    Kotlin 同时也支持传统符号表示的浮点数值:
  • Doubles 默认写法: 123.5, 123.5e10
  • Floats 使用 f 或者 F 后缀:123.5f
    你可以使用下划线使数字常量更易读:
val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
比较两个数字

在 Kotlin 中,三个等号 === 表示比较对象地址,两个 == 表示比较两个值大小

fun main(args: Array<String>) {
    val a: Int = 10000
    println(a === a) // true,值相等,对象地址相等

    //经过了装箱,创建了两个不同的对象
    val boxedA: Int? = a
    val anotherBoxedA: Int? = a

    //虽然经过了装箱,但是值是相等的,都是10000
    println(boxedA === anotherBoxedA) //  false,值相等,对象地址不一样
    println(boxedA == anotherBoxedA) // true,值相等

    val a = 10000//不显示声明类型不会封装新对象
    val boxedA = a
    val anotherBoxedA = a
    println(boxedA === anotherBoxedA) //  true
}


类型转换
val b: Byte = 1 // OK, 字面值是静态检测的
val i: Int = b // 错误

val b: Byte = 1 // OK, 字面值是静态检测的
val i: Int = b.toInt() // OK
每种数据类型都有下面的这些方法,可以转化为其它的类型:
toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char
位运算

对于Int和Long类型,还有一系列的位操作符可以使用,分别是:

  • shl(bits) – 左移位 (Java’s «)
  • shr(bits) – 右移位 (Java’s »)
  • ushr(bits) – 无符号右移位 (Java’s »>)
  • and(bits) – 与
  • or(bits) – 或
  • xor(bits) – 异或
  • inv() – 反向

字符

Kotlin 中的 Char 不能直接和数字操作,Char 必需是单引号 ‘ 包含起来的。比如普通字符 ‘0’,’a’:

fun check(c: Char) {
    if (c == 1) { // 错误:类型不兼容
        // ……
    }
}

字符字面值用单引号括起来: ‘1’。 特殊字符可以用反斜杠转义。 支持这几个转义序列:\t、 \b、\n、\r、'、"、\ 和 $。 编码其他字符要用 Unicode 转义序列语法:’\uFF00’。

我们可以显式把字符转换为 Int 数字:

fun decimalDigitValue(c: Char): Int {
    if (c !in '0'..'9')
        throw IllegalArgumentException("Out of range")
    return c.toInt() - '0'.toInt() // 显式转换为数字
}

当需要可空引用时,像数字、字符会被装箱。装箱操作不会保留同一性。

布尔

布尔用 Boolean 类型表示,它有两个值:true 和 false。
若需要可空引用布尔会被装箱。

|| – 短路逻辑或
&& – 短路逻辑与
! - 逻辑非

数组

数组用类 Array 实现,并且还有一个 size 属性及 get 和 set 方法,由于使用 [] 重载了 get 和 set 方法,所以我们可以通过下标很方便的获取或者设置数组对应位置的值。
数组的创建两种方式:一种是使用函数arrayOf();另外一种是使用工厂函数。如下所示,我们分别是两种方式创建了两个数组:

fun main(args: Array<String>) {
    //[1,2,3]
    val a = arrayOf(1, 2, 3)
    //[0,2,4]
    val b = Array(3, { i -> (i * 2) })

    //读取数组内容
    println(a[0])    // 输出结果:1
    println(b[1])    // 输出结果:2
}

如上所述,[] 运算符代表调用成员函数 get() 和 set()。
注意: 与 Java 不同的是,Kotlin 中数组是不型变的(invariant)。 除了类Array,还有ByteArray, ShortArray,IntArray,用来表示各个类型的数组,省去了装箱操作,因此效率更高,其用法同Array一样:

val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]

字符串

和 Java 一样,String 是不可变的。

fun main(args: Array<String>) {
    val text = """   //Kotlin 支持三个引号 """ 扩起来的字符串,支持多行字符串
    |多行字符串
    |多行字符串
    """
    println(text)   // 输出有一些前置空格
    println(text.trimMargin())    // 前置空格删除了
    //默认 | 用作边界前缀,但你可以选择其他字符并作为参数传入,比如 trimMargin(">")

    //方括号 [] 语法可以很方便的获取字符串中的某个字符,也可以通过 for 循环来遍历
    for (c in str) {  
      println(c)
    }
}

字符串模板

fun main(args: Array<String>) {
    val i = 10
    val s = "i = $i" // 求值结果为 "i = 10"
    println(s)

    val s = "runoob"
    val str = "$s.length is ${s.length}" // 求值结果为 "runoob.length is 6"

    val price = """
    ${'$'}9.99
    """
    println(price)  // 求值结果为 $9.99
}

If条件控制

// 传统用法
var max = a 
if (a < b) max = b

// 使用 else 
var max: Int
if (a > b) {
    max = a
} else {
    max = b
}
 
// 作为表达式
val max = if (a > b) a else b

val max = if (a > b) {
    print("Choose a")
    a
} else {
    print("Choose b")
    b
}

When 表达式

//类似switch
when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> { // 注意这个块
        print("x 不是 1 ,也不是 2")
    }
}

//如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔
when (x) {
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
}

when (x) {
    in 1..10 -> print("x is in the range")
    in validNumbers -> print("x is valid")
    !in 10..20 -> print("x is outside the range")
    else -> print("none of the above")
}

fun hasPrefix(x: Any) = when(x) {
    is String -> x.startsWith("prefix")
    else -> false
}

//如果不提供参数,when 也可以用来取代 if-else if链
when {
    x.isOdd() -> print("x is odd")
    x.isEven() -> print("x is even")
    else -> print("x is funny")
}

For 循环

for (item in collection) print(item)

for (item: Int in ints) {
    // ……
}

//如果你想要通过索引遍历一个数组或者一个 list
for (i in array.indices) {
    print(array[i])
}

for ((index, value) in array.withIndex()) {
    println("the element at $index is $value")
}

while 与 do…while 循环

while( 布尔表达式 ) {
  //循环内容
}

do {
       //代码语句
}while(布尔表达式)

Kotlin 类

class Runoob {  // 类名为 Runoob
    // 大括号内是类体构成
}
//我们也可以定义一个空类
class Empty

class Runoob() {
    fun foo() { print("Foo") } // 成员函数
}

类的属性

class Runoob {
    var name: String = ……
    var url: String = ……
    var city: String = ……
}

val site = Runoob() // Kotlin 中没有 new 关键字
site.name           // 使用 . 号来引用
site.url

//Koltin 中的类可以有一个主构造器,以及一个或多个次构造器,主构造器是类头部的一部分,位于类名称之后

class Person constructor(firstName: String) {}
//如果主构造器没有任何注解,也没有任何可见度修饰符,那么constructor关键字可以省略。
class Person(firstName: String) {}

var allByDefault: Int? // 错误: 需要一个初始化语句, 默认实现了 getter 和 setter 方法
var initialized = 1    // 类型为 Int, 默认实现了 getter 和 setter
val simple: Int?       // 类型为 Int ,默认实现 getter ,但必须在构造函数中初始化
val inferredType = 1   // 类型为 Int 类型,默认实现 getter

//getter setter
class Person {
    var no: Int = 100
        get() = field                // 后端变量
        set(value) {
            if (value < 10) {       // 如果传入的值小于 10 返回该值
                field = value
            } else {
                field = -1         // 如果传入的值大于等于 10 返回 -1
            }
        }
}

// 测试
fun main(args: Array<String>) {
    var person: Person = Person()
    person.no = 9
    println("no:${person.no}")

    person.no = 20
    println("no:${person.no}")

}

类的构造器

class Person constructor(firstName: String) {
    init {
        println("FirstName is $firstName")
    }
}

//可以通过主构造器来定义属性并初始化属性值(可以是var或val)
class People(val firstName: String, val lastName: String) {
    //...
}

class Person { 
    constructor(parent: Person) {     //次构造函数
        parent.children.add(this) 
    }
}


class Person(val name: String) {
    constructor (name: String, age:Int) : this(name) {  //在同一个类中代理另一个构造函数使用 this 关键字
        ...
    }
}

class DontCreateMe private constructor () {} //构造函数修饰符

/*在 JVM 虚拟机中,如果主构造函数的所有参数都有默认值,编译器会生成一个附加的无参的构造函数,这个构造函数会直接使用默认值。这使得 Kotlin 可以更简单的使用像 Jackson 或者 JPA 这样使用无参构造函数来创建类实例的库*/
class Customer(val customerName: String = "")

抽象类

open class Base {
    open fun f() {}
}

abstract class Derived : Base() {
    override abstract fun f()
}

嵌套类

class Outer {                  // 外部类
    private val bar: Int = 1
    class Nested {             // 嵌套类
        fun foo() = 2
    }
}

fun main(args: Array<String>) {
    val demo = Outer.Nested().foo() // 调用格式:外部类.嵌套类.嵌套类方法/属性
    println(demo)    // == 2
}

内部类

class Outer {
    private val bar: Int = 1
    var v = "成员属性"
    /**嵌套内部类**/
    inner class Inner {
        fun foo() = bar  // 访问外部类成员
        fun innerTest() {
            var o = this@Outer //获取外部类的成员变量
            println("内部类可以引用外部类的成员,例如:" + o.v)
        }
    }
}

fun main(args: Array<String>) {
    val demo = Outer().Inner().foo()
    println(demo) //   1
    val demo2 = Outer().Inner().innerTest()   
    println(demo2)   // 内部类可以引用外部类的成员,例如:成员属性
}

匿名内部类

class Test {
    var v = "成员属性"
    fun setInterFace(test: TestInterFace) {
        test.test()
    }
}

/**
 * 定义接口
 */
interface TestInterFace {
    fun test()
}

fun main(args: Array<String>) {
    var test = Test()

    /**
     * 采用对象表达式来创建接口对象,即匿名内部类的实例。
     */
    test.setInterFace(object : TestInterFace {
        override fun test() {
            println("对象表达式创建匿名内部类的实例")
        }
    })
}

类的修饰符

classModifier: 类属性修饰符,标示类本身特性。
abstract    // 抽象类  
final       // 类不可继承,默认属性
enum        // 枚举类
open        // 类可继承,类默认是final的
annotation  // 注解类

accessModifier: 访问权限修饰符
private    // 仅在同一个文件中可见
protected  // 同一个文件中或子类可见
public     // 所有调用的地方都可见
internal   // 同一个模块中可见

类的继承

Kotlin 中所有类都继承该 Any 类,它是所有类的超类,对于没有超类型声明的类是默认超类, Any 默认提供了三个函数:

  • equals()
  • hashCode()
  • toString()
class Example // 从 Any 隐式继承

open class Base(p: Int)           // 如果一个类要被继承,可以使用 open 关键字进行修饰
class Derived(p: Int) : Base(p)

//子类有主构造函数
open class Person(var name : String, var age : Int){}// 基类
class Student(name : String, age : Int, var no : String, var score : Int) : Person(name, age) {}

//子类没有主构造函数
class Student : Person {
    constructor(ctx: Context) : super(ctx) {
    } 
    constructor(ctx: Context, attrs: AttributeSet) : super(ctx,attrs) {
    }
}

重写

在基类中,使用fun声明函数时,此函数默认为final修饰,不能被子类重写。如果允许子类重写该函数,那么就要手动添加 open 修饰它, 子类重写方法使用 override 关键词

/**用户基类**/
open class Person{
    open fun study(){       // 允许子类重写
        println("我毕业了")
    }
}
/**子类继承 Person 类**/
class Student : Person() {
    override fun study(){    // 重写方法
        println("我在读大学")
    }
}

属性重写

可以用一个var属性重写一个val属性,但是反过来不行。因为val属性本身定义了getter方法,重写为var属性会在衍生类中额外声明一个setter方法

你可以在主构造函数中使用 override 关键字作为属性声明的一部分

interface Foo {
    val count: Int
}

class Bar1(override val count: Int) : Foo
class Bar2 : Foo {
    override var count: Int = 0
}

----

open class Foo {
    open val x: Int get { …… }
}
class Bar1 : Foo() {
    override val x: Int = ……
}

Kotlin 接口

interface MyInterface {
    var name:String //name 属性, 抽象的

    fun bar()    // 未实现
    fun foo() {  //已实现
      // 可选的方法体
      println("foo")
    }
    fun say() { print("say") } // 已实现
}

class Child : MyInterface {
    override fun say() {  super<MyInterface>.say() }   // 重写
    override var name: String = "runoob" //重载属性
    override fun bar() {
        // 方法体
    }
}

扩展函数

Kotlin 可以对一个类的属性和方法进行扩展,且不需要继承或使用 Decorator 模式

class User(var name:String)

/**扩展函数**/
fun User.Print(){
    print("用户名 $name")
}

fun main(arg:Array<String>){
    var user = User("Runoob")
    user.Print()
}

/* 扩展函数是静态解析的,并不是接收者类型的虚拟成员,在调用扩展函数时,具体被调用的的是哪一个函数,由调用函数的的对象表达式来决定的,而不是动态的类型决定的 */
open class C
class D: C()

fun C.foo() = "c"   // 扩展函数 foo
fun D.foo() = "d"   // 扩展函数 foo

fun printFoo(c: C) {
    println(c.foo())  // 类型是 C 类
}

fun main(arg:Array<String>){
    printFoo(D()) // => c
}

//若扩展函数和成员函数一致,则使用该函数时,会优先使用成员函数
class C {
    fun foo() { println("成员函数") }
}

fun C.foo() { println("扩展函数") }

fun main(arg:Array<String>){
    var c = C()
    c.foo() // => 成员函数
}

//扩展一个空对象
fun Any?.toString(): String {
    if (this == null) return "null"
    // 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()
    // 解析为 Any 类的成员函数
    return toString()
}
fun main(arg:Array<String>){
    var t = null
    println(t.toString())
}

扩展属性

val <T> List<T>.lastIndex: Int
    get() = size - 1
 
//不允许被初始化,只能由显式提供的 getter/setter 定义
val Foo.bar = 1 // 错误:扩展属性不能有初始化器

伴生对象的扩展

类内部的对象声明可以用 companion 关键字标记,这样它就与外部类关联在一起,我们就可以直接通过外部类访问到对象的内部元素。
一个类里面只能声明一个内部关联对象,即关键字 companion 只能使用一次。

class MyClass {
    companion object { }  // 将被称为 "Companion"
}

fun MyClass.Companion.foo() {
    println("伴随对象的扩展函数")
}

val MyClass.Companion.no: Int
    get() = 10

fun main(args: Array<String>) {
    println("no:${MyClass.no}")
    MyClass.foo()
}

扩展的作用域

package foo.bar
fun Baz.goo() { …… } 

---
package com.example.usage

import foo.bar.goo // 导入所有名为 goo 的扩展
import foo.bar.*   //  foo.bar 导入一切

fun usage(baz: Baz) {
    baz.goo()
}

扩展声明为成员

class D {
    fun bar() { println("D bar") }
}

class C {
    fun baz() { println("C baz") }

    fun D.foo() {
        bar()   // 调用 D.bar
        baz()   // 调用 C.baz
    }

    fun caller(d: D) {
        d.foo()   // 调用扩展函数
    }
}

fun main(args: Array<String>) {
    val c: C = C()
    val d: D = D()
    c.caller(d) // D bar C baz
}

数据类

数据类关键字data, 编译器会自动的从主构造函数中根据所有声明的属性提取以下函数:

  • equals() / hashCode()
  • toString() 格式如 “User(name=John, age=42)”
  • componentN() functions 对应于属性,按声明顺序排列
  • copy() 函数
    如果这些函数在类中已经被明确定义了,或者从超类中继承而来,就不再会生成。

为了保证生成代码的一致性以及有意义,数据类需要满足以下条件:

  • 主构造函数至少包含一个参数。
  • 所有的主构造函数的参数必须标识为val 或者 var ;
  • 数据类不可以声明为 abstract, open, sealed 或者 inner;
  • 数据类不能继承其他类 (但是可以实现接口)。
data class User(val name: String, val age: Int) //关键字为 data
fun copy(name: String = this.name, age: Int = this.age) = User(name, age) val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
println(jack)    // => User(name=Jack, age=1)
println(olderJack) // => User(name=Jack, age=2)

密封类

使用 sealed 修饰类,密封类可以有子类,但是所有的子类都必须要内嵌在密封类中。

sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

fun eval(expr: Expr): Double = when (expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
}

泛型

class Box<T>(t: T) {
    var value = t
}

//创建类的实例时我们需要指定类型参数
val box: Box<Int> = Box<Int>(1)
// 或者
val box = Box(1) // 编译器会进行类型推断,1 类型 Int,所以编译器知道我们说的是 Box<Int>。


fun main(args: Array<String>) {
    val age = 23
    val name = "runoob"
    val bool = true

    doPrintln(age)    // 整型
    doPrintln(name)   // 字符串
    doPrintln(bool)   // 布尔型
}

fun <T> doPrintln(content: T) {
    when (content) {
        is Int -> println("整型数字为 $content")
        is String -> println("字符串转换为大写:${content.toUpperCase()}")
        else -> println("T 不是整型,也不是字符串")
    }
}

//泛型约束
fun <T : Comparable<T>> sort(list: List<T>) {
    // ……
}
sort(listOf(1, 2, 3)) // OK。Int 是 Comparable<Int> 的子类型
sort(listOf(HashMap<Int, String>())) // 错误:HashMap<Int, String> 不是 Comparable<HashMap<Int, String>> 的子类型

// 明处的类型变异使用协变注解修饰符:in、out,消费者 in, 生产者 out。
// 使用 out, 协变类型参数只能用作输出,可以作为返回值类型但是无法作为入参的类型
class Runoob<out A>(val a: A) {
    fun foo(): A {
        return a
    }
}

fun main(args: Array<String>) {
    var strCo: Runoob<String> = Runoob("a")
    var anyCo: Runoob<Any> = Runoob<Any>("b")
    anyCo = strCo
    println(anyCo.foo())   // 输出 a
}

// in 使得一个类型参数逆变,逆变类型参数只能用作输入,可以作为入参的类型但是无法作为返回值的类型
class Runoob<in A>(a: A) {
    fun foo(a: A) {
    }
}

fun main(args: Array<String>) {
    var strDCo = Runoob("a")
    var anyDCo = Runoob<Any>("b")
    strDCo = anyDCo
}

//如果类型定义为interface Function<in T, out U> , 那么可以出现以下几种星号投射
Function<*, String> , 代表 Function<in Nothing, String> ;
Function<Int, *> , 代表 Function<Int, out Any?> ;
Function<, > , 代表 Function<in Nothing, out Any?> .

Kotlin 枚举类

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}


enum class ProtocolState {
    WAITING {
        override fun signal() = TALKING
    },

    TALKING {
        override fun signal() = WAITING
    };

    abstract fun signal(): ProtocolState
}

enum class RGB { RED, GREEN, BLUE }
//可以使用 enumValues<T>() 和 enumValueOf<T>() 函数以泛型的方式访问枚举类中的常量
inline fun <reified T : Enum<T>> printAllValues() {
    print(enumValues<T>().joinToString { it.name })
}

fun main(args: Array<String>) {
    printAllValues<RGB>() // 输出 RED, GREEN, BLUE
    var color:Color=Color.BLUE
    println(Color.values()) // 以数组的形式,返回枚举值
    println(Color.valueOf("RED")) 
    //转换指定 name为枚举值,若未匹配成功,会抛出IllegalArgumentException
    println(Color.valueOf("RED"))
    println(color.name)          //获取枚举名称
    println(color.ordinal)       //获取枚举值在所有枚举数组中定义的顺序
}

Kotlin 对象表达式和对象声明

class C {
    // 私有函数,所以其返回类型是匿名对象类型
    private fun foo() = object {
        val x: String = "x"
    }

    // 公有函数,所以其返回类型是 Any
    fun publicFoo() = object {
        val x: String = "x"
    }

    fun bar() {
        val x1 = foo().x        // 没问题
        val x2 = publicFoo().x  // 错误:未能解析的引用“x”
    }
}

//作用域
fun countClicks(window: JComponent) {
    var clickCount = 0
    var enterCount = 0

    window.addMouseListener(object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
            clickCount++
        }

        override fun mouseEntered(e: MouseEvent) {
            enterCount++
        }
    })
    // ……
}

//以下实例中,两个对象都输出了同一个 url 地址
object Site {
    var url:String = ""
    val name: String = "菜鸟教程"
}
fun main(args: Array<String>) {
    var s1 =  Site
    var s2 = Site
    s1.url = "www.runoob.com"
    println(s1.url)
    println(s2.url)
}

kotlin 委托

// 创建接口
interface Base {   
    fun print()
}

// 实现此接口的被委托的类
class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}

// 通过关键字 by 建立委托类
class Derived(b: Base) : Base by b

fun main(args: Array<String>) {
    val b = BaseImpl(10)
    Derived(b).print() // 输出 10
}

--- 

import kotlin.reflect.KProperty
// 定义包含属性委托的类
class Example {
    var p: String by Delegate()
}

// 委托的类
class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, 这里委托了 ${property.name} 属性"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$thisRef 的 ${property.name} 属性赋值为 $value")
    }
}
fun main(args: Array<String>) {
    val e = Example()
    println(e.p)     // 访问该属性,调用 getValue() 函数

    e.p = "Runoob"   // 调用 setValue() 函数
    println(e.p)
}

class C {
    var prop: Type by MyDelegate()
}

// 这段是由编译器生成的相应代码:
class C {
    private val prop$delegate = MyDelegate()
    var prop: Type
        get() = prop$delegate.getValue(this, this::prop)
        set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}

延迟属性 Lazy

val lazyValue: String by lazy {
    println("computed!")     // 第一次调用输出,第二次调用不执行
    "Hello"
}

fun main(args: Array<String>) {
    println(lazyValue)   // 第一次执行,执行两次输出表达式
    println(lazyValue)   // 第二次执行,只输出返回值
}

可观察属性 Observable

import kotlin.properties.Delegates

class User {
    var name: String by Delegates.observable("初始值") {
        prop, old, new ->
        println("旧值:$old -> 新值:$new")
    }
}

fun main(args: Array<String>) {
    val user = User()
    user.name = "第一次赋值"
    user.name = "第二次赋值"
}


=> 旧值:初始值 -> 新值:第一次赋值
=> 旧值:第一次赋值 -> 新值:第二次赋值

把属性储存在映射中

class Site(val map: Map<String, Any?>) {
    val name: String by map
    val url: String  by map
}

fun main(args: Array<String>) {
    // 构造函数接受一个映射参数
    val site = Site(mapOf(
        "name" to "菜鸟教程",
        "url"  to "www.runoob.com"
    ))
    
    // 读取映射值
    println(site.name)
    println(site.url)
}

Not Null

//notNull 适用于那些无法在初始化阶段就确定属性值的场合。
class Foo {
    var notNullBar: String by Delegates.notNull<String>()
}

foo.notNullBar = "bar"
println(foo.notNullBar) //需要注意,如果属性在赋值前就被访问的话则会抛出异常。

局部委托属性

class MyReadOnlyPropertyImpl : ReadOnlyProperty<MyTestClass, String> {
    override fun getValue(thisRef: MyTestClass, property: KProperty<*>): String {
        val s  = "aaa"
        return s;
    }
}
class MyProvider {
    operator fun provideDelegate(thisRef: MyTestClass, prop: KProperty<*>): ReadOnlyProperty<MyTestClass, String> { 
        println("do something") // 这行代码是在getValue方法之外调用的
        val myReadOnlyPropertyImpl = MyReadOnlyPropertyImpl() // 这里才是调用getValue方法的地方
        return myReadOnlyPropetyImpl
    }
}
class MyTestClass {
    val myProvider = MyProvider()
    val myField1: String by myProvider
}
// 测试
fun main(args: Array<String>) {    
    val myTestClass = MyTestClass()
    println(myTestClass.myField1)
}

=> do something
=> aaa

kotlin

  1. 在kotlin中各种map, filter其实都是使用的for(item in list)的方式
  2. Calling multiple methods on an object instance (‘with’)
    val myTurtle = Turtle()
    with(myTurtle) { //draw a 100 pix square
     penDown()
     for(i in 1..4) {
         forward(100.0)
         turn(90.0)
     }
     penUp()
    }