Kotlin从入门到掉坑

参考kotlin官方网站Kotlin 语言中文站,该网站还支持在线编写kotlin代码,对于没有kotlin环境的同学来说,非常的方便。

感觉官方的教程翻译的比较生硬,有些地方读起来非常拗口,可以参考这个学习网站https://www.runoob.com/kotlin/kotlin-tutorial.html

一个有意思的链接,从java到kotlin的对比学习https://github.com/MindorksOpenSource/from-java-to-kotlin/blob/master/README-ZH.md

菜鸟工具可以对很多主流的语言提供在线运行的功能,非常强大https://c.runoob.com/compile/2960

简介

Kotlin-一个用于现代多平台应用的静态编程语言 。Kotlin可以编译成Java字节码,支持在J VM上运行;也可以编译成JavaScript,方便在没有JVM的设备上运行。Kotlin已正式成为Android官方支持开发语言。

Kotlin用于原生开发

Kotlin/Native 是一种将 Kotlin 代码编译为无需虚拟机就可运行的原生二进制文件的技术。

为什么选用 Kotlin/Native?
Kotlin/Native 的主要设计目标是让 Kotlin 可以为不希望或者不可能使用 虚拟机 的平台(例如嵌入式设备或者 iOS)编译。 它解决了开发人员需要生成无需额外运行时或虚拟机的自包含程序的情况。

目标平台
Kotlin/Native 支持以下平台:

  • iOS(arm32、 arm64、 模拟器 x86_64)
  • MacOS(x86_64)
  • Android(arm32、arm64)
  • Windows(mingw x86_64、x86)
  • Linux(x86_64、 arm32、 MIPS、 MIPS 小端次序、树莓派)
  • WebAssembly(wasm32)

Kotlin 进行 Android 开发

Kotlin 非常适合开发 Android 应用程序,将现代语言的所有优势带入 Android 平台而不会引入任何新的限制:

  • 兼容性:Kotlin 与 JDK 6 完全兼容,保障了 Kotlin 应用程序可以在较旧的 Android 设备上运行而无任何问题。Kotlin 工具在 Android Studio 中会完全支持,并且兼容 Android 构建系统。
  • 性能:由于非常相似的字节码结构,Kotlin 应用程序的运行速度与 Java 类似。 随着 Kotlin 对内联函数的支持,使用 lambda 表达式的代码通常比用 Java 写的代码运行得更快。
  • 互操作性:Kotlin 可与 Java 进行 100% 的互操作,允许在 Kotlin 应用程序中使用所有现有的 Android 库 。这包括注解处理,所以数据绑定与 Dagger 也是一样。
  • 占用:Kotlin 具有非常紧凑的运行时库,可以通过使用 ProGuard 进一步减少。 在实际应用程序中,Kotlin 运行时只增加几百个方法以及 .apk 文件不到 100K 大小。
  • 编译时长:Kotlin 支持高效的增量编译,所以对于清理构建会有额外的开销,增量构建通常与 Java 一样快或者更快。
  • 学习曲线:对于 Java 开发人员,Kotlin 入门很容易。包含在 Kotlin 插件中的自动 Java 到 Kotlin 的转换器有助于迈出第一步。Kotlin 心印 通过一系列互动练习提供了语言主要功能的指南。

多平台项目: iOS 与 Android

在 iOS 与 Android 之间共享 Kotlin 代码
创建一个 iOS 与一个 Android 应用,来展示 Kotlin 代码的共享功能。 在 Android 上将使用 Kotlin/JVM,而在 iOS 上将是 Kotlin/Native。

使用

Androidstudio3.0之后自带kotlin插件,所以可以直接方便使用。
but,还是需要配置一下kotlin相关的,不然会显示“kotlin not configured”,不能正常使用kotlin。

配置

在Project对应的build.gradle文件里面添加如下配置:

buildscript {
    ext.kotlin_version = '1.3.31' //kotlin版本号
    
    // ..........
    
    dependencies {
   		 // ..........
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // 添加依赖
    }
}

在Module对应的build.gradle文件里面添加如下配置:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'    // 添加app plugin
apply plugin: 'kotlin-android-extensions'    // 添加app plugin

// ..........

dependencies {
	// ..........
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" //添加依赖
}

