Kotlin基础入门

1. 简介

1.1. 历史发展

  • 2011年7月JetBrains推出Kotlin项目,这是一个面向JVM的新语言,它已被开发一年之久。
  • 2012年2月JetBrains以Apache 2许可证开源此项目,Jetbrains希望这个新语言能够推动IntelliJ IDEA的销售。
  • 2016年2月15日发布1.0版,被认为是第一个官方稳定版本,并且JetBrains已准备从该版本开始的长期向后兼容性。
  • 2017年3月,JetBrains发布Kotlin 1.1版本,Kotlin在全球范围内成长显著。
  • 2017年5月18日Google I/O大会上宣布Kotlin正式成为Android的官方一级开发语言,同时AS3.0默认集成Kotlin Plugin。
  • 2017年11月,JetBrains发布了Kotlin 1.2版本,推出了多平台项目特性,可以将原始代码编译成多个平台的目标代码,目前支持JVM和JavaScript。
  • 2018年2月,Google发布了Android KTX扩展库,目前已属于Android Jetpack系列中的一员,简单的说Android KTX旨在让我们利用Kotlin语言功能(例如扩展函数/属性、lambda、命名参数和参数默认值),以更简洁、更愉悦、更惯用的方式使用Kotlin进行Android开发。甚至可以简单理解为Google为Kotlin准备的适配Android的一系列xxxUtils工具。这个库由Jake Wharton负责维护。
  • 2018年10月,JetBrains发布了Kotlin 1.3版本,这个版本最重要的特性就是协程,它使得非阻塞代码易于读写。此外在多平台方面,支持了支持 JVM、Android、JavaScript和Native。这意味着Kotlin能进行前端、移动端以及后端代码的开发了。
  • 目前已有非常多的开源库推出Kotlin版本:RxKotlinkotterknifeleakcanarymaterial-dialogskotlin-web-sitekotlin-dslSwiftKotlinkotlinconf-appDesign-Patterns-In-Kotlinankorecyclerview-animatorsglide-transformationsretrofit2-kotlin-coroutines-adapter

1.2. APP集成现状

使用 未使用
Pinterest    Evernote    Uber    Facebook    twitter    微信读书    豆瓣    钉钉    京东    百度    抖音    今日头条    爱奇艺 YouTube    Instagram    迅雷    微信    快手    手机QQ    手机淘宝

1.3. 优势

1.4. 学习资料

2. 基本语法

下面介绍Kotlin的最重要的基础语法,里面的某些示例代码参考github上的仓库:SampleOfKotlin-Basic

2.1. Defining functions

  • 调用的时候可以显式的标示参数名。
  • 可以提供默认参数值,减少重载有奇效。
  • 【示例见SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.definingFunctions】
private fun double(x: Int = 100, y: Int = 200): Int {
    return (2 * x) + y
}

2.2. Defining variables

  • Kotlin具有可推倒特性。
  • val:只读变量用val关键字,它仅能被赋值一次。val表示可读的变量(read-only,注意不是immutable)。
  • var:可被重复赋值的变量用var关键字。var表示变量,是可变的(mutable)。
    • val与var的区别在于:val是无法提供set函数的,只有get函数。
  • const:用于定义常量,准确的说const所定义的常量叫编译时常量
    • 如果它的值无法在编译时确定,则编译不过,这个常量跟Java的static final是有所区别的。这里就是说你定义的const val常量要即时赋值,而不能稍后再赋值。
    • const无法定义局部变量,因为局部变量会存放在栈区,它会随着调用的结束而销毁,这点跟Java的static final一致。
    • 所以const的使用必须满足:1. 顶层属性或者object的成员,2. String或者基本类型的值,3. 没有自定义 getter。
    • const的使用是一个容易出错的点,我们应该好好理解它的规则。
  • 【示例见SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic】

2.3. Using string templates

  • 字符串模版配合表达式可以输出丰富的字符串。
  • """中可以包含换行、反斜杠等等特殊字符。
  • 【示例见SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.usingStringTemplates】
val i = 10
println("i = $i") // prints "i = 10"

val s = "abc"
println("$s.length is ${s.length}") // prints "abc.length is 3"

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


            这里展示 / or // or \  or \\
            """

2.4. Using loop

//注意这里Followers是集合类型
for (item in Followers) print(item)

for (i in 1..10) println(i)

for (i in 1 until 10) println(i)

