Kotlin学习(十六): 关键字与操作符(Keywords and Operators)

图片来源于网络

本文同步更新于旺仔的个人博客,访问可能有点慢,多刷新几次。

Kotlin中有一些常用的关键字和标识符,同时还有一些操作符和特殊符号,这些都是和Java有不一样的地方的,这里将他们介绍一下,方便记忆和回看。

硬关键字(Hard Keywords)

Kotlin中的硬关键字不能作为标识符

package

与Java一样,Kotlin的源文件同样以包声明开始的。

package foo.bar

fun baz() {}

class Goo {}

// ...

interface

interface表示声明一个接口,

interface MyInterface {
    fun bar()
    fun foo() {
      // 可选的方法体
    }
}

class

Kotlin的类的声明与Java一样,使用class关键字

class Invoice {
}

object

object为同时声明一个类及其实例,请看对象表达式

super

具体内容可看Kotlin学习_类和继承、接口与实现

引用一个方法或属性的超类实现

open class Foo {
    open fun f() { println("Foo.f()") }
    open val x: Int get() = 1
}

class Bar : Foo() {
    override fun f() { 
        super.f()
        println("Bar.f()") 
    }
    
    override val x: Int get() = super.x + 1
}

在此构造函数中调用超类构造函数

class MyView : View {
    constructor(ctx: Context) : super(ctx)

    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}

null

null是表示不指向任何对象的对象引用的常量。

this

引用当前接收者

次构造函数(二级构造函数)中调用同一个类中的另一个构造函数

class Person(val name: String) {
    constructor(name: String, paret: Person) : this(name) {
        parent.children.add(this)
    }
    constructor(name: String, parent: Person, count: Int) : this(name) {
        parent.children.add(this)
    }
}

typealias

类型别名为现有类型提供替代名称。如果类型名称太长,您可以引入不同的较短的名称,并使用新的名称。
缩短长泛型类型:

typealias NodeSet = Set<Network.Node>

typealias FileTable<K> = MutableMap<K, MutableList<File>>

可以为功能类型提供不同的别名:

typealias MyHandler = (Int, String, Any) -> Unit

typealias Predicate<T> = (T) -> Boolean

as

as是一个中缀操作符。
用于类型转换
as是不安全的转换操作符,如果as转换失败,会抛出一个异常,这就是不安全的。

val x: String = y as String

上面的代码表示将y强转为String类型,如果y为null,那么将不能转换成String,因为String是不可空的类型,那么就会抛出一个异常,所以如果y的类型是可空类型的话,那么强转的类型就必须是可空的

val x: String? = y as String?

用于指定导入包的别名
as除了用于类型转换之外,还有一个作用就是可以指定导入包的别名

import foo.Bar // Bar 可访问
import bar.Bar as bBar // bBar 代表“bar.Bar”

as?

as?as类似,也是转换操作符,但是与as不同的是,as?是安全的,也就是可空的,可以避免抛出异常,在转换失败是会返回null

val x: String? = y as? String

as后面的类型是个可空的类型,而as?后面的类型确实非空类型,但是as?转换的类型却是可空的,这样是主要的区别。

ifelse

在Kotlin中,if表达式表示返回一个值(truefalse),Kotlin中没有三目运算符。
else与Java定义一样,定义一个if表达式条件为false时执行的分支。

//传统用法
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

truefalse

指定布尔类型的"真"值和"假"值。

whiledo

while是开始一个while循环(前置条件的循环),而do为开始一个do/while循环(后置条件的循环),do...while 与Java的一样,有一个区别是,语句块里面的变量在外面是可见的

while (x > 0) {
    x--
}

do {
    val y = retrieveData()
} while (y != null) // y 在这是可见的

for

for表示开始一个for循环

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

when

Kotlin中的when就类似与Java的switch,但是与switch不同的是,when在其它分支都不匹配的时候默认匹配 else 分支,如果没有把所有可能和分支条件列出来,那么else是强制的,这与switchdefault也有区别。

when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> { // 默认
        print("x is neither 1 nor 2")
    }
}

break

break用于终止循环的执行,使用break 跳转到标签处,跳出循环

loop@ for (i in 1..10) {
    for (j in i..10) {
        if (j == 5) 
            break@loop // 跳出循环
        Log.e(Tag, j.toString()) // j 为5的时候跳出了循环,只打印1、2、3、4
    }
}

continue

continue用于跳到最近的闭合循环的下一次循环