Kotlin对包体大小的影响

  • 纯java项目,我编译一个空的项目,打包出来的包体大小是1414KB。
  • 纯kotlin项目,我编译一个空的项目,打包出来的包体大小是1990KB。比纯java项目的包体大小稍微大一点,因为引入了一些kotlin相关的库。
  • 把第一个的纯java项目改成兼容kotlin,还是1414KB大小,说明kotlin对包体大小几乎没有影响。

基础语法

  • 函数
    用fun修饰
  • 定义变量
    局部变量使用关键字 val 定义
  • 使用字符串模板
    在变量前加$符号
var a = 1
// 模板中的简单名称:
val s1 = "a is $a" 
  • 使用区间(range)
    使用 in 运算符来检测某个数字是否在指定区间内:
val x = 10
val y = 9
if (x in 1..y+1) {
    println("fits in range")
}

检测某个数字是否在指定区间外:

val list = listOf("a", "b", "c")
if (-1 !in 0..list.lastIndex) {
    println("-1 is out of range")
}
if (list.size !in list.indices) {
    println("list size is out of valid list indices range, too")
}

区间迭代:

for (x in 1..5) {
    print(x)
}

或数列迭代:

for (x in 1..10 step 2) {
    print(x)
}
println()
for (x in 9 downTo 0 step 3) {
    print(x)
}

总结:

for (i in 1..100) { …… }  // 闭区间:包含 100
for (i in 1 until 100) { …… } // 半开区间:不包含 100
for (x in 2..10 step 2) { …… }
for (x in 10 downTo 1) { …… }
if (x in 1..10) { …… }

习惯用法

待定

编码规范

源代码组织

  • 源文件名称
    如果 Kotlin 文件包含单个类(以及可能相关的顶层声明),那么文件名应该与该类的名称相同,并追加 .kt 扩展名。如果文件包含多个类或只包含顶层声明, 那么选择一个描述该文件所包含内容的名称,并以此命名该文件。使用首字母大写的驼峰风格 (例如 ProcessDeclarations.kt)。
    文件的名称应该描述文件中代码的作用。因此,应避免在文件名中使用诸如“Util”之类的无意义词语。

命名规则
Kotlin 遵循 Java 命名约定。尤其是:
包的名称总是小写且不使用下划线(org.example.myproject)。 通常不鼓励使用多个词的名称,但是如果确实需要使用多个词,可以将它们连接在一起或使用驼峰(org.example.myProject)。
类与对象的名称以大写字母开头并使用驼峰:

open class DeclarationProcessor { …… }
object EmptyDeclarationProcessor : DeclarationProcessor() { …… }
  • 函数名
    函数、属性与局部变量的名称以小写字母开头、使用驼峰而不使用下划线:
fun processDeclarations() { …… }
var declarationCount = ……
  • 属性名
    常量名称(标有 const 的属性,或者保存不可变数据的没有自定义 get 函数的顶层/对象 val 属性)应该使用大写、下划线分隔的名称:
const val MAX_COUNT = 8
val USER_NAME_FIELD = "UserName"

保存带有行为的对象或者可变数据的顶层/对象属性的名称应该使用常规驼峰名称:

val mutableCollection: MutableSet<String> = HashSet()

保存单例对象引用的属性的名称可以使用与 object 声明相同的命名风格:

val PersonComparator: Comparator<Person> = ...

对于枚举常量,可以使用大写、下划线分隔的名称 (enum class Color { RED, GREEN })也可使用以大写字母开头的常规驼峰名称,具体取决于用途。

  • 幕后属性的名称
    如果一个类有两个概念上相同的属性,一个是公共 API 的一部分,另一个是实现细节,那么使用下划线作为私有属性名称的前缀:
class C {
    private val _elementList = mutableListOf<Element>()
    
    val elementList: List<Element>
         get() = _elementList
}
  • 格式化
    在大多数情况下,Kotlin 遵循 Java 编码规范。
    使用 4 个空格缩进。不要使用 tab。
    对于花括号,将左花括号放在结构起始处的行尾,而将右花括号放在与左括结构横向对齐的单独一行。
if (elements != null) {
    for (element in elements) {
        // ……
    }
}

