Kotlin基础用法总结

Kotlin 团队为 Android 开发提供了一套超越标准语言功能的工具:

  • Kotlin Android 扩展是一个编译器扩展, 可以让你摆脱代码中的 findViewById() 调用,并将其替换为合成的编译器生成的属性。
  • Anko 是一个提供围绕Android APIKotlin 友好的包装器的库 ,以及一个可以用 Kotlin 代码替换布局 .xml 文件的 DSL

定义变量

  • val 变量值不可变(只读)

    val a: Int = 1  // 立即赋值
    val b = 2   // 自动推断出 `Int` 类型
    val c: Int  // 如果没有初始值类型不能省略
    c = 3       // 明确赋值
  • var 可变变量

    var x = 5 // 自动推断出 `Int` 类型
    x += 1

is 运算符检测一个表达式是否某类型的一个实例。

fun getStringLength(obj: Any): Int? {
    // obj 在 && 右边自动转换成 String 类型
    if (obj is String && obj.length > 0) {
        return obj.length
    }

    return null
}

使用 in运算符来检测某个数字是否在指定区间内

编码规范

命名风格

如果拿不准的时候,默认使用Java的编码规范,比如:

  • 使用驼峰法命名(并避免命名含有下划线)
  • 类型名以大写字母开头
  • 方法和属性以小写字母开头
  • 使用 4 个空格缩进
  • 公有函数应撰写函数文档,这样这些文档才会出现在 Kotlin Doc

Unit

如果函数返回 Unit 类型,该返回类型应该省略:

fun foo() { // 省略了 ": Unit"

}

使用可空值及 null 检测

当某个变量的值可以为 null 的时候,必须在声明处的类型后添加 ? 来标识该引用可为空。

如果 str的内容不是数字返回 null

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

对于位运算,没有特殊字符来表示,而只可用中缀方式调用命名函数

例如:

val x = (1 shl 2) and 0x000FF000

完整的位运算列表(只用于 IntLong):

  • shl(bits) – 有符号左移 (Java 的 <<)
  • shr(bits) – 有符号右移 (Java 的 >>)
  • ushr(bits) – 无符号右移 (Java 的 >>>)
    -and(bits) – 位与
  • or(bits) – 位或
  • xor(bits) – 位异或
  • inv() – 位非

字符

字符用 Char类型表示。它们不能直接当作数字

字符串字面值

Kotlin 有两种类型的字符串字面值: 转义字符串 可以有转义字符,以及 原生字符串 可以包含换行和任意文本。转义字符串很像 Java 字符串:

val s = "Hello, world!\n"

转义采用传统的反斜杠方式。

