why kotlin
kotlin引入现代语言的所有特性,而没有引入新的限制,它适合android原生开发
- 兼容性
kotlin完全兼容jdk1.6,保证在老的设备上运行也完全没问题。 - 高性能
kotlin写的app和java的性能一样好,有些情况下甚至比java还要好 - 简洁
- 安全
对空安全的完美支持 - 互操作
Kotlin 可与 Java 进行 100% 的互操作 - 强大的工具支持
和 java 、 js 比较
特性 | kotlin | java | js |
---|---|---|---|
类型系统 | 强类型(也叫静态类型) | 强类型 | 弱类型(也叫动态类型) |
对象系统 | 一切都是对象,你可以在任何变量上调用成员函数和属性 | 分原始类型和引用类型 | 一切都是对象 |
函数式编程 | 是 | 否 | 是 |
函数的特性 | 函数也是对象,一等公民,函数可以赋值给变量,函数的定义可以不依赖于类的存在。你可以看到扩展函数的强大 | 方法的定义离不开类的定义,方法不能被赋值给变量。 | 和kotlin差不多,稍微要强大一点 |
泛型系统 | 运行时擦除,但比java强大 | 运行时擦除 | 不支持 |
协程 | 支持(实验版本) | 不支持 | 支持 |
线程系统 | 和java一样多线程 | 多线程 | 单线程 |
空安全 | 是 | 否 | 否 |
android集成
- 安装kotlin插件
as3.0之后,kotlin插件和as捆绑在一起的。如果你安装的老的版本,你需要安装kotlin插件,打开Settings | Plugins | Install JetBrains plugin,搜索Kotlin,安装,重启。 - 创建项目
和创建普通的android项目一样。 - java代码转成kotlin代码
如果是老版本的as,上面的步骤会创建一个java的activity,你可以将java代码一键转成kotlin代码,最方便的方法是执行Find Action
(⇧⌘A
),找到Convert Java File to Kotlin File
选项,敲回车,然后你就得到了kotlin版的activity。 - 在项目中配置kotlin
如果你是第一次编辑kotlin文件,as会提示你kotlin还没有配置,或者你可以执行配置选项,Tools | Kotlin | Configure Kotlin in Project
接下来你会看到下面的提示,选择的版本按照:
配置好了之后你将看到app下面的build.gradle加了apply plugin: 'kotlin-android'
和kotlin-stdlib
依赖:
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
最后一步同步gradle
基础
1. val vs var
- val表示只读的,不可变的变量
- var表示表示可读、可修改的变量
- val和var都可以用来定义属性和局部变量
class Address {
val name: String = ......
var zip: String = ......
}
fun copyAddress(address: Address): Address {
// Kotlin 中没有“new”关键字 result.name = address.name
// 将调用访问器
val result = Address()
result.name = address.name
// ......
return result
}
2.字符串
Kotlin 有两种类型的字符串字面值: 转义字符串可以有转义字符,以及原生字符串。
- 转义字符串很像 Java 字符串:
val s = "Hello, world!\n"
- 原生字符串,使用三个引号( """ )分界符括起来,内部没有转义并且可以包含换行和任何其他字符:
val text = """
for (c in "foo")
print(c)
"""
3.字符串模板
val i = 10
vals="i=$i"// 求值结果为 "i=10"
val s = "abc"
val str = "$s.length is ${s.length}" // 求值结果为 "abc.length is 3"
原生字符串和转义字符串内部都支持模板。 如果你需要在原生字符串中表示字面值 $ 字符 (它不支持反斜杠转义),你可以用下列语法:
val price = """
${'$'}9.99
"""
4. let实现if not null
val data = ......
data?.let {
...... // 代码会执行到此处, 假如data不为null
}
5.“try/catch”表达式
fun test() {
val result = try {
count()
} catch (e: ArithmeticException) {
throw IllegalStateException(e)
}
// 使用 result
}
6.单表达式函数
fun theAnswer() = 42
等价于
fun theAnswer(): Int {
return 42
}
7.对一个对象实例调用多个方法 ( with )
class Turtle {
fun penDown()
fun penUp()
fun turn(degrees: Double)
fun forward(pixels: Double)
}
val myTurtle = Turtle()
with(myTurtle) { // 画一个 100 像素的正方形
penDown()
for(i in 1..4) {
forward(100.0)
turn(90.0)
}
penUp()
}
4.控制流
- If表达式
if
是一个表达式,即它会返回一个值。 因此就不需要三元运算符。
// 作为表达式
val max = if (a > b) a else b
if
的分支可以是代码块,最后的表达式作为该块的值:
val max = if (a > b) {
print("Choose a")
a
} else {
print("Choose b")
b
}
- 返回 when 表达式
when取代了java的switch case流程控制,如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔:
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
3, 4 -> print("3 == 0 or x == 4")
else -> { // 注意这个块
print("x is not in (1, 2, 3, 4) ")
}
}
when 既可以被当 做表达式使用也可以被当做语句使用。如果它被当做表达式, 符合条件的分支的值就是整个 表达式的值,如果当做语句使用, 则忽略个别分支的值。
如果其他分支都不满足条件将会求值 else 分支。 如果 when 作为一个表达式使用,则必须 有 else 分支, 除非编译器能够检测出所有的可能情况都已经覆盖了。
fun describe(obj: Any): String =
when (obj) {
1 -> "One"
"hello" -> "Greeting"
is Long -> "Long"
!is String -> "Not a string"
else -> "Unknown"
}
fun main(args: Array<String>) {
println(describe(1))
println(describe("Hello"))
println(describe(1000L))
println(describe(2))
println(describe("other"))
}
类和对象
- Kotlin 中使用关键字 class 声明类
class Invoice {
}
- 构造函数
主构造函数是类头的 一部分:它跟在类名(和可选的类型参数)后
class Person constructor(firstName: String) {
}
如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字。
class Person(firstName: String) {
}
如果需要初始化的代码可以放到 init
关键字作为前缀的初始化块中:
class Customer(name: String) {
init {
logger.info("Customer initialized with value ${name}")
}
}
主构造的参数,也可以在类体内声明的属性初始化器中使:
class Customer(name: String) {
val customerKey = name.toUpperCase()
}
可以更方便的申明属性及从主构造函数初始化:
class Person(val firstName: String, val lastName: String, var age: Int) {
// ......
}
- 次构造函数
class Person(val name: String) {
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
- 创建类的实例
val customer = Customer("Joe Smith")
- 继承
在 Kotlin 中所有类都有一个共同的超类 Any ,没有超类型声明的类隐式继承Any:
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
关键字申明,在kotlin中继承接口和类都是用:
操作符,和java一样一个类只能继承一个类,多个接口。
- 覆盖方法
kotlin需要使用open
关键字标注该类的成员可以被覆盖,覆盖父类的方法必须使用override关键之声明:
open class Base {
open fun v() {}
fun nv() {} }
class Derived() : Base() {
override fun v() {}
}
Derived
的 v
方法允许被子类覆盖,如果要禁止被子类覆盖,应该使用 final
关键字。
class Derived() : Base() {
final override fun v() {}
}
覆盖规则:如果一个类从它的直接超类继承相同成员的多个实 现, 它必须覆盖这个成员并提供其自己的实现。为了表示采用 从哪个超类型继承的实现,我们使用由尖括号中超类型名限定的 super ,如 super<Base> :
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<A>.f() // 调用 A.f()
super<B>.f() // 调用 B.f()
}
}
- 覆盖属性
属性覆盖与方法覆盖类似:
open class Animal {
}
class Dog : Animal() {
}
open class A {
open val x: Int get() = 1
open var y: Int = 0
open val animal: Animal get() = Animal()
}
class B(override var y: Int) : A() {
override val x: Int = 2
override var animal: Dog = Dog()
set(value) {
field = value
}
}
- 对象表达式
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 foo() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
}
注意,匿名对象可以用在只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公用函数的返回类型或者用作公有属性的类型,那么该函数或属性的实际类型会是匿名对象的基类型。
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”
}
}
就像 Java 匿名内部类一样,对象表达式中的代码可以访问来自包含它的作用域的变量。(与 Java 不同的是,这不仅限于 final 变量。)
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++
}
})
// ......
}
- 对象声明
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ......
}
val allDataProviders: Collection<DataProvider>
get() = // ......
}
这也是使用单例模式方式,对象声 明不是一个表达式,不能用在赋值语句的右边。
要引用该对象,我们直接使用其名称即可:
DataProviderManager.registerDataProvider(......)
这些对象可以有超类型:
object DefaultListener : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ......
}
override fun mouseEntered(e: MouseEvent) {
// ......
}
}
注意:对象声明不能在局部作用域(即直接嵌套在函数内部),但是它们可以嵌套到其他对象声明或非内部类中。
- 伴生对象
与java不同的是,kotlin中的类没有静态方法,在大多数情况下,它建议简单地使用包级函数。伴生对象的好处是无需用一个类的实例来调用、但需要访问类内部的函数。说的白话一点就是你可以像在java中一样通过类名调用静态方法。
伴生对象使用 companion
关键字标记:
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
val instance = MyClass.create()
可以省略伴生对象的名称,在这种情况下将使用名称 Companion :
class MyClass {
companion object {
}
}
val x = MyClass.Companion
在运行时他们仍然是真实对象的实例成员,而且还可以实现接口:
interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
}
- 对象表达式和对象声明之间的语义差异
- 对象表达式是在使用他们的地方立即执行(及初始化)的
- 对象声明是在第一次被访问到时延迟初始化的
- 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配
委托
- 委托模式,kotlin通过类委托原生支持:
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
}
- 委托属性
- 延迟属性(lazy properties): 其值只在首次访问时计算
- 可观察属性(observable properties): 监听器会收到有关此属性变更的通知
- 把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中
延迟属性 Lazy:
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
fun main(args: Array<String>) {
println(lazyValue)
println(lazyValue)
}
上面将输出:
computed!
Hello
Hello
注意,lazy属性的求值默认是同步的。如果多个线程可以同时执行,那么请将
LazyThreadSafetyMode.PUBLICATION
作为参数传递给传给lazy()
函数。如果你确定初始化将总是发生在单个线程,那么你可以使用LazyThreadSafetyMode.NONE
模 式, 它不会有任何线程安全的保证和相关的开销。
- 可观察属性 Observable
class User {
var name: String by Delegates.observable("initialName") {
property, oldValue, newValue ->
println("${property.name}, $oldValue -> $newValue")
}
}
fun main(args: Array<String>) {
val user = User()
user.name = "first"
user.name = "second"
}
上面将输出:
name, initialName -> first
name, first -> second
如果你想能够截获一个赋值并“否决”它,就使用 vetoable() 取代 observable() 。 在属性被 赋新值生效之前会调用传递给 vetoable 的处理程序。
class User {
var age: Int by Delegates.vetoable(0) {
property, oldValue, newValue ->
newValue > 0
}
}
fun main(args: Array<String>) {
val user = User()
user.age = -100
println(user.age)
user.age = 10
println(user.age)
user.age = -10
println(user.age)
}
上面的代码将输出:
0
10
10
- 把属性储存在映射中
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
println(user.name) // Prints "John Doe"
println(user.age) // Prints 25
我画了一张图讲解可见性修饰符。
其他
2.集合
与大多数语言不同,Kotlin 区分可变集合(MutableList、MutableSet、MutableMap)和不可变集合(List、Set、Map),精确控制 什么时候集合可编辑有助于消除 bug 和设计良好的 API。
我们可以看下 list 及 set 类型的基本用法:
val numbers: MutableList<Int> = mutableListOf(1, 2, 3)
val readOnlyView: List<Int> = numbers
println(numbers) // 输出 "[1, 2, 3]"
numbers.add(4)
println(readOnlyView) // 输出 "[1, 2, 3, 4]"
readOnlyView.clear() // -> 不能编译
val strings = hashSetOf("a", "b", "c", "c")
assert(strings.size == 3)
Kotlin 没有专门的语法结构创建 list
或 set
。 要用标准库的方法,如 listOf()
、 mutableListOf()
、 setOf()
、 mutableSetOf()
。 在非性能关键代码中创建 map
可以用一个简单的惯用法来完成: mapOf(a to b, c to d)
有时你想给调用者返回一个集合在某个特定时间的一个快照, 一个保证不会变的:
class Controller {
private val _items = mutableListOf<String>()
val items: List<String> get() = _items.toList()
}
List 和 set 有很多有用的扩展方法值得熟悉:
val items = listOf(1, 2, 3, 4)
items.first() == 1
items.last() == 4
items.filter{it%2==0} // 返回 [2,4]
val rwList = mutableListOf(1, 2, 3)
rwList.requireNoNulls() // 返回 [1, 2, 3]
if (rwList.none { it > 6 }) println("No items above 6") // 输出“No items above 6”
val item = rwList.firstOrNull()
...... 以及所有你所期望的实用工具,例如 sort、zip、fold、reduce 等等。
Map 遵循同样模式:
val readWriteMap = hashMapOf("foo" to 1, "bar" to 2)
println(readWriteMap["foo"]) // 输出“1”
readWriteMap.put("aaa", 1)
val snapshot: Map<String, Int> = HashMap(readWriteMap)
val readMap = mapOf<String, Int>("foo" to 1, "bar" to 2)
println(readWriteMap["foo"]) // 输出“1”
3.区间
if(i in 1..10){ // 等同于 1<=i && i<=10
println(i)
}
序迭代数字
for (i in 4 downTo 1) print(i) // 输出“4321”
step
for (i in 1..4 step 2) print(i) // 输出“13”
for (i in 4 downTo 1 step 2) print(i) // 输出“42”
不包括其结束元素的区间:
for(i in 1 until 10) { //i in [1,10) 排除了 10
println(i)
}
4.类型检查
is
操作符,来检查对象是否符合给定类型,以及它的否定形式 !is
:
if (obj is String) {
print(obj.length)
}
if (obj !is String) { // 与 !(obj is String) 相同
print("Not a String")
} else {
print(obj.length)
}
智能转换
fun demo(x: Any) {
if (x is String) {
print(x.length) // x 自动转换为字符串
}
}
编译器太智能了:
if (x !is String) return
print(x.length) // x 自动转换为字符串
在 && 或者 || 的右侧:
// `||` 右侧的 x 自动转换为字符串
if (x !is String || x.length == 0) return
// `&&` 右侧的 x 自动转换为字符串
if (x is String && x.length > 0) {
print(x.length) // x 自动转换为字符串
}
when -表达式
和 while -循环
也具有职能转换的功能:
when (x) {
is Int -> print(x + 1)
is String -> print(x.length + 1)
is IntArray -> print(x.sum())
}
注意智能转换的适用条件是变量在检查和使用期间不会被改变,规则大致如下:
-
val
局部变量——总是可以 -
val
属性——如果属性是private
或internal
,或者该检查在声明属性的同一模块中执 行。智能转换不适用于open
的属性或者具有自定义getter
的属性 -
var
局部变量——如果变量在检查和使用之间没有修改、并且没有在会修改它的 lambda 中捕获 - var 属性——决不可能
5.this表达式
为了表示当前的 接收者 我们使用 this 表达式:
- 在
类
的成员中,this 指的是该类的当前对象 - 在
扩展函数
或者带接收者的函数字面值
中,this
表示在点左侧传递的 接收者 参数。
如果 this 没有限定符,它指的是最内层的包含它的作用域。要引用其他作用域中的 this ,请使用 标签限定符(限定的 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
}
}
}
}
6.异常
Kotlin 中所有异常类都是 Throwable
类的子孙类。 每个异常都有消息、堆栈回溯信息和可选 的原因。
使用 throw
-表达式来抛出异常。
throw MyException("Hi There!")
使用 try -表达式来捕获异常。
try {
// 一些代码
}
catch (e: SomeException) {
// 处理程序
}
finally {
// 可选的 finally 块
}
Try 是一个表达式
try -表达式的返回值是 try 块中的最后一个表达式或者是(所有) catch 块中的最后一个 表达式。 finally 块中的内容不会影响表达式的结果。
val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }
受检的异常
Kotlin 没有受检的异常
要想了解更深入的了解kotlin的lambda
和函数的特性,请看 kotlin中级篇
好了,我们老的项目或者一些库需要迁移,下面分享我在迁移过程中遇到的坑