简介
本文主要讲解 Kotlin 基础语法
。
本文是《Kotlin Jetpack 实战》的开篇。
主要内容
每个 Java 开发者都应该学 Kotlin
快速认识 Kotlin
基础语法
扩展函数
委托
结尾
正文
每个 Java 开发者都应该学 Kotlin
推荐学习 Kotlin 的理由有很多,比如:Kotlin 更简洁,Kotlin 有协程,Kotlin 有扩展函数,学了 Kotlin 后学别的语言会很快,比如:Python,Swift,Dart, Ruby…
不过,如果你是 Android 开发者,我劝你别再做无谓的挣扎了,赶紧入坑吧!
快速认识 Kotlin
Kotlin 是著名 IDE 公司 JetBrains 创造出的一门基于 JVM 的语言。Kotlin 有着以下几个特点:
- 简洁,1行顶5行
- 安全,主要指“空安全”
- 兼容,与 Java 兼容
- 工具友好,IntelliJ 对 Kotlin 简直不要太友好
JetBrains 不仅创造了 Kotlin,还创造了著名的 IntelliJ IDEA。Android 开发者使用的 Android Studio 就是基于 IntelliJ 改造出来的。
基础语法
1. 所有 Kotlin 类都是对象 (Everything in Kotlin is an object)
与 Java 不一样是:Kotlin 没有基本数据类型
(Primitive Types),所有 Kotlin 里面的类都是对象,它们都继承自: Any
这个类;与 Java 类似的是,Kotlin 提供了如下的内置类型:
Type | Bit width | 备注 |
---|---|---|
Double | 64 | Kotlin 没有 double |
Float | 32 | Kotlin 没有 float |
Long | 64 | Kotlin 没有 long |
Int | 32 | Kotlin 没有 int/Integer |
Short | 16 | Kotlin 没有 short |
Byte | 8 | Kotlin 没有 byte |
思考题1:
既然 Kotlin 与 Java 是兼容的,那么 Kotlin Int 与 Java int、Java Integer 之间是什么关系?
思考题2:
Kotlin Any 类型与 Java Object 类型之间有什么关系?
2. 可见性修饰符 (Visibility Modifiers)
修饰符 | 描述 |
---|---|
public | 与Java一致 |
private | 与Java一致 |
protected | 与Java一致 |
internal | 同 Module 内可见 |
3. 变量定义 (Defining Variables)
定义一个 Int 类型的变量:
1 | kotlin复制代码var a: Int = 1 |
定义一个 Int 类型的常量(不可变的变量?只读的变量?)
1 | kotlin复制代码val b: Int = 1 |
类型可推导时,类型申明可省略:
1 | kotlin复制代码val c = 1 |
语句末尾的;
可有可无:
1 | kotlin复制代码val d: Int; |
小结:
var
定义变量val
定义常量(不可变的变量?只读变量?)- Kotlin 支持类型自动推导
思考题3:
Kotlin val 变量与 Java 的 final 有什么关系?
4 空安全 (Null Safety)
定义一个可为空的 String 变量:
1 | kotlin复制代码var b: String? = "Kotlin" |
定义一个不可为空的 String 变量:
1 | kotlin复制代码var a: String = "Kotlin" |
变量赋值:
1 | kotlin复制代码var a: String? = "Kotlin" |
空安全调用
1 | kotlin复制代码var a: String? = "Kotlin" |
Elvis 操作符
1 | kotlin复制代码// 下面两个语句等价 |
小结:
- T 代表不可为空类型,编译器会检查,保证不会被 null 赋值
- T? 代表可能为空类型
- 不能将 T? 赋值给 T
- 使用 instance?.fun() 进行空安全调用
- 使用 Elvis 操作符为可空变量替代值,简化逻辑
5. 类型检查与转换 (Type Checks and Casts)
类型判断、智能类型转换:
1 | kotlin复制代码if (x is String) { |
不安全的类型转换 as
1 | kotlin复制代码val y = null |
安全的类型转换 as?
1 | kotlin复制代码val y = null |
小结:
- 使用
is
关键字进行类型判断 - 使用
as
进行类型转换,可能会抛异常 - 使用
as?
进行安全的类型转换
6. if 判断
基础用法跟 Java 一毛一样。它们主要区别在于:Java If
is Statement,Kotlin If
is Expression。因此它对比 Java 多了些“高级”用法,懒得讲了,咱看后面的实战吧。
7. for 循环
跟 Java 也差不多,随便看代码吧:
1 | kotlin复制代码// 集合遍历,跟 Java 差不多 |
8. when
when 就相当于高级版的 switch,它的高级之处在于支持模式匹配(Pattern Matching)
:
1 | kotlin复制代码val x = 9 |
9. 相等性 (Equality)
Kotlin 有两种类型的相等性:
- 结构相等 (Structural Equality)
- 引用相等 (Referential Equality)
结构相等:
1 | kotlin复制代码// 下面两句两个语句等价 |
引用相等:
1 | kotlin复制代码print(a === b) |
思考题4:
1 | kotlin复制代码val a: Int = 10000 |
思考题5:
1 | kotlin复制代码val a: Int = 1 |
10. 函数 (Functions)
1 | kotlin复制代码fun triple(x: Int): Int { |
11. 类 (Classes)
类定义
使用主构造器(Primary Constructor)定义类一个 Person 类,需要一个 String 类型的变量:
1 | kotlin复制代码class Person constructor(firstName: String) { ... } |
如果主构造函数没有注解或者可见性修饰符,constructor 关键字可省略:
1 | kotlin复制代码class Person(firstName: String) { ... } |
也可以使用次构造函数(Secondary Constructor)定义类:
1 | kotlin复制代码class Person { |
init 代码块
Kotlin 为我们提供了 init 代码块,用于放置初始化代码:
1 | kotlin复制代码class Person { |
以上代码输出结果为:
1 | css复制代码I am Kotlin. |
结论:init 代码块执行时机在类构造之后,但又在“次构造器”执行之前。
12. 继承 (Inheritance)
- 使用 open 关键字修饰的
类
,可以被继承 - 使用 open 关键字修饰的
方法
,可以被重写 没有
open 关键字修饰的类,不可
被继承没有
open 关键字修饰的方法,不可
被重写- 以 Java 的思想来理解,
Kotlin 的类和方法,默认情况下是 final 的
定义一个可被继承的
Base 类,其中的 add() 方法可以被重写
,test() 方法不可
被重写:
1 | kotlin复制代码open class Base { |
定义 Foo 继承 Base 类,重写 add() 方法
1 | kotlin复制代码class Foo() : Base() { |
- 使用
:
符号来表示继承 - 使用
override
重写方法
13. This 表达式 (Expression)
1 | kotlin复制代码class A { |
小结:
inner
关键字定义内部类- 在内部类当中访问外部类,需要显示使用
this@OutterClass.fun()
的语法
14. 数据类 (Data Class)
假设我们有个这样一个 Java Bean:
1 | java复制代码public class Developer { |
如果我们将其翻译成 Kotlin 代码,大约会是这样的:
1 | kotlin复制代码class Developer(var name: String?) { |
然而,Kotlin 为我们提供了另外一种选择,它叫做数据类
:
1 | kotlin复制代码data class Developer(var name: String) |
上面这一行简单的代码,完全能替代
前面我们的写的那一大堆模板 Java 代码,甚至额外多出了一些功能。如果将上面的数据类
翻译成等价的 Java 代码,大概会长这个样子:
1 | java复制代码public final class Developer { |
可以看到,Kotlin 的数据类不仅为我们提供了 getter、setter、equals、hashCode、toString,还额外的帮我们实现了 copy 方法!这也体现了 Kotlin 的简洁
特性。
序列化的坑
如果是旧工程迁移到 Kotlin,那么可能需要注意这个坑:
1 | kotlin复制代码// 定义一个数据类,其中成员变量 name 是不可为空的 String 类型,默认值是 MOMO |
对于上面的情况,由于 Gson 最初是为 Java 语言设计的序列化框架,并不支持 Kotlin 不可为空
、默认值
这些特性,从而导致原本不可为空的属性变成null
,原本应该有默认值的变量没有默认值。
对于这种情,市面上已经有了解决方案:
15. 扩展 (Extensions)
如何才能在不修改源码的情况下给一个类新增一个方法?比如我想给 Context 类增加一个 toast 类,怎么做?
如果使用 Java,上面的需求是无法被满足的。然而 Kotlin 为我们提供了扩展
语法,让我们可以轻松实现以上的需求。
扩展函数
为 Context 类定义一个 toast 方法:
1 | kotlin复制代码fun Context.toast(msg: String, length: Int = Toast.LENGTH_SHORT){ |
扩展函数的使用:
1 | kotlin复制代码val activity: Context? = getActivity() |
属性扩展
除了扩展函数,Kotlin 还支持扩展属性
,用法基本一致。
思考题6:
上面的例子中,我们给不可为空的
Context 类增加了扩展函数,因此我们在使用这个方法的时候需要判空。实际上,Kotlin 还支持我们为 可为空的
类增加扩展函数:
1 | kotlin复制代码// 为 Context? 添加扩展函数 |
扩展函数使用:
1 | kotlin复制代码val activity: Context? = getActivity() |
请问这两种定义扩展函数的方式,哪种更好?分别适用于什么情景?为什么?
16. 委托 (Delegation)
Kotlin 中,使用by
关键字表示委托:
1 | kotlin复制代码interface Animal { |
属性委托 (Property Delegation)
其实,从上面类委托的例子中,我们就能知道,Kotlin 之所以提供委托这个语法,主要是为了方便我们使用者,让我们可以很方便的实现代理
这样的模式。这一点在 Kotlin 的委托属性
这一特性上体现得更是淋漓尽致。
Kotlin 为我们提供的标准委托非常有用。
by lazy 实现”懒加载“
1 | kotlin复制代码// 通过 by 关键字,将 lazyValue 属性委托给 lazy {} 里面的实现 |
以上代码输出的结果:
1 | markdown复制代码computed! |
由此可见,by lazy 这种委托的方式,可以让我们轻松实现懒加载
。
其内部实现,大致是这样的:
lazy 求值的线程模式: LazyThreadSafetyMode
Kotlin 为lazy 委托
提供三种线程模式,他们分别是:
- LazyThreadSafetyMode.SYNCHRONIZED
- LazyThreadSafetyMode.NONE
- LazyThreadSafetyMode.PUBLICATION
上面这三种模式,前面两种很好理解:
- LazyThreadSafetyMode.SYNCHRONIZED 通过加锁实现多线程同步,这也是默认的模式。
- LazyThreadSafetyMode.NONE 则没有任何线程安全代码,线程不安全。
我们详细看看LazyThreadSafetyMode.PUBLICATION
,官方文档的解释是这样的:
Initializer function can be called several times on concurrent access to uninitialized [Lazy] instance value, but only the first returned value will be used as the value of [Lazy] instance.
意思就是,用LazyThreadSafetyMode.PUBLICATION
模式的 lazy 委托变量,它的初始化方法是可能会被多个线程执行多次的,但最后这个变量的取值是仅以第一次算出的值为准的。即,哪个线程最先算出这个值,就以这个值为准。
by Delegates.observable 实现”观察者模式”的变量
观察者模式
,又被称为订阅模式
。最常见的场景就是:比如读者们订阅了MOMO
公众号,每次MOMO
更新的时候,读者们就会收到推送。而观察者模式应用到变量层面,就延伸成了:如果这个的值改变了,就通知我
。
1 | kotlin复制代码class User { |
以上代码的输出为:
1 | sql复制代码name 改变了:<no name> -> first: Tom |
思考题7:
lazy 委托的LazyThreadSafetyMode.PUBLICATION
适用于什么样的场景?
结尾
都看到这了,点个赞呗!
下一章–>从一个膜拜大神的 Demo 开始
回目录–>《Kotlin Jetpack 实战》
本文转载自: 掘金