loop@ for (i in 1..10) {
    for (j in i..10) {
        if (j == 5) 
            continue@loop // 跳出本次循环,进行下一次循环
        Log.e(Tag, j.toString()) // j 为5的时候跳出了循环,所有不会打印5
    }
}

return

return默认从最直接包围它的函数或者匿名函数返回。

fun foo() {
    ints.forEach {
        if (it == 0) return // 跳出forEach
        print(it)
    }
}

fun

fun表示声明一个函数

fun test() {

}

in

用于指定for循环中迭代的对象

for (item in collection) print(item)

用作中缀操作符以检查一个值属于一个区间、一个集合或者其他定义contains方法的实体。

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

when中使用

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

将一个类型参数标记为逆变

abstract class Comparable<in T> {
    abstract fun compareTo(other: T): Int
}

fun demo(x: Comparable<Number>) {
    x.compareTo(1.0) // 1.0 拥有类型 Double,它是 Number 的子类型
    // 因此,我们可以将 x 赋给类型为 Comparable <Double> 的变量
    val y: Comparable<Double> = x // OK!
}

!in

!in表示与in相反
用作中缀操作符以检查一个值属于一个区间、一个集合或者其他定义contains方法的实体。

if (i !in 1..10) { // 表示i不在1到10区间
    println(i)
}
if(a !in b){ // a !in b等同于!b.contains(a)
    println("a !in b")
}

when中使用

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

is!is

是否符合给定类型

类似与Java的instanceOfis操作符或其否定形式!is来检查对象是否符合给定类型:

if (obj is String) {
    print(obj.length)
}

if (obj !is String) { // 与 !(obj is String) 相同
    print("Not a String")
}
else {
    print(obj.length)
}

when表达式中用于判定是否符合

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

throwtry

throwtry与Java定义一样,throw为抛出一个异常,而try为捕获异常。

throw MyException("Hi There!")

try {
    // 一些代码
}
catch (e: SomeException) {
    // 处理程序
}
finally {
    // 可选的 finally 块
}

val

val表示声明一个只读属性或局部变量

val name: String = ……

var

val表示声明一个可变属性或局部变量

var name: String = ……

软关键字(Soft Keywords)

以下符号在适用的上下文中充当关键字,而在其他上下文中可用作标识符:

import

导入一个包里面的类文件

import foo.Bar // 导入foo包里面的Bar

by

将接口的实现委托给另一个对象

interface Base {
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}

class Derived(b: Base) : Base by b

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

将属性访问器的实现委托给另一个对象

class Example {
    var p: String by Delegate()
}
class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }
 
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name} in $thisRef.'")
    }
}

get

声明属性的getter

val isEmpty: Boolean
    get() = this.size == 0

用作注解使用处目标

set

声明属性的setter

var stringRepresentation: String
    get() = this.toString()
    set (value) {
        setDataFormString(value) // 格式化字符串,并且将值重新赋值给其他元素
    }

用作注解使用处目标

dynamic

引用一个Kotlin/JS代码中的动态类型

val dyn: dynamic = ……

catch

与Java一样,处理异常

try {
    // 一些代码
}
catch (e: SomeException) {
    // 处理程序
}

finally

与Java一样,try退出时总会执行的块

try {
    // 一些代码
}
catch (e: SomeException) {
    // 处理程序
}
finally {
    // 可选的 finally 块
}

constructor

声明一个主构造函数或次构造函数

class Person constructor(firstName: String) {
}

init

主构造函数不能包含任何的代码。初始化的代码可以放到以init关键字作为前缀的初始化块中:

class Customer(name: String) {
    init {
        logger.info("Customer initialized with value ${name}")
    }
}

paramsetparamdelegatefieldfileproperty

用作注解使用处目标

class Example(@field:Ann val foo,    // 标注 Java 字段
              @get:Ann val bar,      // 标注 Java getter
              @param:Ann val quux)   // 标注 Java 构造函数参数

使用目标(Use-site Targets)支持的有:

  • file
  • property使用此目标的注解对Java不可见
  • field
  • get 属性的getter
  • set 属性的setter
  • receiver 扩展函数或属性的接收器参数
  • param 构造函数参数
  • setparam 属性的setter的参数
  • delegate 该字段存储代理属性的代理实例

receiver

where

whera用于指定泛型多个类型的上界约束

fun <T> cloneWhenGreater(list: List<T>, threshold: T): List<T>
    where T : Comparable,
          T : Cloneable {
  return list.filter { it > threshold }.map { it.clone() }
}

修饰词关键字(Modifier Keywords)

out