for (i in 10 downTo 1) println(i)

for (i in 1..10 step 2) println(i)

repeat(10) {
 println(it)
}

for ((index, str) in list.withIndex()) {
 println("第${index}个元素$str")
}
while (x > 0) {
    x--
}

do {
    val y = retrieveData()
} while (y != null) // y is visible here!

2.5. Operations

  • 位运算符只可用在Int和Long类型上
  • 左移(in java <<) → shl(bits)
  • 右移(in java >>) → shr(bits)
  • 无符号右移(in java >>>) → ushr(bits)
  • 按位与 → and(bits)
  • 按位或 → or(bits)
  • 按位异或 → xor(bits)
  • 按位取反 → inv()
    • 原码:符号位加上真值的绝对值。

    • 反码:正数的反码是其本身;负数的反码是在其原码的基础上,符号位不变,其余各个位取反。

    • 补码:正数的补码就是其本身;负数的补码是在其原码的基础上,符号位不变,其余各位取反,最后+1。(即在反码的基础上+1)

    • 求证~8的过程为什么是-9?

    • 求证-8无符号右移2位后为什么是1073741822?

      ~8的求证过程:
      8原码:0000 0000 0000 0000 0000 0000 0000 1000
      8反码:0000 0000 0000 0000 0000 0000 0000 1000
      8补码:0000 0000 0000 0000 0000 0000 0000 1000
      ~后的补码:1111 1111 1111 1111 1111 1111 1111 0111
      -1后转成反码:1111 1111 1111 1111 1111 1111 1111 0110
      再转成原码:1000 0000 0000 0000 0000 0000 0000 1001
      即十进制:-9

      -8 >>> 2的求证过程:
      -8原码:1000 0000 0000 0000 0000 0000 0000 1000
      -8反码:1111 1111 1111 1111 1111 1111 1111 0111
      -8补码:1111 1111 1111 1111 1111 1111 1111 1000
      右移两位后的补码:0011 1111 1111 1111 1111 1111 1111 1110
      由于是正数,所以补码、反码、原码一直,所以上面的补码就是原码,
      所以00111111111111111111111111111110转成十进制就是1073741822

2.6. Returns and Jumps

  • return/break/continue。
  • 可以根据需求,返回到指定的标签处。
  • 【示例展示见官网链接】

2.7. Object Expressions and Declarations

  • 可用于创建匿名类的对象。
  • 如果匿名类中包含构造函数,则必须传递合适的入参给它。
  • 可以创建一个临时对象,而不用声明data class(or javabean)。
  • 对象声明总是在object关键字后跟一个名称,就像变量声明一样,但对象声明不是一个表达式,不能用在赋值语句的右边。使用的时候直接通过该名称引用其中的函数即可。
  • 伴生对象(即著名的companion object),伴生对象的成员可通过只使用类名来调用其中的函数(类似Java中的静态方法)。尽管伴生对象看起来像静态成员,但实际上在运行时它仍然是真实对象的实例成员。
  • 【示例见SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.object】
//可用于创建匿名类的对象。
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
}

//对象声明
object DataProviderManager {
    fun registerDataProvider(provider: String) {
        echo("$provider 注册成功")
    }
}
DataProviderManager.registerDataProvider("1号数据源")

2.8. Classes and Inheritance

  • 类及构造函数
    • 构造函数分主次: primary constructor、Secondary Constructors
  • 在Koltin中继承和实现都通过 :
  • 静态函数的实现:【示例见SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.staticMethod】
    • 在Kotlin中没有类似java的静态方法,想要使用可以考虑采用包级函数代替。
    • 还要一种便利的方式就是通过伴生对象:companion object
//类及构造函数
class Person(val firstName: String, val lastName: String, var age: Int) {
    constructor(parent: Person) {
        parent.children.add(this)
    }
}

//继承的实现
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
}

//抽象类
open class Base {
    open fun f() {}
}

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

2.9. Data Classes

类似JavaBean,不过我们不需要再写get/set方法,还有equals()/hashCode()对以及toString()方法都会被自动生成。

data class Customer(val name: String, val email: String)

2.10. Interfaces

  • 实现多个父类,可以灵活解决方法覆盖的冲突问题。
  • 想要实现一个匿名内部类实例,需要通过object关键字。
  • 【示例见SampleOfKotlin-Basic → Interface】