注意:在 Kotlin 中,分号是可选的,因此换行很重要。语言设计采用 Java 风格的花括号格式,如果尝试使用不同的格式化风格,那么可能会遇到意外的行为。

  • 链式调用换行
    当对链式调用换行时,将 . 字符或者 ?. 操作符放在下一行,并带有单倍缩进:
val anchor = owner
    ?.firstChild!!
    .siblings(forward = true)
    .dropWhile { it is PsiComment || it is PsiWhiteSpace }

调用链的第一个调用通常在换行之前,当然如果能让代码更有意义也可以忽略这点。

空安全

可空类型与非空类型
在 Kotlin 中,类型系统区分一个引用可以容纳 null (可空引用)还是不能容纳(非空引用)。 例如,String 类型的常规变量不能容纳 null:

var a: String = "abc"
a = null // 编译错误

如果要允许为空,我们可以声明一个变量为可空字符串,写作 String?:

var b: String? = "abc"
b = null // ok
print(b)

现在,如果你调用 a 的方法或者访问它的属性,它保证不会导致 NPE,这样你就可以放心地使用:

val l = a.length

但是如果你想访问 b 的同一个属性,那么这是不安全的,并且编译器会报告一个错误:

val l = b.length // 错误:变量“b”可能为空

但是我们还是需要访问该属性,对吧?有几种方式可以做到。
在条件中检查 null
安全的调用
你的第二个选择是安全调用操作符,写作 ?.:

val a = "Kotlin"
val b: String? = null
println(b?.length)
println(a?.length) // 无需安全调用

如果 b 非空,就返回 b.length,否则返回 null
!! 操作符
第三种选择是为 NPE 爱好者准备的:非空断言运算符(!!)将任何值转换为非空类型,若该值为空则抛出异常。我们可以写 b!! ,这会返回一个非空的 b 值 (例如:在我们例子中的 String)或者如果 b 为空,就会抛出一个 NPE 异常:

val l = b!!.length

因此,如果你想要一个 NPE,你可以得到它,但是你必须显式要求它,否则它不会不期而至。
lateinit
在某个类中,如果某些成员变量没办法在一开始就初始化,并且又不想使用可空类型(也就是带?的类型)。那么,可以使用lateinit来修饰它。不然编译报错:

Property must be initialized or be abstract

被lateinit修饰的变量,并不是不初始化,它需要在生命周期流程中进行获取或者初始化。
如果访问未初始化的 lateinit 变量会导致

UninitializedPropertyAccessException。

基础

基本类型

  • 数字
    Kotlin 处理数字在某种程度上接近 Java,但是并不完全相同。例如,对于数字没有隐式拓宽转换(如 Java 中 int 可以隐式转换为long——译者注),另外有些情况的字面值略有不同。
    Kotlin 提供了如下的内置类型来表示数字(与 Java 很相近):
Type Bit width
Double 64
Float 32
Long 64
Int 32
Short 16
Byte 8

数字字面值中的下划线(自 1.1 起),你可以使用下划线使数字常量更易读:

val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
  • 数组
    数组在 Kotlin 中使用 Array 类来表示,它定义了 get 与 set 函数(按照运算符重载约定这会转变为 [])以及 size 属性,以及一些其他有用的成员函数:
class Array<T> private constructor() {
    val size: Int
    operator fun get(index: Int): T
    operator fun set(index: Int, value: T): Unit
​    operator fun iterator(): Iterator<T>
    // ……
}

我们可以使用库函数 arrayOf() 来创建一个数组并传递元素值给它,这样 arrayOf(1, 2, 3) 创建了 array [1, 2, 3]。 或者,库函数 arrayOfNulls() 可以用于创建一个指定大小的、所有元素都为空的数组。

  • 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 分支, 除非编译器能够检测出所有的可能情况都已经覆盖了[例如,对于 枚举(enum)类条目与密封(sealed)类子类型]。
如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔:

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
}
  • 标签处返回
    Kotlin 有函数字面量、局部函数和对象表达式。因此 Kotlin 的函数可以被嵌套。 标签限制的 return 允许我们从外层函数返回。 最重要的一个用途就是从 lambda 表达式中返回。回想一下我们这么写的时候:
fun foo() {
    listOf(1, 2, 3, 4, 5).forEach {
        if (it == 3) return // 非局部直接返回到 foo() 的调用者
        print(it)
    }
    println("this point is unreachable")
}