将类型参数标记为协变

abstract class Source<out T> {
    abstract fun nextT(): T
}

fun demo(strs: Source<String>) {
    val objects: Source<Any> = strs // 这个没问题,因为 T 是一个 out-参数
    // ……
}

annotation

annotation表示声明一个注解类

annotation class Fancy

companion

companion表示声明一个伴生对象

class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}

const

const表示将属性标记为编译期常量,可用于注解当中

const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"

@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { …… }

external

external表示将一个声明标记为不是在 Kotlin 中实现(通过JNI访问或者在 JavaScript中实现)

// JNI
external fun foo(x: Int): Double

// JavaScript
external fun alert(message: Any?): Unit

external class Node {
    val firstChild: Node

    fun append(child: Node): Node

    fun removeChild(child: Node): Node

    // 等等
}

external val window: Window

inline

声明一个函数为内联函数

inline fun lock<T>(lock: Lock, body: () -> T): T {
    // ……
}

crossinline

crossinline表示禁止传递给内联函数的lambda中的非局部返回

inline fun f(crossinline body: () -> Unit) {
    val f = object: Runnable {
        override fun run() = body()
    }
    // ……
}

noinline

noinline表示一个内联函数如果只有一些被内联,另外的不想内联,可以将函数禁用内联:

inline fun <T> T.one (inlined: () -> Unit, noinline notInlined: () -> Unit) {

}

如果一个内联函数没有可内联的函数参数并且没有具体化类型参数,则会产生一个禁告,因为这样的内联函数没有什么用处。

warnning

final

final为禁止成员覆盖。

open class AnotherDerived() : Base() {
    final override fun v() {} // v方法不可被重写
}

open

允许一个类子类化或覆盖成员,openfinal相反,它允许其他类从这个类继承,默认情况下,在Kotlin中所有的类都是final

open class Base(p: Int)

class Derived(p: Int) : Base(p)

data

声明一个类为数据类

data class User(val name: String, val age: Int)

abstract

与Java一样,abstract将一个类或成员标记为抽象

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

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

enum

声明一个枚举类

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

inner

声明一个内部类,允许在嵌套类中引用外部类实例

class Outer {
    private val bar: Int = 1
    inner class Inner {
        fun foo() = bar
    }
}

val demo = Outer().Inner().foo() // == 1

sealed

声明一个密封类(限制子类化的类)

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

lateinit

延迟初始化属性,允许在构造函数之外初始化非空属性

public class MyTest {
    lateinit var subject: TestSubject

    @SetUp fun setup() {
        subject = TestSubject()
    }

    @Test fun test() {
        subject.method()  // 直接解引用
    }
}

operator

将一个函数标记为重载一个操作符,也就是操作符重载

override

与Java类型,override表示重写,Derived.v() 函数上必须加上 override标注。如果没写,编译器将会报错。

open class Base {
    open fun v() {}
    fun nv() {}
}
class Derived() : Base() {
    override fun v() {}
}

private

可见性,将一个声明标记为在当前类或文件中可见

protected

可见性,将一个声明标记为在当前类及其子类中可见

internal

可见性,将一个声明标记为在当前模块中可见

public

可见性,将一个声明标记为在任何地方可见

reified

suspend

将一个函数或lambda表达式标记为挂起式(可用做协程)

suspend fun doSomething(foo: Foo): Bar {
    ……
}

infix

允许以中缀表示法调用函数

// 给 Int 定义扩展
infix fun Int.shl(x: Int): Int {
……
}

// 用中缀表示法调用扩展函数

1 shl 2

// 等同于这样

1.shl(2)

tailrec

tailrec表示将一个函数标记为尾递归(允许编译器将递归替换为迭代)

private fun findFixPoint(): Double {
    var x = 1.0
    while (true) {
        val y = Math.cos(x)
        if (x == y) return y
        x = y
    }
}

vararg

vararg表示可变参数(通常是最后一个参数):

fun <T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for (t in ts) // 在这里ts的类型是数组
        result.add(t)
    return result
}

使用:

val list = asList(1, 2, 3)

当我们调用vararg函数,不仅可以接收可以一个接一个传递参数,例如asList(1, 2, 3),也可以将一个数组传递进去,在数组变量前面加spread操作符,就是*号:

val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4) // 表示(-1, 0, 1, 2, 3, 4)

特殊标识符(Special Identifiers)

field

field为备用字段,Kotlin中的类并不允许使用字段,在自定义gettersetter的时候,可以使用field来起到局部变量的作用。

