Kotlin使用浅谈(一)
黑鲨时刻项目Kotlin实践效果分享
学习/Kotlin#
我能告诉你什么
- Kotlin初中级(与Java)差异的语法,常用的语法
- 使用Kotlin过程中需要注意的一些规范(坑)
- Kotlin对比Java,以及对你的开发有哪些(正面或负面)的影响
我无法告诉你什么
- 我的项目要不要切换成Kotlin
- Kotlin对比Java性能怎么样
- Kotlin与Java互通的深层工作机制等
什么是Kotlin
- 官网定义:
Modern, concise and safe programming language(太谦虚)
(更)现代、(更)简洁、(更)安全的编程语言
- 大神怎么说:
- 17位谷歌专家称号大牛:
- Kotlin 对于安卓开发者有很多优势。采用 Kotlin 开发,我的代码变得简洁而又健壮!
- 你听说过传说中的 10 倍效率开发者吗? Kotlin 有时候让我有种自己就是 10 倍效率开发者的感觉。
- 一旦你开始用 Kotlin 编程,你就根本不会再用 Java 了。
- ...
- Steve Yegge:为什么说 Kotlin 比你们用的那些垃圾语言都好
- Jake Wharton直接从Square离职,加入Google Kotlin工作组
- 郭霖:之前喷kotlin,觉得用了kotlin后项目的构建速度会大幅降低,现在我改真香了!
- 黑鲨巨佬尼奥👎 (2017年7月13日的某条朋友圈):用过Kotlin后,看Java就像一个啰哩叭嗦的老妈子,再也回不去的Java😕,啾咪。
- 我的理解:
包含多种现代语言特征、符合现代编程习惯、语法简洁、语法糖丰富、空指针安全、与Java互通的JVM语言
为什么要用Kotlin
- Android一级开发语言,Google习惯性始乱终弃的例外
- 全面的Java兼容性
- 体验一致的开发环境
- 空指针安全
- (相对于Java)极简的语法规则(杀手锏)
- (相对于Java)极少的模板代码
- 跨平台(gradle脚本、spring框架、kotlin/Native、kotlin/JS)
基本语法
object
对象定义
存在形式
在JVM中,编译后对应的是一个自动实现单例的类
Kotlin
实现
object Name {
....
}
Java
实现
public final class Name {
@NotNull
public static final Name INSTANCE;
private Name () {
}
...
static {
Name var0 = new Name();
INSTANCE = var0;
}
}
申明静态函数
object Name {
@JvmStatic
fun getFistName() = "neo"
}
匿名对象申明
匿名对象的简单使用
fun foo() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
}
匿名对象的返回类型
匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数的返回类型或者用作公有属性的类型,那么该函数或属性的实际类型会是匿名对象声明的超类型,如果你没有声明任何超类型,就会是
Any
。在匿名对象中添加的成员将无法访问。
class C {
// 私有函数,所以其返回类型是匿名对象类型: object:Any
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”
}
}
属性定义
只读属性,只能赋值一次
val a: Int = 1 // 立即赋值
val b = 2 // 自动推断出 `Int` 类型
val c: Int // 如果没有初始值类型不能省略
c = 3 // 明确赋值
//编译器常量,等价于java的private final static String BS = "blackshark";
private const val BS = "blackshark"
延迟初始化,只有在该属性被使用时才进行初始化
val p by lazy {
var str: String = "nothing"
...
//return str
str
}
可读可写变量
var a: Int = 1 //标准定义
var b = 5 //自动类型推导
b += 1
延迟初始化,在将来合适的位置初始化,不可用于基础类型,且必须要初始化
lateinit var str: String
Getters
与 Setters
val
只有get()
,var
有get()/set()
val isEmpty: Boolean
get() = this.size == 0
val hash: String
get() {
val hc = this.hashcode
return hc
}
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // 解析字符串并赋值给其他属性
}
幕后字段与幕后属性
幕后字段field
为该属性实际内存指向的引用,只能用在属性的访问器内
var counter = 0
set(value) {
if (value >= 0) field = value
}
null
值检测
kotlin
要求显式申明类型是否可空
val a: Int? = 1
val b: Long = 2L
val c: String? = null
基础类型可空时,编译成.class时自动装箱
val a:Int = 1 //kotlin
//编译成基础类型
//int a = 1; //java
val b:Long? = -1L //kotlin
//编译成包装类型
//Long b = -1L; //java
可空属性的使用
可空值使用时必须带?
,且链式调用时需要一直传递下去
var data: Data? = null
println("${data?.name?.firstName}")
如果链式调用时排除了空值的可能性,接下来的调用可以移除?
var response:Response? = null
...
response?.body()
?.contentType()
.run { Derived(name = "neo", lastName = "he") }
.lastName
可空属性可以通过?:
操作符赋予默认值
var data: Body? = null
data = response?.body() ?: Body(token = "nothing")
也可以通过?:return
阻断非法数据的继续流转
var data: Body? = null
data = response?.body() ?: return
也可以抛出异常
var data: Body? = null
data = response?.body() ?: throw Exception(message = "body is null")
字符串模板
简单属性值,使用$
前缀
var a = 1
// 模板中的简单名称:
val s1 = "a is $a"
复杂值或表达式,使用${}
包括模板内容
a = 2
// 模板中的任意表达式:
val s2 = "${s1.replace("is", "was")}, but now is $a"
预格式化字符串使用""" """
,默认 |
用作边界前缀,但你可以选择其他字符并作为参数传入,比如trimMargin(">")
val text = """
|Tell me and I forget.
|Teach me and I remember.
|Involve me and I learn.
| ---(Benjamin Franklin)
| current time:${System.currentTimeMillis()}
""".trimMargin()
函数结构
标准函数结构
println(sum(1, 2))
//标准函数结构
fun sum(a: Int, b: Int): Int {
//return a + b
return a.plus(b)
}
返回值类型推导
println(times(3, 4))
//返回值类型推导
fun times(a: Int, b: Int) = a.times(b) //a * b
无返回值
div(12, 6)
//无返回值
fun div(a: Int, b: Int): Unit {
//println(a / b)
println(a.div(b))
}
//省略Unit
fun div(a: Int, b: Int) {
//println(a / b)
println(a.div(b))
}
fun div(a: Int, b: Int) = println(a.div(b))
可变参数
使用关键字vararg
fun <T> asList(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts) // ts is an Array
result.add(t)
return result
}
//调用
val list = asList(1, 2, 3)
默认参数
由于默认参数的存在,一个函数定义,可以编译生成多个级联调用函数
fun div(a: Int, b: Long) { ... }
fun div(a: Int, b: Long = 1L) { ... }
fun div(a: Int = 0, b: Long) { ... }
fun div(a: Int = 0, b: Long = 1L) { ... }
//调用
div(1, 2L)
div(1)
div(2L)
div()
具名参数
由于默认参数的存在,多个同类型参数的不同默认参数签名可能会导致误解,可以通过具名参数显示申明,更便于阅读和编译
fun div(a: Int = 0, b: Int = 0, c: Int) { ... }
//调用
//div(1) 错误
div(c = 1)
中缀表示法
关键字infix
修饰,并且是有且只有一个参数的扩展函数
infix fun Int.shl(x: Int): Int { …… }
// 用中缀表示法调用该函数
1 shl 2
// 等同于这样
1.shl(2)
类定义
创建对象
创建新的对象,无new
关键字
val persion = Person()
val person = Person("Neo")
val person = Person(firstName = "Neo")
构造函数
无参构造定义
class Person {
//...
}
默认有参构造
class Person (firstName: String) { /*……*/ }
多构造
class Person constructor(firstName: String) {
constructor(firstName: String, email: String) : this(firstName) {
//...
}
//...
}
私有构造
class Person private constructor(firstName: String) { /*……*/ }
级联调用的多构造
class Person constructor(
firstName: String = "neo",
familyName: String = "he"
) {
//...
}
初始化
构造函数初始化代码块init{}
,执行主构造的初始化逻辑
class Person constructor(firstName: String) {
init {
println("the firstName of the person is $firstName")
//...
}
//...
}
伴生对象companion object
,符合object
所有特征,并与class
同时产生,它可以直接访问class
中的所有属性
companion object {
//静态变量、常量、静态函数初始化
}
companion object Static {
//静态变量、常量、静态函数初始化
}
继承与实现
普通类默认final
修饰,若需要被继承,则需要显式open
修饰
接口、抽象类默认open
修饰
open class Mom
abstract class Dad
interface School
class Son : Mom() { /*...*/ }
class Son : Dad(), School { /*...*/ }
方法复写,父类函数默认final
修饰,允许复写需要显式的用open
修饰
open class Mom {
fun pretty() { /*...*/ }
open fun funny() { /*...*/ }
}
class Son : Mom() {
final override fun funny() {
super.funny()
/*...*/
}
fun handsome() { /*...*/ }
}
属性复写,父类属性默认final
修饰,允许复写需要显式的用open
修饰
open class Dad {
open val money = 1_000
}
class Son : Dad() {
override val money = -1_000
}
集成初始化顺序是Dad.constructor
->Dad.init
->Dad.field
->Son.init
->Son.field
,所以不能在父类的构造函数体或者init
代码块中执行open
的函数或属性,避免不幸的bug出现
内部类
嵌套类
编译成Java后为static class,无法访问外部类的属性和方法
class Parent {
private val money = 200
fun makeMoney(): Int {
println("earn $money yuan per month")
return money
}
class Child {
fun spendMoney() {
println("i have no money")
}
}
}
内部类
可以直接访问外部类的属性和方法,编译成Java不是static class
class Parent {
private val money = 200
...
inner class Child {
fun spendMoney() {
val money = makeMoney()
println("spend half money:${money / 2}")
}
}
}
这里的修饰符逻辑与Java相反
匿名内部类
ktolin
可以通过对象表达式object
实现一个接口类/抽象类,或者一个匿名模板类
匿名实现接口/抽象类
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { …… }
override fun mouseEntered(e: MouseEvent) { …… }
})
条件表达式
if-else
表达式
Kotlin
没有三元表达式,都是用if()...else...
代替
标准用法
//条件分别返回
fun maxOf(a: Int, b: Int): Int {
if (a > b) {
return a
} else {
return b
}
}
//表达式整体返回
fun maxOf(a: Int, b: Int): Int {
return if (a > b) {
a
} else {
b
}
}
类型推导
fun maxOf(a: Int, b: Int) = if (a > b) a else b
when
表达式
等价于Java
中的switch-case
链,对主语句中的条件值进行判定
fun condition(x: Any) = when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
in 3..4 -> print("x is 3 or 4")
in 5 until 7 -> print("x is 5 or 6")
is Int -> print("x is Int")
else -> { // 注意这个块
print("x is illegal")
}
}
主语句无条件值,可替代if-else-if-else
链
when {
x.isOdd() -> print("x is odd")
y.isEven() -> print("y is even")
else -> print("x+y is odd.")
}
for
循环
标准循环
//array
for (item: Int in array) {
// ……
}
//list
for (item: Int in list) {
// ……
}
//map
for ((k, v) in map) {
println("$k -> $v")
}
区间循环
//闭区间
for (i in 1..3) {
println(i)
}
//左闭右开
for (i in 1 until 4) {
println(i)
}
数组下标循环
for (i in array.indices) {
println(array[i])
}
带步进的循环
//升序步进
for (x in 1..10 step 2) {
print(x)
}
//降序步进
for (x in 9 downTo 0 step 3) {
print(x)
}
while
循环与Java
相同
kotlin
特有的常用用法
POJO的创建
标准创建
data class Customer(val name: String, var email: String = "")
此时它已经具备了以下功能:
- 所有属性的 getters (对于
var
定义的还有 setters) equals()
hashCode()
toString()
copy()
- 所有属性的
component1()
、component2()
……等等(参见数据类)
通过var
/var
控制属性的读写可见性
数据类不能是抽象、开放、密封或者内部的
实现序列化
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
...
//启动kotlin序列化注解
androidExtensions {
experimental = true
}
...
}
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
@Parcelize
data class Customer(val name: String, var email: String = "") : Parcelable
解构申明
val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // 输出 "Jane, 35 years of age"
内置数据类
标准库提供了 Pair
与 Triple
,减少模板代码
TODO()
Kotlin 的标准库有一个 TODO()
函数,该函数总是抛出一个 NotImplementedError
。 其返回类型为 Nothing
,因此无论预期类型是什么都可以使用它。 还有一个接受原因参数的重载:
fun calcTaxes(): BigDecimal = TODO("Waiting for feedback from accounting")
默认函数的空实现IDEA会自动添加TODO()
,如果短期内不实现,一定要移除
SAM(函数式接口)转换,减少模板类
对于函数式接口,可以通过 lambda 表达式实现 SAM 转换,从而使代码更简洁、更有可读性。
使用 lambda 表达式可以替代手动创建实现函数式接口的类。 通过 SAM 转换, Kotlin 可以将其签名与接口的单个抽象方法的签名匹配的任何 lambda 表达式转换为实现该接口的类的实例。
fun interface IntPredicate {
fun accept(i: Int): Boolean
}
不使用SAM转换是的使用:
// 匿名实现接口
val isEven = object : IntPredicate {
override fun accept(i: Int): Boolean {
return i % 2 == 0
}
}
使用SAM转换:
// 通过 lambda 表达式创建一个实例
val isEven = IntPredicate {i -> i % 2 == 0 }
//使用默认幕后字段`it`
val isEven = IntPredicate { it % 2 == 0 }
扩展
Kotlin
能够扩展一个类的新功能而无需继承该类或者使用像装饰者这样的设计模式。 这通过叫做 扩展 的特殊声明完成。 例如,你可以为一个你不能修改的、来自第三方库中的类编写一个新的函数。 这个新增的函数就像那个原始类本来就有的函数一样,可以用普通的方法调用。 这种机制称为 扩展函数 。此外,也有 扩展属性 , 允许你为一个已经存在的类添加新的属性。
扩展函数
java的装饰者模式:
public static void swap(List<Integer> receiver,int index1, int index2) {
Integer tmp = receiver.get(index1);
receiver.set(index1, receiver.get(index2));
receiver.set(index2, tmp);
}
//使用
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
swap(list, 1, 2);
kotlin扩展函数:
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // “this”对应该列表
this[index1] = this[index2]
this[index2] = tmp
}
//使用
val list = mutableListOf(1, 2, 3)
list.swap(0, 2) // “swap()”内部的“this”会保存“list”的值
扩展是静态解析的,它并不会真正的变更接受者的类内容,只是形式上更符合设计模式
“静态解析”的理解:相对于运行时解析,代码在被编译成字节码时就明确了定义逻辑,相反的例子是泛型
open class Shape
class Rectangle: Shape()
fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"
fun printClassName(s: Shape) {
println(s.getName())
}
printClassName(Rectangle())
//打印“Shape”
扩展函数优先级低于接受者的成员函数
扩展函数与成员函数定义一样,优先调用成员函数
class Example {
fun printFunctionType() { println("Class method") }
}
fun Example.printFunctionType() { println("Extension function") }
Example().printFunctionType()
//打印“Class method”
扩展属性
形式上与扩展函数类似,但因为扩展行为实际上并没有改变接受者类结构,也就不存在幕后字段filed
,也就不存在初始化器
val <T> List<T>.lastIndex: Int
get() = size - 1
val House.number = 1 // 错误:扩展属性不能有初始化器
扩展的作用域
- 在类内部,作用域只限于类内部
- 在包内(Kotlin允许不在类内部的函数存在),符合可见性修饰符的范围定义
泛型
密封类
委托
委托类
两个类同时实现一个接口(/抽象类),class A的实现无法满足当前场景,可以将自己的任务委托给class B执行
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
class Derived(b: Base) : Base by b
fun main() {
val b = BaseImpl(10)
Derived(b).print()
}
委托属性
属性自有的get()/set()
函数无法很好的满足当前场景,我们可以通过一些标准化封装的委托类,对这些属性进行委托覆盖操作
class Example {
var p: String by Delegate()
}
委托类的实现,不需要实现任何结构,但要复写命名约束函数getValue
,setValue
import kotlin.reflect.KProperty
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
属性委托的编译前后对比
class C {
var prop: Type by MyDelegate()
}
这段是由编译器生成的相应代码:
class C {
private val prop$delegate = MyDelegate()
var prop: Type
get() = prop$delegate.getValue(this, this::prop)
set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}
内置标准委托类
延迟初始化属性
在第一次属性调用时进行初始化
val lazyValue: String by lazy {
println("computed!")
//return "Hello"
"BlackShark"
}
可通过重载函数by lazy(LazyThreadSafetyMode) { ... }
指定多线程调度策略
延迟属性同样适用于局部委托
fun example(computeFoo: () -> Boolean) {
val memoizedFoo by lazy(computeFoo)
println(memoizedFoo)
println(memoizedFoo)
println(memoizedFoo)
}
//调用
example {
val ts = System.currentTimeMillis()
println(ts)
return@example ts % 2 == 1L
}
//输出,ts只会打印一次
1608716042783
true
true
true
可观察属性
属性变更后,进行处理
var p: String by Delegates.observable("NB") { property, oldValue, newValue ->
println("filed:${property.name},changed value from $oldValue to $newValue")
}
属性变更前,进行处理,并返回Boolean
值决定是否接受该变化
var p: String by Delegates.vetoable("NB") { property, oldValue, newValue ->
println("filed:${property.name},changed value from $oldValue to $newValue")
return@vetoable true
}
一个属性委托给另一个属性(进行get/set
操作)@Since Kotlin1.4
典型的应用场景是一个属性已过时,此时将旧的属性标记过时,并委托为新的属性
使用限定符::
class MyClass {
var newName: Int = 0
@Deprecated("Use 'newName' instead", ReplaceWith("newName"))
var oldName: Int by this::newName
}
多属性存储在map映射中
map中的key值自动绑定到属性名,减少DTO型的模板类
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
var
属性需要使用MutableMap
class MutableUser(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
高阶函数
高阶函数是将函数用作参数或返回值的函数。
普通示例,参数 combine
具有函数类型 (R, T) -> R
fun <T, R> Collection<T>.fold(
initial: R,
combine: (acc: R, nextElement: T) -> R
): R {
var accumulator: R = initial
for (element: T in this) {
//accumulator = combine.invoke(accumulator, element)
accumulator = combine(accumulator, element)
}
return accumulator
}
函数类型
- 标准类型:
(Int, Int) -> Int
- 可控函数类型:
((Int, Int) -> Int)?
- 扩展函数式函数类型:
Int.(Int) -> Int
- 带入参高阶函数类型的函数类型:
((Int) -> (Int)) -> Unit
- 带返回高阶函数类型的函数类型:
(Int) -> ((Int) -> Unit)
- 箭头
->
遵循右结合优先级:(Int) -> (Int) -> Unit
等价(Int) -> ((Int) -> Unit)
可以通过别名关键字typealias
给较长的高阶函数类型简化
typealias ClickHandler = (Button, ClickEvent) -> Unit
函数类型实例化
- lambda 表达式:
{ a, b -> a + b }
, - 匿名函数:
fun(s: String): Int { return s.toIntOrNull() ?: 0 }
单个方法的接口与函数式类型在实现效果上等价
interface Foo {
call(name: String, age: Int)
}
//以下三个表达式使用上一样
val foo1 = object:Foo{
call(name: String, age: Int) {
...
}
}
val foo1 = object:Foo{name,age -> ...}
val foo2 = {name,age -> ... }
Lambda
表达式的使用
当方法的最后一个参数是函数类型,则该函数可以使用Lambda
表达式代替
fun foo(version:Int, block: (Info) -> Unit ) {
...
}
//调用
fun foo(1, {info ->
println(info.version)
})
//调用
fun foo(1) { println(it.version) }
当方法的最后一个参数是单函数的接口,则该函数可以使用Lambda
表达式代替
interface CallBack {
call(name: String, age: Int)
}
fun foo(version:Int, cb: CallBack) {
...
}
//调用
fun foo(1) {name,age -> println("name:$name,age:$age") }
内联函数
高阶函数每个函数类型都是一个闭包,本质上会在每次编译时生成一个匿名内部类,会有性能损失,因此诞生了内联函数通过关键字
inline
修饰的函数,原本高阶闭包函数可以在编译阶段被优化掉
lock本身是一个闭包,存在内存开销
fun <T> lock(lock: Lock, body: () -> T): T {
l.lock()
try {
body.invoke()
}
finally {
l.unlock()
}
}
fun foo() {
...
lock(l) { foo() }
...
}
我们可以通过内联,消除这个闭包
inline fun <T> lock(lock: Lock, body: () -> T): T { ... }
fun foo() {
...
lock(l) { foo() }
...
}
//编译后的效果等价于
fun foo() {
...
l.lock()
try {
foo()
}
finally {
l.unlock()
}
...
}
内联函数的特点和局限
内联函数无法访问所在类/对象的私有成员
val sGson by lazy { GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").create()!! }
//如果sGson为private,则编译失败
inline fun <reified T> T?.toJson(): String? =
if (this == null) null else sGson.toJson(this)
内联函数的lambda
中return
是非局部返回,需要@<函数名>的别名返回规避这个影响
fun handsUp() {
println("1")
say {
println("3")
//因为say的高阶函数是inline,这里的return是非局部返回,直接返回了handsUp函数
return
//使用函数名的返回别名,规避这种影响
//return@say
}
println("4")
}
inline fun say(block:() -> Unit) {
println("2")
block.invoke()
}
//输出
1
2
3
如果不希望存在非局部返回的特性,避免带来一些极难察觉的问题,可以使用关键字crossinline
修饰
inline fun say(crossinline block:() -> Unit) {
println("2")
block.invoke()
}
fun handsUp() {
println("1")
say {
println("3")
// return 编译错误,crossinline修饰的高阶函数不允许非局部返回
return@say
}
println("4")
}
如果不希望某个高阶函数被内联,可以使用关键字noinline
修饰
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { …… }
具体化的类型参数
我们要使用一个泛型的类型作为参数,参与执行,标准做法
fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
var p = parent
while (p != null && !clazz.isInstance(p)) {
p = p.parent
}
@Suppress("UNCHECKED_CAST")
return p as T?
}
//调用
treeNode.findParentOfType(MyTreeNode::class.java)
kotlin
允许我们在内联函数中使用具体化的类型参数,我们可以想访问参数一样,访问这个泛型类型,减少一次类型传参
inline fun <reified T> TreeNode.findParentOfType(): T? {
var p = parent
while (p != null && p !is T) {
p = p.parent
}
return p as T?
}
//调用
treeNode.findParentOfType<MyTreeNode>()
网友评论