这个 return 表达式从最直接包围它的函数即 foo 中返回。 (注意,这种非局部的返回只支持传给内联函数的 lambda 表达式。) 如果我们需要从 lambda 表达式中返回,我们必须给它加标签并用以限制 return。

fun foo() {
    listOf(1, 2, 3, 4, 5).forEach lit@{
        if (it == 3) return@lit // 局部返回到该 lambda 表达式的调用者,即 forEach 循环
        print(it)
    }
    print(" done with explicit label")
}

现在,它只会从 lambda 表达式中返回。通常情况下使用隐式标签更方便。 该标签与接受该 lambda 的函数同名。

fun foo() {
    listOf(1, 2, 3, 4, 5).forEach {
        if (it == 3) return@forEach // 局部返回到该 lambda 表达式的调用者,即 forEach 循环
        print(it)
    }
    print(" done with implicit label")
}

类和对象

Kotlin 类可以包含:构造函数和初始化代码块、函数、属性、内部类、对象声明。
Kotlin 中使用关键字 class 声明类

class Invoice { ... }

类声明由类名、类头(指定其类型参数、主构造函数等)以及由花括号包围的类体构成。类头与类体都是可选的; 如果一个类没有类体,可以省略花括号。

class Empty

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

class Person constructor(firstName: String) { ... }

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

class Person(firstName: String) { ... }

主构造函数不能包含任何的代码。初始化的代码可以放到以 init 关键字作为前缀的初始化块(initializer blocks)中。
在实例初始化期间,初始化块按照它们出现在类体中的顺序执行,与属性初始化器交织在一起:

class InitOrderDemo(name: String) {
    val firstProperty = "First property: $name".also(::println)
    init {
        println("First initializer block that prints ${name}")
    }
    
    val secondProperty = "Second property: ${name.length}".also(::println)    
    init {
        println("Second initializer block that prints ${name.length}")
    }
}

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

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

主构造器
主构造器中不能包含任何代码,初始化代码可以放在初始化代码段中,初始化代码段使用 init 关键字作为前缀。

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

注意:主构造器的参数可以在初始化代码段中使用,也可以在类主体定义的属性初始化代码中使用。 一种简洁语法,可以通过主构造器来定义属性并初始化属性值(可以是var或val):

class People(val firstName: String, val lastName: String) {
    //...
}

注意主构造器中参数有没有var或val的区别:

fun main() {
    var aaa = AAA("a")
    println(aaa.tt)
}

class AAA(var tt:String) {}

加上var或val之后,相当于类中定义了该属性。

次构造函数
类也可以有二级构造函数,需要加前缀 constructor:

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

如果类有主构造函数,每个次构造函数都要,或直接或间接通过另一个次构造函数代理主构造函数。在同一个类中代理另一个构造函数使用 this 关键字:

class Person(val name: String) {
    constructor (name: String, age:Int) : this(name) {
        // 初始化...
    }
}

如果一个非抽象类没有声明构造函数(主构造函数或次构造函数),它会产生一个没有参数的构造函数。构造函数是 public 。如果你不想你的类有公共的构造函数,你就得声明一个空的主构造函数:

class DontCreateMe private constructor () {
}

创建类的实例
要创建一个类的实例,我们就像普通函数一样调用构造函数:

val invoice = Invoice()
val customer = Customer("Joe Smith")

注意 Kotlin 并没有 new 关键字。
继承
在 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)
}

覆盖方法
我们之前提到过,Kotlin 力求清晰显式。与 Java 不同,Kotlin 对于可覆盖的成员(我们称之为开放)以及覆盖后的成员需要显式修饰符:

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

Derived.v() 函数上必须加上 override 修饰符。如果没写,编译器将会报错。 如果函数没有标注 open 如 Base.nv(),那么子类中不允许定义相同签名的函数, 不论加不加 override。将 open 修饰符添加到 final 类(即没有 open 的类)的成员上不起作用。
标记为 override 的成员本身是开放的,也就是说,它可以在子类中覆盖。如果你想禁止再次覆盖,使用 final 关键字:

open class AnotherDerived() : Base() {
    final override fun v() { ... }
}

调用超类实现
派生类中的代码可以使用 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() {
            [email protected]() // 调用 Foo 实现的 f()
            println([email protected]) // 使用 Foo 实现的 x 的 getter
        }
    }
}

