Kotlin官方参考整理——05.其他

5.1 解构声明

见《03类和对象2.md》->“解构声明”

5.2 集合

与大多数语言不同,Kotlin区分只读集合和可读写集合,这有助于消除bug和设计良好的API。

只读 可读写
List<out T> MutableList<T>
Set<out T> MutableSet<T>
Map<K, out V> MutableMap<K, V>

以List集合为例:

//只读集合
val list = listOf(1, 2, 3)                  //list的类型是List<out T>,即只支持读,不支持写。泛型被out修饰,因此List<out T>是一个可协变的类型
//list.add(4)                               //编译报错,不允许写操作

//可读写集合
val mutableList = mutableListOf(1, 2, 3)    //mutableList的类型是MutableList<T>,支持读写。MutableList<T>不可协变也不可逆变
mutableList.add(4)

可读写集合是对应的只读集合的子类,因此可以将一个可读写集合的对象赋值给一个只读集合的引用:

val numbers: MutableList<Int> = mutableListOf(1, 2, 3)
val readOnlyView: List<Int> = numbers//MutableList是List的子类,因此可以这样做
println(numbers)        // 输出 "[1, 2, 3]"

numbers.add(4)
println(readOnlyView)   // 输出 "[1, 2, 3, 4]"

//readOnlyView.add(5)   // 编译报错,不允许写操作

可以为可读写集合创建一个只读的副本:

val mutableList = mutableListOf<String>()
val list = mutableList.toList()

集合的一些习惯用法

val items = listOf(1, 2, 3, 4)
print(items.first())
print(items.last())
//filter接受一个函数为参数,函数类型为“(T) -> Boolean”
//filter会将集合中的每一个元素分别传入该函数,如果函数返回true,则保留该元素
items.filter { it % 2 == 0 } // 返回 [2, 4]

val rwList = mutableListOf(1, 2, 3)
//返回集合中非null的元素,这里会返回[1, 2, 3]
rwList.requireNoNulls()
//rwList.none { it > 6 }:集合中是否没有大于6的元素
if (rwList.none { it > 6 }) println("No items above 6") //输出“No items above 6”
//返回集合中的第一个元素,如果集合为空则返回null
val item = rwList.firstOrNull()

val readWriteMap = hashMapOf("foo" to 1, "bar" to 2)
println(readWriteMap["foo"]) //输出“1”

5.3 区间

见《01开始.md》->“使用区间”

5.4 类型的检查与转换

见《01开始.md》->“类型检测与转换”

5.5 this表达式

  • 类的成员中,this指的是该类的当前对象
  • 扩展函数或者带接收者的函数字面值中,this表示调用时位于“.”左侧的接收者对象

带标签的this表达式参考《03类和对象2.md》->“嵌套类、内部类、匿名内部类”

5.6 相等性

Kotlin中有两种类型的相等性:

  • 引用相等(两个引用指向同一对象则引用相等)
  • 结构相等(也可称为值相等。调用equals(),如果返回true则结构相等)

引用相等由===(否定形式为!==)来判断。a===b当且仅当a和b指向同一个对象时为true。

结构相等由==(否定形式为!=)来判断。如果a不是null则调用equals函数,如果a是null则查看b是否为null。

当然了,==和===两端的操作数必须是同一类型,否则无法通过编译,提示类型不兼容。

5.7 操作符重载

类似于C++,Kotlin也支持操作符重载。重载操作符可以通过类的成员函数或扩展函数来进行。重载操作符的函数前面需要加上“operator”修饰符。

操作符重载一般用得不多,详见官方参考。

5.8 空安全

见《01开始.md》->“空安全”

5.9 异常

Kotlin中异常的使用方式与Java中基本一样,要注意的主要是以下几点:

Kotlin中的所有异常都是非受检异常,没有受检异常

Java中的异常分为受检异常(Checked Exception)和非受检异常(Unchecked Exception)。

  • 受检异常:必须被显式地捕获或者传递的异常。Java中大部分的异常都是受检异常。
  • 非受检异常:无须被显式地捕获或者传递的异常。Java中的非受检异常只有一种,即RuntimeException及其子类。

Kotlin中的try…语句可以当做表达式使用

val a: Int? = try {
    parseInt(input)
} catch (e: NumberFormatException) {
    null
}

try…表达式的值是try块中的最后一个表达式的值或者是(所有)catch块中的最后一个表达式的值。finally块中的内容不会影响try…表达式的值。

在Kotlin中throw也可以当做表达式使用

val s = person.name ?: throw IllegalArgumentException("Name required")

throw表达式的类型是特殊类型Nothing。该类型没有值,而是用于标记永远不能达到的代码位置(即程序运行时永远到达不了需要用到该值地方)。你也可以使用Nothing来标记一个永远不会返回的函数:

fun fail(message: String): Nothing {
    throw IllegalArgumentException(message)
}

//注意:此处s的类型是非空类型,因为fail函数的类型是Nothing
//如果person.name为空的话,那么会执行fail函数,而fail函数是永远不可能返回的,也就是说只要执行了fail函数,那么永远执行不到将等号右边表达式的值赋给s这一步
//因此,只要执行了将等号右边表达式的值赋给s这一步,说明person.name必然是非空的,那么s也就必然是非空的
val s = person.name ?: fail("Name required")
println(s)

5.10 注解

定义注解的方式与Java中不同,详见官方参考。

5.11 反射