var counter = 0 //初始化值会直接写入备用字段
    get() = field
    set(value) {
        if (value >= 0)
            field  = value
    }

编译器会检查访问器的代码,如果使用了备用字段(或者访问器是默认的实现逻辑),就会自动生成备用字段,否则就不会。

// 这种情况并不需要备用字段,所有不会生成备用字段
val isEmpty: Boolean
    get() = this.size == 0

注意:field标识符只允许在属性的访问器函数内使用.

it

it为单个参数的隐式名称,若函数参数对应的函数只有一个参数,在使用时,可以省略参数定义(连同->),直接使用it代替参数:

val doubled = ints.map { it -> it * 2 }
ints.filter { it > 0 } // it表示 '(it: Int) -> Boolean'

这种方式可以写成LINQ-style代码:

strings.filter { it.length == 5 }
.sortBy { it }
.map { it.toUpperCase() }

操作符和特殊符号(Operators and Special Symbols)

+-*/%

数学操作符,其中*还能用于将数组传给vararg参数

fun <T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for (t in ts) // ts is an Array
        result.add(t)
    return result
}
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)

=

=除了作为赋值操作符外,还用于指定参数的默认值

fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) {

}

+=-=*=/=%=

广义赋值操作符

++--

递增递减操作符

&&||!

逻辑“与”、“或”、“非”操作符,对应的中缀函数

  • and(bits) – 位与
  • or(bits) – 位或
  • xor(bits) – 位异或
  • inv() – 位非

==!=

相等操作符,对于非原生类型会翻译为调用equals()

===!==

引用相等操作符,引用相等由===(以及其否定形式 !==)操作判断。a === b 当且仅当ab指向同一个对象时求值为true

<><=>=

比较操作符,对于非原生类型会翻译为调用compareTo()

[]

索引访问操作符,会翻译为调用getset

!!

一个表达式非空

val l = b!!.length

?.

执行安全调用,如果接收者非空,就调用一个方法或访问一个属性

b?.length

?:

如果左侧的值为空,就取右侧的值(elvis操作符)

val l = b?.length ?: -1

::

创建一个成员引用或者一个类引用

fun isOdd(x: Int) = x % 2 != 0
val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd)) // 输出 [1, 3]

..

创建一个区间

val s = 1..10

?

将类型标记为可空

val s: String? = null

->

分隔lambda表达式的参数与主体

val sum = { x: Int, y: Int -> x + y }

分隔在函数类型中的参数类型与返回类型声明

// less类型是函数参数
fun <T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? {
    var max: T? = null
    for (it in collection)
        if (max == null || less(max, it))
            max = it
    return max
}

分隔 when 表达式分支的条件与代码体

when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> { 
        print("x is neither 1 nor 2")
    }
}

@

引入一个注解

@Fancy class Foo {
    @Fancy fun baz(@Fancy foo: Int): Int {
        return (@Fancy 1)
    }
}

引入或引用一个循环标签

loop@ for (i in 1..100) {
    for (j in 1..100) {
        if (……) break@loop
    }
}

引入或引用一个lambda表达式标签

fun foo() {
    ints.forEach lit@ {
        if (it == 0) return@lit
        print(it)
    }
}

引用一个来自外部作用域的 this表达式

class A { // 隐式标签 @A
    inner class B { // 隐式标签 @B
        fun Int.foo() { // 隐式标签 @foo
            val a = this@A // A 的 this
            val b = this@B // B 的 this

            val c = this // foo() 的接收者,一个 Int
            val c1 = this@foo // foo() 的接收者,一个 Int

            val funLit = lambda@ fun String.() {
                val d = this // funLit 的接收者
            }


            val funLit2 = { s: String ->
                // foo() 的接收者,因为它包含的 lambda 表达式
                // 没有任何接收者
                val d1 = this
            }
        }
    }
}

引用一个外部超类

class Bar : Foo() {
    override fun f() { /* …… */ }
    override val x: Int get() = 0
    
    inner class Baz {
        fun g() {
            super@Bar.f() // 调用 Foo 实现的 f()
            println(super@Bar.x) // 使用 Foo 实现的 x 的 getter
        }
    }
}

;

分隔位于同一行的多个语句

map.forEach { _, value -> 
    println("$value!");println("$value!") 
}

$

在字符串模版中引用变量或者表达式

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

_

lambda表达式中代替未使用的参数

map.forEach { _, value -> 
    println("$value!") 
}

在解构声明中代替未使用的参数

val (_, status) = getResult()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266