嵌套类
我们可以把类嵌套在其他类中,看以下实例:

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
}

内部类
内部类使用 inner 关键字来表示。
内部类会带有一个对外部类的对象的引用,所以内部类可以访问外部类成员属性和成员函数。

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)   // 内部类可以引用外部类的成员,例如:成员属性
}

注意嵌套类和内部类的区别,通过创建对象的方式可以看出:

val demo = Outer.Nested() // 嵌套类不需要创建外部类的对象
val demo = Outer().Inner() // 内部类需要创建外部类的对象

所以嵌套类和外部类是没有直接关系的,内部类和使用外部类的成员变量。

匿名内部类
使用对象表达式来创建匿名内部类:

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 和_accessModifier_:

  • classModifier: 类属性修饰符,标示类本身特性。
    abstract    // 抽象类  
    final       // 类不可继承,默认属性
    enum        // 枚举类
    open        // 类可继承,类默认是final的
    annotation  // 注解类
  • accessModifier: 访问权限修饰符
    private    // 仅在同一个文件中可见
    protected  // 同一个文件中或子类可见
    public     // 所有调用的地方都可见
    internal   // 同一个模块中可见

Kotlin 对象表达式和对象声明

Kotlin 用对象表达式和对象声明来实现创建一个对某个类做了轻微改动的类的对象,且不需要去声明一个新的子类。
对象表达式
通过对象表达式实现一个匿名内部类的对象用于方法的参数中:

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        // ...
    }
    override fun mouseEntered(e: MouseEvent) {
        // ...
    }
})

对象可以继承于某个基类,或者实现其他接口:

open class A(x: Int) {
    public open val y: Int = x
}

interface B {……}

val ab: A = object : A(1), B {
    override val y = 15
}

如果超类型有一个构造函数,则必须传递参数给它。多个超类型和接口可以用逗号分隔。
通过对象表达式可以越过类的定义直接得到一个对象:

fun main(args: Array<String>) {
    val site = object {
        var name: String = "菜鸟教程"
        var url: String = "www.runoob.com"
    }
    println(site.name)
    println(site.url)
}

请注意,匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数的 返回类型或者用作公有属性的类型,那么该函数或属性的实际类型 会是匿名对象声明的超类型,如果你没有声明任何超类型,就会是 Any。在匿名对象 中添加的成员将无法访问。

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++
        }
    })
    // ……
}

对象声明
Kotlin 使用 object 关键字来声明一个对象。
Kotlin 中我们可以方便的通过对象声明来获得一个单例。

object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ……
    }

    val allDataProviders: Collection<DataProvider>
        get() = // ……
}

引用该对象,我们直接使用其名称即可:

DataProviderManager.registerDataProvider(……)

当然你也可以定义一个变量来获取获取这个对象,当时当你定义两个不同的变量来获取这个对象时,你会发现你并不能得到两个不同的变量。也就是说通过这种方式,我们获得一个单例。

fun main(args: Array<String>) {   
    var a1 = AAA
    println(a1.name)
    var a2 = AAA
    a2.name = "ls"
    println(a1.name)
}

object AAA {
    var name: String = "zs"
}

输出结果:

zs
ls

对象可以有超类型:

object DefaultListener : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        // ……
    }

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

与对象表达式不同,当对象声明在另一个类的内部时,这个对象并不能通过外部类的实例访问到该对象,而只能通过类名来访问,同样该对象也不能直接访问到外部类的方法和变量。

class Site {
    var name = "菜鸟教程"
    object DeskTop{
        var url = "www.runoob.com"
        fun showName(){
            print{"desk legs $name"} // 错误,不能访问到外部类的方法和变量
        }
    }
}
fun main(args: Array<String>) {
    var site = Site()
    site.DeskTop.url // 错误,不能通过外部类的实例访问到该对象
    Site.DeskTop.url // 正确
}

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

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

val instance = MyClass.create()   // 访问到对象的内部元素

我们可以省略掉该对象的对象名,然后使用 Companion 替代需要声明的对象名:

class MyClass {
    companion object {
    	fun foo() {}
    }
}

val x = MyClass.foo() // 直接使用
val x = MyClass.Companion.foo() // Kotlin也提供了一个叫Companion的默认名称,注意首字母大写