//多父类下,方法覆盖冲突问题
interface A {
    fun foo() { print("A") }
    fun bar()
}

interface B {
    fun foo() { print("B") }
    fun bar() { print("bar") }
}

class C : A {
    override fun bar() { print("bar") }
}

class D : A, B {
    override fun foo() {
        super<A>.foo()
        super<B>.foo()
    }

    override fun bar() {
        super<B>.bar()
    }
}

//匿名内部类
D().setClickListener(object : ClickListener {
    override fun countClick() {
        Log.d(this@MainActivity.localClassName, "点击回调")
    }
})

2.11. Extensions

  • 扩展函数:Kotlin中的扩展函数是静态的给一个类添加方法的,不具备运行时的多态效应。
    • String.lastChar()
    • Activity.hideKeyboard()
  • 扩展属性
    • List<T>.lastIndex
  • 【示例见SampleOfKotlin-Basic → Extensions】

2.12. Generics

  • Java中泛型的特点

    • 类型擦除(在编译期间擦掉类型信息)
      • 泛型类型参数之间没有继承关系(ArrayList<Object> arrayList1=new ArrayList<String>(); //编译错误)
    • 不可具体化的类型(运行时包含的信息比它的编译时包含的信息更少)
    • 通配符(有界通配符和无界通配符)
  • Kotlin中泛型特点

    • 声明处型变:out和in

      • out:当一个类(List)的类型参数(E)被声明为out时,我们就可以将子类泛型的对象赋值给使用父类泛型的对象,即通常所说的类List是在参数E上是协变的。但是副作用是它只能出现在该类成员的输出位置上。
      • in:当一个类(Consumer)的类型参数(E)被声明为in时,我们就可以将父类泛型的对象赋值给使用子类泛型的对象,即通常所说的类Consumer在参数E上是逆变的。但副作用是它只能出现在该类成员的输入位置。
      • 协变和逆变关系图:


        in-out关系图.png
    • 类型投影

      • 使用处型变
        • Kotlin的使用处型变直接对应Java的有界通配符。Kotlin中的MutableList<out T>和Java中的MutableList<? extends T>是一个意思。in投影的MutableList<in T>对应到Java的MutableList<? super T>。
      • 星号投影
        • Kotlin的MyType<*>相当于Java中的MyType<?>。
    • 同时限定多个类型

    • Kotlin中可以获取泛型的实际类型

      • inline(内联函数) + reified(实化类型)能处理方法级别的真泛型
        • 我们都知道内联函数的原理,编译器把实现内联函数的字节码动态插入到每次的调用点。那么实化的原理正是基于这个机制,每次调用带实化类型参数的函数时,编译器都知道此次调用中作为泛型类型实参的具体类型。所以编译器只要在每次调用时生成对应不同类型实参调用的字节码插入到调用点即可。
      • 类级别的泛型需要自己改造
        • 通过伴生对象结合重载invoke调用操作符的方式
  • 对比Java与Kotlin中的使用对比

~ 协变 逆变 不型变
Java ? extend E ? super E ArrayList<Apple>
Kotlin out E in E MutableList<Apple>

Java中的泛型是不型变的,这意味着List<RedApple>并不是List<Apple>的子类型。而通过有界通配符extends关键字使得类型可以协变,即可以让Collection<RedApple>表示为Collection<? extends Apple>的子类型。

  • 【示例见SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.generic】

2.13. Null Safety

  • ? 可为空,不会抛出NPE
  • !! 不可为空,会抛出NPE
  • String和String?是两种完全不同的数据类型,简单来说就是String?类型包含了String类型,使用的时候务必小心变量是否为空。
  • Java代码与Kotlin代码互调注意点:在Kotlin代码中,接收一个Java对象的时候,如果你不确定是否可能空,务必将其赋值为可空类型的变量,这样才能保证代码是安全的,否则的话极有可能抛出NPE。
  • 【示例见SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.nullSafe】

2.14. Lambda

  • 如果Lambda没有参数,可以省略箭头符号
  • 如果Lambda是函数的最后一个参数,可以将大括号放在小括号外面
  • 如果函数只有一个参数且这个参数是Lambda,则可以省略小括号
  • 【示例见SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.lambda】
//如果Lambda没有参数,可以省略箭头符号
fun main(args: Array<String>){        
    val thread = Thread({ })        
    thread.start()    
}

//如果Lambda是函数的最后一个参数,可以将大括号放在小括号外面
fun main(args: Array<String>){        
    val thread = Thread(){ }        
    thread.start()    
}

//如果函数只有一个参数且这个参数是Lambda,则可以省略小括号
fun main(args: Array<String>){        
    val thread = Thread{ }        
    thread.start()    
}

2.15. Lambda闭包声明

  • Lambda+闭包
  • 【示例见SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.lambda】
val echo = { name: Any -> println(name) }

val lambdaA = { a: Int, b: Int, c: Int, d: Int, e: Int, f: Int, g: Int, h: Int,
                i: Int, j: Int, k: Int, l: Int, m: Int, n: Int, o: Int, p: Int,
                q: Int, r: Int, s: Int, t: Int, u: Int, v: Int, w: Int ->
    println("23个入参的闭包")
}

2.16. Higher-Order Functions

  • 高阶函数指函数或者Lambda的参数又是一个函数或者Lambda。
  • 当Lambda作为函数参数的最后一个时,是可以写到小括号外面的。
  • 【示例见SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.lambda】
private fun resultByOpt(num1: Int, num2: Int, result: (Int, Int) -> Int): Int {
    return result(num1, num2)
}

2.17. Visibility Modifiers

  • Kotlin中的访问修饰符internal : 模块内可以调用,跨模块则不行。这里的模块指类似AS中的module。
  • 如果不特意声明,默认是public
  • classes, objects, interfaces, constructors, functions, properties and their setters can have visibility modifiers。
  • 【示例见SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.visible】

2.18. Delegation

  • Kotlin的动态代理本质上是通过静态代理去调用的
  • 通过关键字by
  • 【示例见SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.delagation】

2.19. Sealed Classes

  • 超级枚举,可提高程序扩展性。
  • 【示例见SampleOfKotlin-Basic → com.leeeyou.sampleofkotlin.basic.seal】

3. 扩展

3.1. 为什么能实现扩展函数和属性这样的特性?

3.1.1. 边界

  • Kotlin语言最后需要编译为class字节码,Java也是编译为class执行。
  • 可以理解为Kotlin需要转成Java一样的语法结构, Kotlin就是一种强大的语法糖而已。
  • 在虚拟机规范范围以内提供外界最便利的支持,虚拟机不具备的功能Kotlin也不能越界。

3.1.2. Kotlin与Java互转

  • Kotlin转Java
    • Tools ---> Kotlin ---> Show Kotlin Bytecode
    • Decompile
  • Java转Kotlin
    • Java代码 ---> 选择Code ---> Convert Java File to Kotlin File

3.1.3. 源码解析

  • 对于扩展函数,转化为Java的时候其实就是一个静态的函数,同时这个静态函数的第一个参数就是该类的实例对象。
  • 扩展函数和扩展属性内只能访问到类的公有方法和属性,私有的和protected是访问不了的。
  • 扩展函数不是真的修改了原来的类,定义一个扩展函数不是将新成员函数插入到类中,扩展函数的类型是"静态的",不是在运行时决定类型。

3.2. kotlin-android-extensions插件原理介绍

  • 在示例项目中有使用到插件:apply plugin: 'kotlin-android-extensions'
  • 观察MainActivity这个页面中view的引用方式
  • public View _$_findCachedViewById(int var1){}
  • private HashMap _$_findViewCache;

3.3. Kotlin与Swift的对比

Kotlin-Swift.png

4. 总结

4.1. Kotlin基础语法总结

  • 语法简单,不啰嗦
  • 空指针安全
  • 支持方法扩展和属性扩展
  • Lambda, 高阶函数,闭包支持,函数式编程支持
  • 字符串模板,密闭类,代理模式,函数默认参数,data class
  • 与Java交互性好

4.2. 原理扩展以及与Swift对比

  • 利用好Kotlin与Java互转,掌握工具的使用
  • 让你有种可以写swift app的“幻觉”

4.3. 关于转向Kotlin

  • 对于个人的项目来转向Kotlin,通常不是很难的选择。
  • 让团队转用Kotlin,困难可能来自学习成本、历史包袱、编程思维的转变。
  • 目前Kotlin已推出稳定版1.3,大公司的许多app中也引入了Kotlin,在2019年04月编程语言排行榜中Kotlin排到35位,加上google的加持,引入Kotlin改写或重构代码,让团队成员自我提升,似乎无需置疑,好处多多。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容