原生字符串 使用三个引号(""")分界符括起来,内部没有转义并且可以包含换行和任何其他字符:

val text = """
    for (c in "foo")
        print(c)
"""

字符串模板

字符串可以包含 模板表达式 ,即一些小段代码,会求值并把结果合并到字符串中。 模板表达式以美元符($)开头,由一个简单的名字构成:

val i = 10
val s = "i = $i" // 求值结果为 "i = 10"

原生字符串和转义字符串内部都支持模板。 如果你需要在原生字符串中表示字面值 $ 字符(它不支持反斜杠转义),你可以用下列语法:

val price = """
${'$'}9.99
"""

控制流

if表达式

Kotlin 中,if是一个表达式,即它会返回一个值。 因此就不需要三元运算符(条件 ? 然后 : 否则),因为普通的 if就能胜任这个角色。

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

// With else 
var max: Int
if (a > b) {
    max = a
} else {
    max = b
}

// 作为表达式
val max = if (a > b) a else b

if的分支可以是代码块,最后的表达式作为该块的值:

 val a=2
    val b=5
    val max=if(a>b){
        print("choose a")

        a
    }else{
        print("Choose b")
        b
    }

如果你使用 if作为表达式而不是语句(例如:返回它的值或者把它赋给变量),该表达式需要有 else 分支。

When 表达式

when 取代了类 C 语言的 switch 操作符。其最简单的形式如下:

when (x) {
    1 -> print("x == 1")
    2 -> print("x == 2")
    else -> { // 注意这个块
        print("x is neither 1 nor 2")
    }
}

when 将它的参数和所有的分支条件顺序比较,直到某个分支满足条件。 when 既可以被当做表达式使用也可以被当做语句使用。如果它被当做表达式, 符合条件的分支的值就是整个表达式的值,如果当做语句使用, 则忽略个别分支的值。(像 if 一样,每一个分支可以是一个代码块,它的值是块中最后的表达式的值。)

如果其他分支都不满足条件将会求值 else分支。 如果when 作为一个表达式使用,则必须有 else 分支, 除非编译器能够检测出所有的可能情况都已经覆盖了。

如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔:

when (x) {
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
}

我们可以用任意表达式(而不只是常量)作为分支条件

when (x) {
    parseInt(s) -> print("s encodes x")
    else -> print("s does not encode x")
}

我们也可以检测一个值在(in)或者不在(!in)一个区间或者集合中:

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")
}

另一种可能性是检测一个值是(is)或者不是(!is)一个特定类型的值。注意: 由于智能转换,你可以访问该类型的方法和属性而无需任何额外的检测。

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循环可以对任何提供迭代器(iterator)的对象进行遍历,这相当于像 C#这样的语言中的 foreach 循环。语法如下:

for (item in collection) print(item)
循环体可以是一个代码块。

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

While 循环

whiledo..while 照常使用

循环中的Break和continue

在循环中 Kotlin 支持传统的 breakcontinue 操作符。

Break 与 Continue 标签

表达式,是指由常量、变量或是操作数与运算符所组合而成的语句。

Kotlin 中任何表达式都可以用标签(label)来标记。 标签的格式为标识符后跟 @ 符号,例如:abc@fooBar@都是有效的标签(参见语法)。 要为一个表达式加标签,我们只要在其前加标签即可。

loop@ for (i in 1..100) {
    // ……
}

现在,我们可以用标签限制 break 或者continue

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

标签限制的 break 跳转到刚好位于该标签指定的循环后面的执行点。 continue 继续标签指定的循环的下一次迭代。

类和对象

构造函数

在 Kotlin 中的一个类可以有一个主构造函数和一个或多个次构造函数。主构造函数是类头的一部分:它跟在类名(和可选的类型参数)后。

class Person constructor(firstName: String) {
}

如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字。

class Person(firstName: String) {
}

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

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

!注意,主构造的参数可以在初始化块中使用。它们也可以在类体内声明的属性初始化器中使用:

class Customer(name: String) {
    val customerKey = name.toUpperCase()
}

事实上,声明属性以及从主构造函数初始化属性,Kotlin 有简洁的语法:

class Person(val firstName: String, val lastName: String, var age: Int) {
    // ……
}

与普通属性一样,主构造函数中声明的属性可以是可变的(var)或只读的(`val)。

如果构造函数有注解或可见性修饰符,这个constructor 关键字是必需的,并且这些修饰符在它前面:

class Customer public @Inject constructor(name: String) { …… }

次构造函数

类也可以声明前缀有 constructor的次构造函数:

class Person {
    constructor(parent: Person) {
        parent.children.add(this)
    }
}

如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用 this 关键字即可:

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

如果一个非抽象类没有声明任何(主或次)构造函数,它会有一个生成的不带参数的主构造函数。构造函数的可见性是 public。如果你不希望你的类有一个公有构造函数,你需要声明一个带有非默认可见性的空的主构造函数:

class DontCreateMe private constructor () {
}

注意:在 JVM 上,如果主构造函数的所有的参数都有默认值,编译器会生成 一个额外的无参构造函数,它将使用默认值。这使得 Kotlin 更易于使用像 Jackson 或者 JPA 这样的通过无参构造函数创建类的实例的库。

class Customer(val customerName: String = "")

继承

Kotlin 中所有类都有一个共同的超类 Any,这对于没有超类型声明的类是默认超类:

class Example // 从 Any 隐式继承

Any 不是 java.lang.Object;尤其是,它除了 equals()hashCode()toString()外没有任何成员。

要声明一个显式的超类型,我们把类型放到类头的冒号之后:

open class Base(p: Int)

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

如果该类有一个主构造函数,其基类型可以(并且必须) 用(基类型的)主构造函数参数就地初始化。

如果类没有主构造函数,那么每个次构造函数必须使用super 关键字初始化其基类型,或委托给另一个构造函数做到这一点。 注意,在这种情况下,不同的次构造函数可以调用基类型的不同的构造函数:

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

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

类上的 open 标注与 Javafinal 相反,它允许其他类从这个类继承。默认情况下,在 Kotlin 中所有的类都是 final, 对应于 Effective Java书中的第 17 条:要么为继承而设计,并提供文档说明,要么就禁止继承

覆盖属性

属性覆盖与方法覆盖类似;在超类中声明然后在派生类中重新声明的属性必须以 override 开头,并且它们必须具有兼容的类型。每个声明的属性可以由具有初始化器的属性或者具有 getter方法的属性覆盖。

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

调用超类实现

派生类中的代码可以使用 super 关键字调用其超类的函数与属性访问器的实现:

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
}

在一个内部类中访问外部类的超类,可以通过由外部类名限定的 super 关键字来实现:super@Outer

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
        }
    }
}

覆盖规则

Kotlin 中,实现继承由下述规则规定:如果一个类从它的直接超类继承相同成员的多个实现, 它必须覆盖这个成员并提供其自己的实现(也许用继承来的其中之一)。 为了表示采用从哪个超类型继承的实现,我们使用由尖括号中超类型名限定的 super,如 super

open class A {
    open fun f() { print("A") }
    fun a() { print("a") }
}

interface B {
    fun f() { print("B") } // 接口成员默认就是“open”的
    fun b() { print("b") }
}

class C() : A(), B {
    // 编译器要求覆盖 f():
    override fun f() {
        super.f() // 调用 A.f()
        super.f() // 调用 B.f()
  }
}

同时继承 A 和 B 没问题,并且 a() 和 b() 也没问题因为 C 只继承了每个函数的一个实现。 但是 f() 由 C 继承了两个实现,所以我们必须在 C 中覆盖 f() 并且提供我们自己的实现来消除歧义。

接口

Kotlin 的接口与 Java8类似,既包含抽象方法的声明,也包含实现。与抽象类不同的是,接口无法保存状态。它可以有属性但必须声明为抽象或提供访问器实现。

使用关键字 interface 来定义接口

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

委托

类委托:类的委托即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。
以下实例中派生类 Derived 继承了接口 Base 所有方法,并且委托一个传入的 Base 类的对象来执行这些方法。

// 创建接口
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
}

Derived 声明中,by 子句表示,将 b 保存在 Derived 的对象实例内部,而且编译器将会生成继承自 Base 接口的所有方法, 并将调用转发给 b

注意,覆盖会以你所期望的方式工作:编译器会使用你的 override 实现取代委托对象中的实现。如果我们为 Derived 添加 override fun print() { print("abc") },该程序会输出“abc”而不是“10”。

高阶函数

高阶函数是将函数用作参数或返回值的函数。

val doubled = ints.map { value -> value * 2 }

注意,如果 lambda 是该调用的唯一参数,则调用中的圆括号可以完全省略。

it:单个参数的隐式名称

另一个有用的约定是,如果函数字面值只有一个参数, 那么它的声明可以省略(连同 ->),其名称是 it

ints.map { it * 2 }

Lambda 表达式与匿名函数

一个 lambda 表达式或匿名函数是一个“函数字面值”,即一个未声明的函数, 但立即做为表达式传递。

例:

max(strings, { a, b -> a.length < b.length })

函数max是一个高阶函数,换句话说它接受一个函数作为第二个参数。 其第二个参数是一个表达式,它本身是一个函数,即函数字面值。写成函数的话,它相当于:

fun compare(a: String, b: String): Boolean = a.length < b.length

lambda 表达式总是被大括号括着;

  • 其参数(如果有的话)在 -> 之前声明(参数类型可以省略);
  • 函数体(如果存在的话)在 -> 后面。

如果 lambda 表达式的参数未使用,那么可以用下划线取代其名称:

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

集合

Kotlin 区分可变集合和不可变集合

不可变:

  • listOf()
  • setOf()

可变

  • mutableSetOf()
  • mutableListOf()

区间

区间表达式由具有操作符形式 ..rangeTo 函数辅以in!in 形成。

想要倒序迭代数字可以使用标准库中定义的 downTo() 函数:

for (i in 4 downTo 1) print(i) // 输出“4321”

类型的检查与转换“is”与“as”

is!is 操作符

我们可以在运行时通过使用 is 操作符或其否定形式 !is 来检查对象是否符合给定类型:

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

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

空安全

在条件中检查 null

显式检查 b 是否为 null

安全的调用

安全调用操作符,写作 ?.

b?.length

如果 b 非空,就返回 b.length,否则返回 null,这个表达式的类型是 Int?

安全调用在链式调用中很有用。如:

bob?.department?.head?.name

如果任意一个属性(环节)为空,这个链式调用就会返回 null

如果要只对非空值执行某个操作,安全调用操作符可以与 let 一起使用:

val listWithNulls: List?> = listOf("A", null)
for (item in listWithNulls) {
     item?.let { println(it) } // 输出 A 并忽略 null
}

Elvis 操作符

val l = b?.length ?: -1

如果 ?:左侧表达式非空,elvis 操作符就返回其左侧表达式,否则返回右侧表达式。 请注意,当且仅当左侧为空时,才会对右侧表达式求值。

异常

异常类

Kotlin 中所有异常类都是 Throwable 类的子孙类。 每个异常都有消息、堆栈回溯信息和可选的原因

throw 表达式的类型是特殊类型Nothing.该类型没有值,而是用于标记永远不能达到的代码位置。 在你自己的代码中,你可以使用 Nothing 来标记一个永远不会返回的函数:

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

泛型应用

Kotlin标准库中的三个函数

  • let

    let 可以被任何对象调用。它接收一个函数作为参数,作为参数的函数返回的结果作为整个函数的返回值。它在处理可null对象的时候是非常有用的,下面是它的定义:

    inline fun  T.let(f: (T) -> R): R = f(this)

    它使用了两个泛型类型: TR 。第一个是被调用者定义的,它的类型被函数
    接收到。第二个是函数的返回值类型。

    对非空值执行某个操作,安全调用操作符可以与 let 一起使用,如:

    //使用前
    if (forecast != null) dataMapper.convertDayToDomain(forecast) else null
    
    //使用后
    forecast?.let { dataMapper.convertDayToDomain(it) }
  • with

    with 接收一个对象和一个函数,这个函数会作为这个对象的扩展函数执行。这表示我们根据推断可以在函数内使用 this 。定义如下:

    inline fun  with(receiver: T, f: T.() -> R): R = receiver.f()
    

    T 代表接收类型, R 代表结果。如你所见,函数通过 f: T.() -> R 声明被定义成了扩展函数。这就是为什么我们可以调用 receiver.f()

    例如:

    fun convertFromDomain(forecast: ForecastList) = with(forecast) {
    
    val daily = dailyForecast map { convertDayFromDomain(id, it)}
    
     CityForecast(id, city, country, daily)
    }
  • apply

    它看起来与 with 很相似,但是是有点不同之处。 apply 可以避免创建builder的
    方式来使用,因为对象调用的函数可以根据自己的需要来初始化自己,然后apply函数会返回它同一个对象:

    inline fun  T.apply(f: T.() -> Unit): T { f(); return this }

    这里我们只需要一个泛型类型,因为调用这个函数的对象也就是这个函数返回的对象。例如:

    val textView = TextView(context).apply {
      text = "Hello"
      hint = "Hint"
      textColor = android.R.color.white
    }

获取class 的实例:

class Test

val clazz = Test::class.java

val test = Test()
val clazz2 = test.javaClass

lateinit 和 by lazy

  • lateinit 用于 varby lazy 用于 val

  • lazy()是一个函数,接受一个Lambda表达式作为参数,返回一个Lazy类型的实例,这个实例可以作为一个委托, 实现延迟加载属: 第一次调用 get() 时,保存Lambda表达式执行的结果, 以后对 get()的调用都只会简单地返回以前记住的结果,在lazy中并没有setValue(…)方法.

  • lateinit 用于生命周期流程中进行获取或者初始化的变量。

你可能感兴趣的:(【Kotlin】)