注意:一个类里面只能声明一个内部关联对象,即关键字 companion 只能使用一次。
请伴生对象的成员看起来像其他语言的静态成员,但在运行时他们仍然是真实对象的实例成员。例如还可以实现接口:

interface Factory<T> {
    fun create(): T
}

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

对象表达式和对象声明之间的语义差异
对象表达式和对象声明之间有一个重要的语义差别:

  • 对象表达式是在使用他们的地方立即执行的
  • 对象声明是在第一次被访问到时延迟初始化的
  • 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配

对象声明和伴生对象的差异

- 对象声明 伴生对象
是否可访问外部类成员 ×
对象名是否可以省略 ×

属性与字段

声明属性
Kotlin 类中的属性既可以用关键字 var 声明为可变的,也可以用关键字 val 声明为只读的。

class Address {
    var name: String = ……
    var state: String? = ……
}

要使用一个属性,只要用名称引用它即可,就像 Java 中的字段:

fun copyAddress(address: Address): Address {
    val result = Address() // Kotlin 中没有“new”关键字
    result.name = address.name // 将调用访问器
    result.street = address.street
    // ……
    return result
}

Getters 与 Setters
声明一个属性的完整语法是

var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]

其初始器(initializer)、getter 和 setter 都是可选的。属性类型如果可以从初始器 (或者从其 getter 返回值,如下文所示)中推断出来,也可以省略。
例如:

var allByDefault: Int? // 错误:需要显式初始化器,隐含默认 getter 和 setter
var initialized = 1 // 类型 Int、默认 getter 和 setter

一个只读属性的语法和一个可变的属性的语法有两方面的不同:1、只读属性的用 val开始代替var 2、只读属性不允许 setter

val simple: Int? // 类型 Int、默认 getter、必须在构造函数中初始化
val inferredType = 1 // 类型 Int 、默认 getter

我们可以为属性定义自定义的访问器。如果我们定义了一个自定义的 getter,那么每次访问该属性时都会调用它 (这让我们可以实现计算出的属性)。以下是一个自定义 getter 的示例:

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

如果我们定义了一个自定义的 setter,那么每次给属性赋值时都会调用它。一个自定义的 setter 如下所示:

var stringRepresentation: String
    get() = this.toString()
    set(value) {
        setDataFromString(value) // 解析字符串并赋值给其他属性
}

按照惯例,setter 参数的名称是 value,但是如果你喜欢你可以选择一个不同的名称。
一开始我写的时候报异常:

Your program produces too much output!

让我很是懵逼,后来发现在setter 和setter中要用field代替属性字段,不然就会报错。
正确写法:

open class Test(name: String) {
    
    var no = 0
    
    var mName: String = name
    get() = "name is : $field, no is : $no"
    set(value) {
            field = value
            no++
    }
}

错误写法:

    var mName: String = name
    get() = "name is : $mName, no is : $no"  // 应该用field来表示该属性
    set(value) {
            mName = value // 应该用field来表示该属性
            no++
    }

编译期常量
已知值的属性可以使用 const 修饰符标记为 编译期常量。 这些属性需要满足以下要求:

  • 位于顶层或者是 object 声明 或 companion object 的一个成员
  • 以 String 或原生类型值初始化
  • 没有自定义 getter

这些属性可以用在注解中:

const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { …… }

函数

函数声明

Kotlin 中的函数使用 fun 关键字声明:

fun double(x: Int): Int {
    return 2 * x
}

默认参数

函数参数可以有默认值,当省略相应的参数时使用默认值。与其他语言相比,这可以减少重载数量:

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

默认值通过类型后面的 = 及给出的值来定义。
覆盖方法总是使用与基类型方法相同的默认参数值。 当覆盖一个带有默认参数值的方法时,必须从签名中省略默认参数值:

open class A {
    open fun foo(i: Int = 10) { …… }
}
​class B : A() {
    override fun foo(i: Int) { …… }  // 不能有默认值
}

如果一个默认参数在一个无默认值的参数之前,那么该默认值只能通过使用命名参数调用该函数来使用:

fun foo(bar: Int = 0, baz: Int) { …… }
​foo(baz = 1) // 使用默认值 bar = 0

返回 Unit 的函数

如果一个函数不返回任何有用的值,它的返回类型是 Unit。Unit 是一种只有一个值——Unit 的类型。这个值不需要显式返回:

fun printHello(name: String?): Unit {
    if (name != null)
        println("Hello ${name}")
    else
        println("Hi there!")
    // `return Unit` 或者 `return` 是可选的
}

Unit 返回类型声明也是可选的。上面的代码等同于:

fun printHello(name: String?) { …… }

扩展函数

Kotlin 可以对一个类的属性和方法进行扩展,且不需要继承或使用 Decorator 模式。
扩展是一种静态行为,对被扩展的类代码本身不会造成任何影响。
扩展函数
扩展函数可以在已有类中添加新的方法,不会对原类做修改,扩展函数定义形式:

fun receiverType.functionName(params){
    body
}
  • receiverType:表示函数的接收者,也就是函数扩展的对象
  • functionName:扩展函数的名称
  • params:扩展函数的参数,可以为NULL

举例:

fun main() {
    var a: Int = 5
    println(a.multiple(3)) // 测试结果是15
}

fun Int.multiple(m: Int) = this*m

对Int类型进行扩展一个倍数的函数,参数就是倍数,直接和本身进行相乘,得出最终结果。

kotlin开源项目

Kotlin-for-Android-Developers(★1676)

介绍:这个项目其实是Kotlin-for-Android-Developers这本书的配套代码,如果你是kotlin的初学者,那么这绝对是你学习kotlin的不二之选。项目通过一个天气的例子很好的展示了kotlin带来的强大功能,比如网络数据的请求,数据的缓存设计,数据库的操作,各种扩展函数的妙用等等。

地址:https://github.com/antoniolg/Kotlin-for-Android-Developers

Bandhook-Kotlin (★1494)

介绍:Kotlin版本的音乐播放器,数据来源于LastFm。
地址:https://github.com/antoniolg/Bandhook-Kotlin

GankClient-Kotlin (★1216)

介绍:gank.io kotlin实现的干货集中营Android客户端,风格采用了Material Design。
地址:https://github.com/githubwing/GankClient-Kotlin

PoiShuhui-Kotlin(★897)

介绍:一个用Kotlin写的简单漫画APP。
地址:https://github.com/wuapnjie/PoiShuhui-Kotlin

Eyepetizer-in-Kotlin(★1167)

介绍:Kotlin版本的Eyepetizer客户端
地址:https://github.com/LRH1993/Eyepetizer-in-Kotlin

Tucao(★792)

介绍:Kotlin版本的吐槽客户端
地址:https://github.com/blackbbc/Tucao

kotlin中使用databinding

开启databinding
除了需要在buidl.gradle(moudle:app)中配置如下:

  apply plugin: 'com.android.application'
  apply plugin: 'kotlin-android'
  apply plugin: 'kotlin-android-extensions'
  apply plugin: 'kotlin-kapt'      // 添加 kapt 插件支持
 
  android {
      ...
      dataBinding { // 开启 DataBinding
          enabled true
      }
      ...
  }
 
  dependencies{
      ...
      kapt 'com.android.databinding:compiler:3.0.0  // DataBinding 注解处理依赖(移除,后面有解释)
      ...
  }

然后编译的时候会报错:

ERROR: Data Binding annotation processor version needs to match the Android Gradle Plugin version. You can remove the kapt dependency com.android.databinding:compiler:3.0.0 and Android Gradle Plugin will inject the right version.

百度了一会儿没找到解决方案,然后仔细看了一下报错信息,原来是databinding的版本和Gradle 插件的版本不匹配导致的,甚至解决方案也写出来了,直接移除databinding的依赖,Gradle 插件会自动注入相应的版本。至此,可以在kotlin中正常使用databinding了。

不能自动生成binding类
试了很多方法,包括rebuild、重启AS,甚至重启电脑都没有用,然后我不管错误直接run,居然就好了。。。

kotlin和java混合使用

优点:

  • kotlin和java互相转换非常方便

缺点:

  • 与java混用时,对于kotlin的非空判断不友好

kotlin使用总结

优点:

  • 代码看起来比较优美,java太冗长了

缺点:

  • 与databinding配合使用时,不能直接跳转到xml文件
发布了216 篇原创文章 · 获赞 91 · 访问量 25万+

猜你喜欢

转载自blog.csdn.net/yu75567218/article/details/90749910