官方参考讲的很简略,了解即可

反射允许在运行时自省你的程序的结构。所谓自省即在运行时获悉一个名称、或属性、或函数的类型。

在使用反射之前,请确保已将kotlin-reflect.jar添加到了项目的classpath中。

5.11.1 类引用

类引用是KClass的实例。

  • 通过 类名或对象名::class 可以获得Kotlin的类类型(即KClass)的实例
  • 通过 类名或对象名::class.java 可以获得Java的类类型(即Class)的实例
class SampleClass

fun test() {
    //获得KClass的实例
    val kotlinClass: KClass<SampleClass> = SampleClass::class
    kotlinClass.constructors//获取构造方法
    KClass没有提供获取方法和属性的途径
    ...

    //获得Class的实例
    val javaClass: Class<SampleClass> = SampleClass::class.java
    javaClass.getConstructor()//获取构造方法
    javaClass.getField()//获取属性
    javaClass.getMethod()//获取成员方法
    ...

    val sampleClass = SampleClass()
    sampleClass::class//获得KClass的实例
    sampleClass::class.java//获得Class的实例
}

5.11.2 函数引用

函数引用是KFunction的实例。

当我们有一个命名函数声明如下:

fun isOdd(x: Int) = x % 2 != 0

我们可以很容易地直接调用它,如isOdd(5);但是我们也可以通过“::”操作符来获取函数的引用,并将函数引用传递给另一个函数:

val numbers = listOf(1, 2, 3)
//filter接受一个“(Int)->Boolean”类型的函数为参数
println(numbers.filter(::isOdd))

如果一个函数是类的成员函数或扩展函数,则“::”前必须加上类名或对象名作为限定:

class Haha {
    fun test() {}
}

fun test() {
    val method1 = Haha::test    //通过类来获取方法引用
    method1(Haha()) //调用时需要传入方法所属的类的对象

    val method2 = Haha()::test  //通过对象来获取方法引用
    method2()       //调用时无需传入方法所属的类的对象
}

5.11.3 属性引用

属性引用是KProperty(对于val)KMutableProperty(对于var)的实例。KMutableProperty是KProperty的子类。

通过“::”运算符来获取属性引用,获得属性引用之后,可以调用其getter和setter等方法。如果属性是类的成员,则::前要加上类名或对象名作为限定:

var m = 3

class AA {
    var x = 1
    val y = 2

    fun test() {
        /**
         * 通过类名获取成员变量的引用
         */
        //可读写成员变量
        AA::x.get(this)//需要传入AA的对象,这里使用了this
        AA::x.set(this, 2)
        //只读成员变量
        AA::y.get(this)
        //AA::y.set()//y是只读的,无此方法

        /**
         * 通过对象名获取成员变量的引用
         */
        val aa = AA()
        aa::x.get()
        aa::x.set(2)
        aa::y.get()
        //aa::y.set()

        /**
         * 获取顶层变量的引用
         */
        ::m.get()
        ::m.set(1)
    }
}

5.11.4 构造函数引用

构造函数可以像方法和属性那样引用。格式:::类名

class Foo

//接受一个无参并返回Foo类型对象的函数为参数
fun function(factory: () -> Foo) {
    val x: Foo = factory()
}

fun test(){
    function(::Foo)//将Foo的构造函数传递给function函数
}

5.12 类型安全的构建器

了解,详见官方参考

构建器可以让我们以非常简单明晰的格式来创建一段复杂的html代码字符串、xml代码字符串等。

fun result(args: Array<String>) =
    html {
        head {
            title { +"XML encoding with Kotlin" }
        }

        body {
            h1 { +"XML encoding with Kotlin" }

            p { +"this format can be used as an alternative markup to XML" }

            a(href = "http://kotlinlang.org") { +"Kotlin" }

            p {
                +"This is some"
                b { +"mixed" }
                +"text. For more see the"
                a(href = "http://kotlinlang.org") { +"Kotlin" }
                +"project"
            }

            p { +"some text" }

            p {
                for (arg in args)
                    +arg
            }
        }
    }

这是完全合法的Kotlin代码。下面我们看看它的实现原理。

首先想想为什么我们可以在代码中这样写:

html {
    ...
}

html实际上是一个函数调用,它接受一个lambda表达式作为参数。该函数定义如下:

//init需要通过一个HTML对象来调用,因此在init函数内部,可以直接调用HTML类中的函数,不需要任何前缀
fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()
    html.init()
    return html
}

这个函数接受一个名为init的函数为参数,该函数参数的类型是HTML.() -> Unit,它是一个带接收者的函数类型,这意味着我们需要通过一个HTML对象(即接收者)来调用init函数,并且我们可以在init函数内部直接调用接收者的成员:

html {
    //head和body是HTML的成员函数
    head { ... }
    body { ... }
}

5.13 类型别名

Kotlin通过typealias关键字来为一个已有类型起一个别名。

//给集合类型起别名
typealias NodeSet = Set<Network.Node>
typealias FileTable<K> = MutableMap<K, MutableList<File>>

//给函数类型起别名
typealias MyHandler = (Int, String, Any) -> Unit
typealias Predicate<T> = (T) -> Boolean

//给嵌套类、内部类起别名
class A {
    class Nested
    inner class Inner
}
typealias ANested = A.Nested
typealias AInner = A.Inner
发布了46 篇原创文章 · 获赞 38 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/al4fun/article/details/73928682