Kotlin的基本数据类型最后都会转化成JAVA中对应的基本数据类型
类型 bit/可选值
Boolean true/false
Double 64
Float 32
Long 64
Int 32
Short 32
Byte 8
Char 16
val
是常量,var
是变量
//val相当于JAVA的final,是常量,var是变量
val A_LONG: Long = 10000L
val B_LONG: Long = 0xffff
val C_BYTE: Byte = 0b01111111
var dFloat: Float = 12.3F
//可以给数字之间加下划线以提高可读性
var fInt: Int = 1_000_000
//自动类型推断,类型可以省略
var fChar = '\u000f'
//基本数据类型之间不能自动转换
var a: Int = 1
var b: Long = a.toLong()
//NaN(Not a number)之间总是不相等的
var d1:Double = Double.NaN
var d2:Double = Double.NaN
(d1==d2)//false print
val
修饰的常量只是在语义上不可修改,但编译成字节码的时候仍然是使用其引用,不会宏替换,如果想使用宏替换,需要定义编译期常量,编译期常量用const val
修饰,相当于JAVA中用final修饰的变量,会被编译优化
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
只能用于Int和Long
shl – 相当于Java的"<<"
shr – 相当于Java的">>"
ushr – 相当于Java的">>>"
and - 与
or - 或
xor - 异或
inv - 取反
如
val x = (1 shl 2) and 0x000FF000
Char类型及String可以用转义字符
\t 制表符
\b 光标后退一个字符
\n 回车
\r 光标回到行首
\' 单引号
\" 双引号
\\ 反斜杠
\$ 美元符号,Kotlin 支持美元符号开头的字符串模板
//字符串模板用$name,或${name},对于复杂的表达式必须带大括号(如调用对象的属性时)
val name = "小明"
("Hello ${name}")
println
//长字符串用三个引号包裹,长字符串无法使用转义字符,但可以用字符串模板
val blobText = """你好
\tHello
\${name}
"""
(blobText) println
==
与===
)//==比较字符串内容,相当于JAVA的的equals,===比较是否为同一个对象,这同样适用于基本数据类型(同理也有!=和!==)
val aString = "Hello World!"
val hello = "Hello"
val bString = hello + " World!"
(aString == bString)//true
println(aString === bString)//false
println
//基本数据类型会根据需要转换成JAVA的装箱类,也会出现值相同,但对象不相同的情况
//可空的基本数据类型是会自动装箱的,因为JAVA中的int等基本数据类型是不能为null的,而包装类Integer是可以为null的
val i1: Int = 128
val i2: Int? = i1//Integer.valueOf(i1)
val i3: Int? = i1//Integer.valueOf(i1)
(i2 == i3)//true
println(i2 === i3)//false println
如果想像JAVA一样定义一个String,给其赋值为null,在Kotlin里面是不行的,如果一定要这样做,需要使用可空类型(类名?
)
var str: String? = null
但是使用的时候就要加?
或!!
(非空断言)
//尝试去掉length,如果str为null,则返回null
(str?.length)//null
println
//确定str不为null,如果str为null,则抛异常
(str!!.length)//报kotlin.KotlinNullPointerException println
如果觉得每次都这样写很烦,可以先判断不为空,这时编译器可以智能识别
if(str != null){
//因为判过是否为空,则调用时不需加?或!!
(str.length)
println}
//同样,在&&或者||右边也是可以智能识别的
if(str != null && str.length > 5){
//..
}
判空的简化写法(Elvis运算符)
var str: String? = null
(str?: "str is null")//如果str为null,则打印str is null
println
//如果不为空则打印长度,如果为空则打印“empty”
(str?.length ?: "empty") println
这种写法在JAVA中实现就需要用到Optional类了
static int strLength(Optional<String> s) {
//如果s里面的内容是null,那就先给s的内容设置一个默认值,然后再调用其length方法
return s.orElse("").length();
}
public static void main(String[] args) {
//JAVA的Optional有点像Kotlin的安全空类型,如果想设置null,要调用ofNullable,就像Kotlin中要声明为可空类型一样
System.out.println( strLength(Optional.of("abc")) );
System.out.println( strLength(Optional.ofNullable(null)) );
}
类型转换使用as
关键字,相当于JAVA的小括号
//会有ClassCastException的风险
val child: Child = parent as Child
//智能类型转换;如果转换失败,则返回null
val child: Child = parent as? Child
类型判断使用is
关键字,可以判断对象是否为某个类或该类的子类
open class Person
class Student: Person()
fun main(args: Array<String>) {
var person = Person()
(person is Any)//true
println(person is Person)//true
println(person is Student)//false
println}
0..100 //相当于[0, 100],对应的类型是IntRange
0 until 100 //相当于[0, 100),对应的类型还是IntRange,其实就是[0,99]
区间可用于for循环,in
用在for中可以遍历区间内的元素,用在条件判断中可以判断一个数是否在该区间内(in
、!in
)
for(i in 1..20){
(i)
println}
//in关键字的另一个用法
if(2 in 1..10){
//...
}
还可以使用downTo
(倒序)和step
(步长)(step无法使用浮点型)
for(i in 20 downTo 1 step 4){
(i)//20 16 12 8 4
println}
基本数据类型都有xxxArrayOf的函数,得到一个基本数据类型的数组xxxArray,其他类型用arrayOf
,得到Array<类型>
val ints: IntArray = IntArrayOf(1,2,3,5)
var charArray: CharArray = charArrayOf('a', 'b', 'c', 'd', 'e')
var stringArray: Array<String> = arrayOf("小红", "小明", "小花", "小强", "小王")
(charArray.joinToString(""))//abcde
println(stringArray.slice(1..3 step 2))//[小明, 小强] println
也可以直接调用Array的构造函数
//创建一个长度为5,内容为下标的平方的数组
val square: Array<Int> = Array(5, { i -> i * i })
.forEach(::println)//输出0 1 4 9 16 square
包的概念和JAVA一样,包的声明也应该在最前面
package com.myapp
导入包中的某个类的时候可以给这个类取别名
import com.myapp.MyClass as A//MyClass这个类别名为A
也可以在代码中给类取别名
typealias B = Boolean
var b: B = true
if与JAVA中的if一样,when相当于JAVA中的switch,但JAVA的switch只支持几个基本数据类型及String,而Kotlin的when几乎支持所有的类型
if(条件1){
//...
}else if(条件2){
//...
}else{
//...
}
when(条件){
1 -> 执行语句1
条件值2 -> 执行语句2
条件值3, 条件值4 -> 执行语句3
条件值else -> 执行语句4
}
如
fun main(args: Array<String>) {
var view = RecyclerView()
(view)
myPrint}
fun myPrint(view : View){
when (view) {
is TextView -> println("TextView")
is RecyclerView -> println("RecyclerView")
is SearchView -> println("SearchView")
//甚至可以调函数
() -> println("MyView")
getMyViewis View -> println("basic View")
else -> println("View type not supported")
}
}
open class View
class TextView:View()
class RecyclerView:View()
class SearchView:View()
class MyView:View()
fun getMyView() = MyView()
除此以外,if和when是有返回值的,当需要用到返回值时,要求表达式是完备的(有else
),通常用于成员变量(val
类型)初始化
class Grade(val grade: Int) {
val TYPE1: String = if(grade in 1..60){
"BAD"
}else if(grade in 61..80){
"GOOD"
}else{
"BEST"
}
val TYPE2: String = when(grade){
in 1..60 -> "BAD"
in 61..80 -> "GOOD"
else -> "BEST"
}
}
//when还可以作为返回值返回
fun transform(color: String): Int {
return when(color) {
"Red" -> 0
"Green" -> 1
"Blue" -> 2
else -> throw IllegalArgumentException("Invalid color param value")
}
}
在Kotlin中没有三元表达式,因为JAVA中的三元表达式完全可以用if或when替代
JAVA:
String text = x > 5 ? "x > 5" : "x <= 5";
Kotlin:
val text = if (x > 5)
"x > 5"
else "x <= 5"
循环语句没有返回值,for
循环使用in
遍历区间或集合
val items = listOf("apple", "banana", "kiwi")
for (i in 1..10)
(i)
println
for (item in items)
(item)
println
for (i in items.indices)
(items[i])
println
//实现了operator componentN后可以以这种方式遍历
for ((index, value) in items.withIndex())
("index=$index value=$value")
println
var lists = listOf(mapOf("name" to "小红", "age" to 14), mapOf("name" to "小明", "age" to 15))
for(map in lists){
for((key,value) in map){
("$key = $value ")
print}
()
println}
for可以遍历任何提供了iterator
的函数或对象
fun main(args: Array<String>) {
for (i in fibonacci()) {
if (i > 100) break
(i)
println}
}
fun fibonacci(): Iterable<Long> {
var first = 0L
var second = 1L
return Iterable {
object : LongIterator() {
override fun hasNext(): Boolean = true
override fun nextLong(): Long {
val result = second
+= first
second = second - first
first return result
}
}
}
}
while、do while和JAVA中一样
continue和break可以使用标签
@ for(i in 1..5) {
loopfor(j in 1..5) {
if(i==j && i>3) break@loop
("($i,$j) ")
print}
}
和if、when一样,try也是有返回值的,捕获异常用try、catch、finally,抛异常用throw,和JAVA是一样的
函数声明用fun
,函数参数后面用:返回值类型
fun add(x: Int, y: Int): Int{
return x + y
}
当函数体只有一行(单表达式函数)时可以写成
fun add(x: Int, y: Int): Int = x + y
//此时返回值类型可以自动推断,返回值的类型可以省略
fun add(x: Int, y: Int) = x + y
函数还可以返回一个函数或Lambda表达式(Kotlin支持闭包)
//返回匿名函数,此时返回值类型为(Int, Int) -> Int
fun getAdd1(): (Int, Int) -> Int {
return fun(x: Int, y: Int): Int{
return x + y
}
}
//函数体只有一行,可以简写成
fun getAdd2(): (Int, Int) -> Int = fun(x: Int, y: Int): Int{ return x + y }
//此时函数的返回值类型可以省略
fun getAdd3() = fun(x: Int, y: Int) = x + y
//返回Lambda表达式,返回值类型同上面一样
fun getAdd4(): (Int, Int) -> Int {
var add = { x: Int, y: Int -> x + y }
return add
}
//同理,函数体只有一行,可以简写成
fun getAdd5() = { x: Int, y: Int -> x + y }
//调用方式
(getAdd3()(4, 5))
println//或者
var add3 = getAdd3()
(4, 5) add3
函数如果没有返回值,则返回值类型为Unit
(用于返回函数时对函数类型的声明,普通函数不写返回值类型就行了)
fun getMethod(): (Int)->Unit{
return fun(i: Int){ println(i) }
}
fun method1(){
("Hello World")
println}
如果想表示一个函数永远返回null
,可以使用Nothing?
fun method2(): Nothing?{
()
printlnreturn null
}
除了具名参数外,函数还可以有默认参数,变长参数(vararg
),只要前面省略了默认参数,则后面的参数必须使用具名参数写法,变长参数不能直接传数组,要把数组的元素放到变长参数的位置,则要用*
(spread
operator)
fun method1(num1: Int = 0,vararg str: String, num2: Double){
//...
}
fun main(args: Array<String>) {
var array = arrayOf("aa", "bb", "cc")
(str = *array, num2 = 2.3)
method1}
由于JAVA不支持默认参数,如果想在JAVA中使用Kotlin定义的默认参数函数,可以加@JvmOverloads
@JvmOverloads
fun method2(num1: Int = 0, vararg str: String, num2: Double) {
(num1)
println.forEach { println(it) }
str(num2)
println}
此时会生成两个不同的重裁方法
@JvmOverloads
public static final void method2(int num1, @NotNull String[] str, double num2) {
.checkParameterIsNotNull(str, "str");
Intrinsics}
@JvmOverloads
public static void method2$default(int var0, String[] var1, double var2, int var4, Object var5) {
if ((var4 & 1) != 0) {
= 0;
var0 }
method2(var0, var1, var2);
}
@JvmOverloads
public static final void method2(@NotNull String[] str, double num2) {
default(0, str, num2, 1, (Object)null);
method2$}
Lambda表达式可以认为是匿名函数的简化写法
当函数最后一个参数是Lambda表达式时,Lambda表达式可以移出括号外
如果函数只有一个参数且该参数是Lambda表达式时,则表示函数参数列表的小括号可以省略
Lambda只有一个参数时,参数名字默认为it,此时可以省略参数列表
Lambda表达式支持自动类型推断,如函数接收Lambda表达式参数时指定了Lambda表达式的类型,从而Lambda表达式的参数类型也确定了,这时使用该函数时Lambda表达式的参数类型可以省略
Lambda表达式的返回值为最后一行表达式的值,如果最后一行没有值,则返回值类型为Unit
Lambda表达式可以用下划线_
表示没有用到的参数
var array: Array<String> = arrayOf("1","2","3")
//array.forEach({ println(it) })
//array.forEach(){ println(it) }
//和上面的写法完全等价
.forEach { println(it) }
array
val map = mapOf("a" to "1","b" to "2","c" to "3")
.forEach{
map, value -> println("$value")
_ }
Kotlin调用JAVA时,Kotlin支持JAVA的FunctionalInterface
写法,即如果一个JAVA中的接口是FunctionalInterface
时,创建该接口的匿名内部类时可以用Lambda表达式替代,但Kotlin自身没有FunctionalInterface
的写法,如果要那样写,就要为使用接口类型参数和使用Lambda类型参数定义两个重裁的方法
一般情况下,把Lambda表达式作为参数传入时,Lambda表达式中的this指的是Lambda表达式所在的类,但像apply
,其Lambda参数类型定义为T.() -> Unit
(指定接收者的Lambda,在后面扩展Lambda
一节中会详细讲到),此时Lambda表达式中的this指调用者,而不再指向所在类了
//这里Lambda的类型为T.() -> Unit,这时Lambda中的this指向调用者本身,也就是T
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
{
contract (block, InvocationKind.EXACTLY_ONCE)
callsInPlace}
()
blockreturn this
}
//其他把Lambda作为参数的函数中Lambda类型是(T) -> Unit,这时Lambda中this指Lambda所在类,如果是包级函数,则不能使用this
public inline fun <T> Array<out T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
包级函数可用::方法名
,如::println
,类中的函数可用类名::方法名
或实例::方法名
来获取方法引用
Lambda表达式中不允许直接使用return
返回,但仍可以通过标签返回
//下面代码编译不通过
/*
fun func1(): Int{
var innerfunc = {x: Int ->
if(x == 0)
return 0//不能在Lambda中直接使用return返回
println(x)
1
}
println("func1 -- end")
}
*/
//下面代码可以通过编译
fun main(args: Array<String>) {
()
func1}
fun func1(){
var innerfunc: (Int) -> Int = tag@{ x: Int ->
if(x == 0){
("x is zero!!")
println@tag 0//通过标签返回是允许的,注意带返回值的标签返回的写法
return}
(x)
println//Lambda表达式最后一行是返回值,这里相当于return@tag 1(这里也不能直接用return)
1
}
(0)
innerfunc("func1 -- end")
println}
输出
x is zero!!
func1 -- end
但是如果是函数(无论匿名还是具名函数)就可以使用空白的return
返回,此时返回的是内部函数,外部函数并不会返回
//下面代码可以通过编译
fun func1(){
var innerfunc = fun(x: Int){
if(x == 0) {
("x is 0!!")
printlnreturn
}
(x)
println}
(0)
innerfunc("func1 -- end")
println}
输出
x is 0!!
func1 -- end
当接收该Lambda表达式的函数是内联(inline
)的时候,Lambda表达式中可以直接使用return
返回,此时return
会把定义该Lambda表达式的函数返回(非局部返回
),而不是结束当前Lambda表达式
fun main(args: Array<String>) {
(func2())
println("main: -- end")
println}
fun func2(): String {
{ x: Int ->
func1 if (x == 0) {
("func2: x is 0!!")
println//这里return是把func2函数返回,所以返回值类型和func2一致
return "x is zero!!"
}
("func2: x is $x")
println//Lambda表达式最后一行是返回值,这里会返回到调用该Lambda表达式的地方,也就是func1,而且不会把func1返回,所以返回值类型只要和函数参数要求一样就行了
x}
("func2: -- end")
printlnreturn "FUNC2"
}
inline fun func1(myFunc: (Int) -> Int) {
var ret: Int = myFunc(0)
("func1: resilt is $ret")
println("func1: -- end")
println}
此时输出
func2: x is 0!!
x is zero!!
main: -- end
如果把myFunc(0)改成myFunc(1),则输出
func2: x is 1
func1: resilt is 1
func1: -- end
func2: -- end
FUNC2
main: -- end
比如forEach就是内联的
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
所以可以使用return
返回
var items = arrayListOf<Int>(1, 4, 0, 9, 7)
fun main(args: Array<String>) {
.forEach{
itemsif(it == 0) return //返回到定义该Lambda表达式的地方,也就是结束当前Lambda表达式
(it)//输出1 4
println}
//可以使用隐式标签,隐式标签为使用该Lambda表达式的函数名
.forEach {
itemsif (it == 0) return@forEach//跳过此次遍历,有点像循环控制中的continue
(it)//输出1 4 9 7
println}
}
由于inline
会导致函数中所有参数都被内联,如果希望某些参数不被内联,可以使用noinline
修饰这些参数
fun main(args: Array<String>) {
{ x: Int ->
func1 if(x==0)
@func1 0//此时只能通过标签返回,隐式标签返回会返回到使用该Lambda表达式的地方,所以后面的打印还能继续执行
return+ 10
x }
}
inline fun func1(noinline myFunc: (Int) -> Int) {
(myFunc(0))
println("func1 -- end")
println}
输出
0
func1 -- end
在inline
函数中使用crossinline
修饰的参数会被禁止非局部返回
inline fun func1(noinline myFunc: (Int) -> Int) {
(myFunc(0))//此时myFunc也不能直接使用return,只能通过标签的形式返回
println}
内联函数还可以使用reified
关键字来具体化类型参数
fun main(args: Array<String>) {
(isTypeof(Person(), Person::class.java))
println(isTypeof<Person>(Person()))
println
}
class Person
//普通函数由于无法知道T是什么具体类型。不能使用is判断
fun <T> isTypeof(obj: Any, clazz: Class<T>): Boolean {
return clazz.isInstance(obj)
}
//使用了reified后,T的类型就是确定了的,可以使用is判断
inline fun <reified T> isTypeof(obj: Any): Boolean {
return obj is T
}
Kotlin中可以重裁已有的运算符,重裁对应的方法即可
运算符 对应的函数名
+a unaryPlus
-a unaryMinus
!a not
a++ inc
a-- des
a + b plus
a - b minus
a * b times
a / b div
a % b mod
a..b rangeTo
a in b contains
a[i] get(i)
a[i] = b set(i, b)
a(i) invoke(i)
a(i, j) invoke(i, j)
a += b plusAssign
a -= b minusAssign
a *= b timesAssign
a /= b divAssign
a %= b modAssign
>、<、>=、<= compareTo
a == b a?.equals(b)?:b.identityEquals(null)
如
fun main(args: Array<String>) {
(Complex(1.0, 2.5) - Complex(2.0, 1.5))
print}
class Complex(var real: Double, var imaginary: Double) {
operator fun plus(c: Complex): Complex {
return Complex(c.real + real, c.imaginary + imaginary)
}
operator fun minus(c: Complex): Complex {
return Complex(real - c.real, imaginary - c.imaginary)
}
override fun toString(): String {
return "$real + $imaginary * i"
}
}
像in
、by
这样的叫中缀表达式,它们通过infix来定义
fun main(args: Array<String>) {
var partent = Partent()
var stu = Student()
(stu belongTo partent)
println}
open class Partent
class Student() : Partent(){
infix fun belongTo(s: Student): Boolean{
return s is Partent
}
infix fun belongTo(s: Partent): Boolean{
return s is Partent
}
}
在Kotlin中,对于每种函数都有对应的接口声明,如:没有参数的函数对应Function0
,一个参数的函数对应Function1
…
public interface Function0<out R> : Function<R> {
public operator fun invoke(): R
}
public interface Function1<in P1, out R> : Function<R> {
public operator fun invoke(p1: P1): R
}//...
public interface Function22<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, in P19, in P20, in P21, in P22, out R> : Function<R> {
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21, p22: P22): R
}
像(index, value) in list.withIndex()
(解构声明)是通过重写componentN
函数实现的,data
class默认重写了这两个函数,对于非data class,可以自己重写
class User(val name: String, val addr: String) {
//componentN是操作符,重载它,必须添加operator修饰符
operator fun component1(): String {
return name
}
operator fun component2(): String {
return addr
}
}
fun main(args: Array<String>) {
val (name, addr) = User("小明","地址")
}
其实是运算符重裁的一种,调用实例()
时,括号内有几个参数就会调有几个参数的invoke
方法(参数类型要匹配)
fun main(args: Array<String>) {
val p = Person("aaa")
()
p("bbb")
p("aaa", "bbb", "ccc", "ddd")
p}
class Person(val name: String) {
operator fun invoke() {
("my name is $name")
println}
operator fun invoke(str: String) {
("my name is $str")
println}
operator fun invoke(str1: String, str2: String, str3: String, str4: String) {
("$str1 $str2 $str3 $str4")
println}
}
输出
my name is aaa
my name is bbb
aaa bbb ccc ddd
前面提到过,函数可以返回函数或Lambda表达式,同样,Lambda表达式也可以作为参数传入函数,也就可以实现数学意义上f(g(x))
的写法了
如,集合中的filter函数
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
for (element in this)
if (predicate(element))
.add(element)
destinationreturn destination
}
实现函数复合
fun main(args: Array<String>) {
(compose(4, { x -> x + 5 }, { y -> y * 2 }))
println}
fun compose(num: Int, block1: (Int) -> Int, block2: (Int) -> Int): Int {
return block2(block1(num))//相当于 (4 + 5) * 2,输出18
}
或者
fun main(args: Array<String>) {
var fg = fgx(::fx, ::gx)
(fg(4))//相当于 (4 + 5) * 2,输出18
println}
fun fx(num: Int) = num * 2
fun gx(num: Int) = num + 5
fun fgx(fx: (Int) -> Int, gx: (Int) -> Int) = { num: Int -> fx(gx(num)) }
也可以将一个函数的返回值作为参数传入,如
var add5 = { i: Int -> i + 5 }
var multiplyBy2 = { i: Int -> i * 2 }
(multiplyBy2(add5(4)))//相当于 (4 + 5) * 2,输出18 println
这时,如果想对任意函数实现复合,可以给FunctionN添加拓展方法,为了简化书写,我们一般还把复合函数定义成中缀表达式
infix fun <P1, P2, R> Function1<P1, P2>.andThen(function: Function1<P2, R>): Function1<P1, R> {
return fun(p1: P1): R {
return function.invoke(this.invoke(p1))
}
}
infix fun <P1, P2, R> Function1<P2, R>.compose(function: Function1<P1, P2>): Function1<P1, R> {
return fun(p1: P1): R {
return this.invoke(function.invoke(p1))
}
}
使用
var add5AndMultiplyBy2 = add5 andThen multiplyBy2
var add5ComposeMultiplyBy2 = add5 compose multiplyBy2
(add5AndMultiplyBy2(8))//相当于 (8 + 5) * 2,输出26
println(add5ComposeMultiplyBy2(8))//相当于 8 * 2 + 5,输出21 println
函数复合的具体应用,如forEach
、map
、flatMap
、reduce
、fold
、filter
、takeWhile
、let
、apply
、with
、use
等函数式编程用到的函数
fun main(args: Array<String>) {
var a = arrayListOf(1, 7, 5, 2, 0, 3, 9)
//forEach和map的区别在于,forEach没有返回值,而map会返回一个新的集合
.forEach { print("$it ") }//1 7 5 2 0 3 9
a()
println
//map后会返回一个新的集合,而不会对a做任何修改
.map { it + 10 }.forEach { print("$it ") }//11 17 15 12 10 13 19
a()
println
//把多个集合展开成一个
var b = arrayListOf(1..3, 2..4, 3..5)
.flatMap { it.map { "No.$it" } }.forEach { print("$it ") }//No.1 No.2 No.3 No.2 No.3 No.4 No.3 No.4 No.5
b()
println
//reduce适用于累加、累乘等情况
(a.reduce { ret, i -> ret + i })//27
println
//fold就是有初始值的reduce
(a.fold(5) { ret, i -> ret + i })//32
println
//筛选
.filter { it > 4 }.forEach { print("$it ") }//7 5 9
a()
println
//取出符合条件的,直到有不符合就不再往后取
.takeWhile { it < 7 }.forEach { print("$it ") }//1
a()
println
//let中的it是a(也就是ArrayList),对it做更改就是对a做更改,let可以有任意返回值
.let { it.add(10) }//ArrayList的add方法返回boolean,所以这里返回值类型就是Boolean
a.forEach { print("$it ") }//1 7 5 2 0 3 9 10
a()
println
//apply中的this是a(也就是ArrayList),在里面对a的更改是有效的,apply返回a本身
.apply { add(11) }.forEach { print("$it ") }//1 7 5 2 0 3 9 10 11
a()
println.forEach { print("$it ") }//1 7 5 2 0 3 9 10 11
a()
println
//with的作用和apply一样,里面的this是a,也可以在里面对a做更改,但它可以有任意返回值
(with(a){ removeAt(8) })//11,removeAt返回被删除的元素
println.forEach { print("$it ") }//1 7 5 2 0 3 9 10
a()
println
//use用于io操作,对于实现了AutoCloseable接口的io流,在use中使用,可以不写close
(File("test.iml")).use {
FileReader//Kotlin给JAVA的io添加了很多方法,readLines就是其中一个
(it.readLines())
println}
}
把多参数的函数转化成一系列单参数函数就是柯里化
//原函数
fun add(x: Int, y: Int) = x + y
//柯里化后
fun add(x: Int): (Int) -> Int {
return fun(y: Int): Int {
return x + y
}
}
//可以简写成
fun add(x: Int) = fun(y: Int) = x + y
//调用
(4)(5)//相当于4 + 5,输出9 add
同样,我们也可以通过给FunctionN添加拓展方法来实现对任意函数的柯里化
fun main(args: Array<String>) {
val ret = ::add.curried()(2)(3)(4)//通过函数引用调用Function3的curried方法
(ret)
println}
fun add(x: Int, y: Int, z: Int) = x + y + z
fun <P1, P2, P3, R> Function3<P1, P2, P3, R>.curried() = fun(p1: P1) = fun(p2: P2) = fun(p3: P3) = this(p1, p2, p3)//这里的this指Function3这个接口
如果函数有固定的几个参数,可以使用默认参数简化书写,也可以使用偏函数(使用柯里化后的函数,固定前几个参数,返回的函数就是偏函数)
var add2And3 = ::add.curried()(2)(3)
(add2And3(4))
println(add2And3(5))
println(add2And3(6)) println
Kotlin支持通过tailrec
关键字给尾递函数归优化,优化后会在编译时把尾递归函数展开成while循环,这样就可以避免内存溢出
尾递归是指递归调用在函数的最后一行且没有其他运算,这时函数在递归时就不需要保存上一次的状态
如,求阶乘的两种写法
//非尾递归,因为有乘运算
//非尾递归使用tailrec修饰无效
fun fact(i: Long): Long{
if(i == 1L) return 1L
return i * fact(i - 1L)
}
//尾递归,在最后一行递归且无其他运算
tailrec fun fact(i: Long, ret: Long = 1L): Long{
if(i == 1L) return ret
return fact(i - 1L, ret * i)
}
类名后面的是默认构造器,其中var或val修饰的会变成成员变量,没有被var或val修饰则只能在构造器或init
块中使用,多个构造器用constructor
,此时在主构造器里用var或val声明过的变量无需再用var或val修饰,其他构造器必须调用默认构造器
class Student constructor(var name: String, var age: Int, sex: Char){
{
init //相当于构造函数体,无论调哪个构造器,都会执行init块
}
//其他构造器必须调用默认构造器,其实这里可以用默认参数实现,后面会讲
constructor(name:String) : this(name,10,'M')
}
//如果构造器没有参数,或类没有自定义的方法,只是定义一个类,则主构造器或大括号可以省略,通常用于data class
class Myclass
//如果主构造函数没有注解或可见性说明,则 constructor 关键字是可以省略的
class Student2 (var name: String, var age: Int, sex: Char)
创建对象不需要写new
var stu: Student = Student("小明",14,'M')
接口和JDK8中是一致的,接口方法可以有默认实现,如果接口和抽象类有同名、同参数的且已实现的方法,子类可以通过super<父类 父接口名>的方式去指定调用哪个父类/父接口的方法,而此时子类必须重写重名的方法,若两个重名方法参数一致但返回值类型不一致,则子类无法重写(返回值不一致不能构成重裁),也就无法同时 继承/实现 方法冲突的两个 类/接口 了
abstract class A{
//抽象类中的非抽象方法是final的,子类要重写需open
open fun func1(){
("A->func1")
println}
}
interface B{
val property: Int //abstract
fun func1(){
("B->func1")
println}
fun func2()
}
//继承的类要调其构造函数,而实现的接口不用,类与多个接口之间用逗号隔开
class C: A(), B{
override val property: Int = 0
override fun func1() {
("C->func1")
println}
override fun func2() {
("C->func2")
printlnsuper<A>.func1()
super<B>.func1()
()
func1}
}
成员变量可以通过get、set方法实现访问控制,field
只有在get和set中才能访问到
class Student {
var name: String? = null
set(value) {
("set name")
println= value
field }
get {
("get name")
printlnreturn field
}
var age: Int = 0
set(value) {
("set age")
println= value
field }
get {
("get age")
printlnreturn field
}
//如果只希望限制属性的读写属性,不需要做其他处理,可以直接这样写
var setterVisibility: String = "abc"
private set
}
成员变量可以通过by
关键字使用delegate(代理),如懒加载用到的lazy
(用于val
)
对应lazy
,var
也可以使用lateinit
来延迟加载,但lateinit
不是delegate
class Student{
//一般在定义变量时就要求初始化,使用lateinit可以延迟初始化
lateinit var mName: String
//只有当使用到这个成员变量时,才会在内存中初始化
val mSex: Char by lazy{
'M'
}
fun setName(name: String){
= name
mName }
}
kotlin.properties.Delegates
提供了很多有用的代理,而我们也可以自定义delegate,属性代理不需要任何接口的实现,但必须要提供getValue
和setValue
两个方法(对于var
,要重写getValue
和setValue
;对于val
,只需重写getValue
)
import kotlin.reflect.KProperty
fun main(args: Array<String>) {
var stu = Student()
(stu.name)
println.name = "小明"
stu(stu.name)
println}
class Student {
var name: String by MyDelegate()
}
class MyDelegate{
var mName: String? = null
operator fun getValue(thisRef: Student, property: KProperty<*>): String {
return mName?:"无名氏"
}
operator fun setValue(thisRef: Student, property: KProperty<*>, value: String) {
= value
mName }
}
同样,类也可以有代理
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() {
(x)
println}
}
class Derived(b: Base) : Base by b
fun main(args: Array<String>) {
val b = BaseImpl(10)
(b).print()//Derived的print由代理对象BaseImpl提供
Derived}
所有类都继承自Any
,相当于JAVA的Object
要使类可继承,需要添加open
关键字(否则默认是final
的),同样,要使属性、方法可重写,也需要添加open
关键字(接口、接口方法、抽象类、抽象类的抽象方法默认是open
的),而且属性、方法的重写必须添加override关键字
继承使用默认构造器:父类构造器
的形式
open class Person(open var name: String, open var age: Int){
open fun speak(){
("speak")
println}
}
class Man(override var name: String, override var age: Int):Person(name, age){
override fun speak(){
("man speak")
println}
}
和JAVA不同,方法默认是public的
,要更改方法的可见性,和JAVA一样,可以使用private
、protected
、public
,除此以外,Kotlin还有internal
,可见范围是module内可见
class Person(var name: String,var age: Int){
fun speak(){}//默认为public
private fun walk(){}
protected fun eat(){}
internal fun sleep(){}
}
前面提到过,方法可以有具名参数、变长参数、默认参数,而方法重裁一般可以通过默认参数来实现,但由于JAVA不支持默认参数,要想在JAVA中使用Kotlin定义的通过默认参数实现方法重裁的函数,需添加@JvmOverloads
注解
class OverLoadTest {
@JvmOverLoads
fun func1(num: Int = 0, str: String="") = 1
}
在JAVA中调用
= new OverLoadTest();
OverLoadTest test .func1()
test.func1(1)
test.func1(1,"aaa") test
可以使用类名.方法名
的形式在类的外部给类添加函数
class User(var name:String){}
//扩展函数,在扩展函数里也可以使用this
fun User.print(){
("用户名 ${this.name}")
print}
fun main(arg:Array<String>){
var user = User("小明")
.print()
user}
扩展函数是动态解析的
open class A
class B : A()
fun A.foo() = "a"
fun B.foo() = "b"
fun printFoo(a: A) {
(a.foo())//扩展函数由发起函数调用的表达式的类型决定的,无论传入的a是A还是A的子类,都调用的是A的foo()
println}
fun main(args: Array<String>) {
(B())//a
printFoo}
如果扩展函数与类中的函数重名,则调类中的函数
class A {
fun foo() = println("member")
}
fun A.foo() = println("extension")
fun main(args: Array<String>) {
().foo()//打印"member"
A}
扩展函数中的this
指扩展的类,且扩展函数可以扩展可空类型,这时对空类型的检查在扩展函数中完成,调用时无需进行判空
fun Any?.toString(): String {
if (this == null) return "null"
// 在空检查之后,this被自动转为非空类型,因此 toString() 可以被解析到任何类的成员函数中
return toString()
}
当Lambda表达式作为参数传入函数中时,可以指定该Lambda表达式的接收者
//声明Lambda的接收者为StringBuilder,且Lambda的类型为()->Unit
fun kotlinDSL(block: StringBuilder.() -> Unit){
//block需要一个类型为StringBuilder的接收者,通过invoke来给它创建接收者
//如果没有创建接受者,Lambda表达式就不会执行
(StringBuilder("Kotlin"))
block}
class MyClass {
fun func() {
{
kotlinDSL (" DSL")
append//this就是block通过invoke创建的那个StringBuilder
(this)//stringBuilder.toString()
println//也可以通过隐式标签指定用哪个this
(this@kotlinDSL)
println(this@MyClass)
println}
}
}
fun main(args: Array<String>) {
().func()
MyClass}
输出
Kotlin DSL
当类的扩展方法与方法中的扩展Lambda的接收者一致时,不需要创建接收者,此时接收者就是扩展的类
class MyClass(var name: String)
fun MyClass.changeName(block: MyClass.() -> String): MyClass {
//这里的this指扩展的类(MyClass)
this.name = block()//执行Lambda表达式,获取结果
return this
}
fun main(args: Array<String>) {
var myClass = MyClass("小明")
.changeName { "小红" }
myClass(myClass.name)
println}
对于属性的拓展,无法在get、set中访问field
字段,一般来说拓展属性的意义不大
val <T> List<T>.lastIndex: Int
get() = size-1
扩展域:
package foo.bar
fun Baz.goo() { }
为了在除声明的包外使用这个扩展,我们需要在 import 时导入
package com.example,usage
import foo.bar.goo
//或者
/*import foo.bar.*
fun usage(baz: Baz) {
baz.goo()
}
*/
跟JAVA类似,有泛型类、泛型接口,泛型函数,带泛型的属性,也可以在扩展函数、扩展属性、包级函数中使用泛型
interface List<T>
class StringList : List<String> // 具体类型实参
class MyList<T> : List<T> // 泛型类型形参
fun <T> List<T>.slice(incides:IntRange):List<T>
val <T> List<T>.penultimate: T
get() = this[size - 1]
也有泛型约束,上界使用<T: 类名>
,规定T必须是所给的类的子类,也可以有多个约束,使用where 泛型占位符:类1, 泛型占位符:类2, ...
,相当于JAVA的<泛型占位符 extends 类1 & 类2>
fun <T : List<T>> List<T>.sum1(){ }
//相当于JAVA中的<T extends Number & Appendable>)
fun <T> List<T>.sum2():T where T : Number, T: Appendable{ }
//如果不指定泛型,上界默认为Any?,如果不想类型可空,应用Any做上界
class MyClass<T: Any>
JAVA中有泛型擦除,而Kotlin中可以通过inline
函数实现类型实参,不被擦除(Kotlin称实化)
//因为泛型会被擦除,下面的代码无法通过编译
//fun <T> isA(value: Any) = value is T // 不能确定T
//使用inline后,会把每一个的函数调用换成实际的代码调用,Lambda也是一样,并结合 reified 标记类型参数,上面的 value is T 就可以通过编译了
inline fun <reified T> isA(value: Any) = value is T
泛型类在初始化时必须指定泛型,如果不想指定泛型,可以使用*
(星投影)
val list = listOf(1,2,3)
//判断一个变量是否是列表
if(list is List<*>) {
// 星投影,类似Java的 <?>
}
和JAVA不同,泛型可以用在类型转换中
val intList = list as? kotlin.collections.List<Int> ? : throw IllegalArgumentException("转换失败")
使用out
和in
来声明泛型是可协变/逆变的
协变(covariant:Foo<父类> => Foo<子类>)
协变为父类泛型型变为具体子类,Kotlin 提供 in
关键字,来代替 Java
中<? extends E>
的通配符语法,同时为了解决Java中泛型型变类型擦除中,所引起的类型转换安全,Kotlin
采用了 C#
的策略,即协变类型作为消费者,只能读取而不能写入
逆变(contravariance:Foo<子类> => Foo<父类>)
逆变为子类型型变为具体父类,Kotlin 提供 out 关键字,来代替 Java
中<? super E>
的通配符语法,同样采用了 C#
的策略,逆变类型作为生产者,只能写入而不能读取
使用object
修饰的类是单例的,实现方式为饿汉式,其方法的调用和JAVA中静态函数调用方式一样,但它不是定义静态类,只是和JAVA中静态函数调用的写法一样而已
object
在局部域内使用没有声明单例对象的语义,在局部域内使用object
关键字表示创建匿名内部类
object DriverManager{
fun connect(str: String){
(str)
println}
}
fun main(args: Array<String>) {
.connect("com.mysql.jdbc.Driver")
DriverManager}
其Kotlin字节码反编译成JAVA后的代码
public final class DriverManager {
public static final DriverManager INSTANCE;
public final void connect(@NotNull String str) {
.checkParameterIsNotNull(str, "str");
IntrinsicsSystem.out.println(str);
}
static {
DriverManager var0 = new DriverManager();
= var0;
INSTANCE }
}
在JAVA中使用该类通过INSTANCE获取实例(由此可以看出其方法并非真正的静态),通过实例掉对应方法
DriverManager dm = DriverManager.INSTANCE;
.connect("com.mysql.jdbc.Driver") dm
在Kotlin中没有static关键字,因为对于工具类的函数,在Kotlin中可以转化为包级函数,但如果要声明静态变量或静态函数,可以使用伴生对象(companion object
)(虽然object可以实现静态调用的写法,但其全部方法、变量都要写成JAVA中静态调用的形式的,而companion object
可以实现部分方法、变量静态;而且companion object
是在对应的类加载时初始化的,和Java的静态初始化是对应的)
class DriverManager {
companion object {
//companion object块内的方法及变量可以静态调用
//JvmField使在JAVA里可以像静态变量那样调用,否则在JAVA里要通过DriverManager.Companion.getMysqlUrl()的方式获取
@JvmField
var mysqlUrl = "com.mysql.jdbc.Driver"
@JvmField
var sqlServerUrl = "com.microsoft.sqlserver.jdbc.SQLServerDriver"
//JvmStatic使在JAVA里可以像静态函数那样调用,否则在JAVA中要通过DriverManager.Companion.connect的方式调用
@JvmStatic
fun connect(url: String) {
(url)
println}
}
//其他函数要通过对象调用
fun otherfun() {
("otherfun")
println}
}
fun main(args: Array<String>) {
.connect(DriverManager.mysqlUrl)
DriverManagervar dm = DriverManager()
.otherfun()
dm}
Kotlin中,内部类默认是静态的,如果想定义非静态的内部类,可以通过inner
关键字实现,要访问外部类名字冲突的函数/变量,可以使用this@Outter
(同理有this@Inner
)
class A{
var num = 100
inner class B{
var num = 50
fun funB1(){
("funB1 ${this@A.num} ${this@B.num}")
println}
}
class C{
fun funC1(){
("funC1")
println}
}
}
fun main(args: Array<String>) {
var b = A().B()
var c = A.C()
}
匿名内部类使用object:父类构造函数,接口1,接口2,...
,可以在继承类的同时实现接口,这点是在JAVA中做不到的
interface B{
fun func()
}
open class A(name: String){
var a: A = object:A("aaa"), B{
override fun func() {
}
}
}
有时候我们只是需要一个没有父类的对象,我们可以这样写:
val position = object {
var x: Int = 0
var y: Int = 0
}
和JAVA不同,Kotlin支持闭包
interface B{
fun func()
}
open class A(name: String)
fun getB(): B{
var num: Int = 0
return object:A("aaa"), B{
override fun func() {
(++num)
println}
}
}
fun main(args: Array<String>) {
var a: B = getB()
.func()//1
a.func()//2
a}
和JDK8一样,如果一个接口是函数式接口,则可以用Lambda表达式创建,不需要使用匿名内部类(Kotlin调用JAVA时,Kotlin支持JAVA的FunctionalInterface
写法,但Kotlin自身没有FunctionalInterface
的写法,只能定义两个重裁的方法或使用别名(把Lambda类型的别名起的和类名一样))
数据类的出现是为了简化JavaBean,一个用data
修饰的类会对构造函数中用var
或val
修饰的参数创建get、set方法(在JAVA中调用get、set,在Kotlin中直接调属性),以及会重写toString、equals、hashCode、copy、componentN等方法
数据类有如下限制:
val
或者var
abstract
,open
,sealed
或者inner
和JavaBean不同的是,data class没有默认的空构造器,且data
class默认为final类型,但可以通过安装allOpen
、noArg
插件更改生成的字节码
data class Person(val name: String, var age: Int)
fun main(args: Array<String>) {
val p = Person("小明",14)
(p.age)
println(p.name)
println(p.toString())
println(p.hashCode())
println
val (name, age) = Person("小红", 13)
}
每个枚举常量只有一个实例,用法和JAVA类似
enum class Color(val rgb: Int) {
(0xFF0000){
REDoverride fun printSelf() {
("RED")
println}
},
(0x00FF00) {
GREENoverride fun printSelf() {
("GREEN")
println}
},
(0x0000FF) {
BLUEoverride fun printSelf() {
("BLUE")
println}
};//枚举成员与函数之间必须用分号隔开
fun printValues(){
("RED GREEN BLUE")
println}
abstract fun printSelf()
}
fun main(args: Array<String>) {
for(c in Color.values()){
(c)
println}
}
密封类可以限制类的子类有限(好处在于写when的时候不用写else),密封类要求其子类与其定义在同一个文件中
sealed class Operation {
class Add(val value: Int) : Operation()
class Substract(val value: Int) : Operation()
class Multiply(val value: Int) : Operation()
class Divide(val value: Int) : Operation()
}
或者
sealed class Operation {
}
class Add(val value: Int) : Operation()
class Substract(val value: Int) : Operation()
class Multiply(val value: Int) : Operation()
class Divide(val value: Int) : Operation()
Kotlin的注解在使用上完全兼容JAVA的注解
声明
annotation class myanno
//也可以有构造函数
annotation class special(val why: String)
使用
@myanno class MyClass {
@myanno fun func(@myanno num: Int): Int {
return (@myanno 1)
}
}
//有构造函数时
@special("example") class MyClass {}
在多数情形中@
标识是可选的。只有在注解表达式或本地声明中才必须:
class MyClass {
myanno fun func(myanno num: Int): Int {
myanno return (@myanno 1)
}
}
如果要给构造函数注解,就需要在构造函数声明时添加 constructor 关键字,并且需要在前面添加注解
class MyClass @inject constructor(num: Int)
也可以注解属性访问者
class MyClass {
var num: Int?=null
@inject set
}
也可以给Lambda表达式加注解(区别于标签,标签的@
在后面),这将会应用到
lambda 生成的invoke()
方法
val f = @myanno { x: Int -> x + 10 }
通过DSL,Kotlin可以实现其他语言的部分写法,比如写如下的Kotlin风格的html
{
html {
head = "utf-8"
charset }
{
body {
h1 +"标题"
}
}
}
html其实是一个参数为Lambda的函数,所以小括号可以省略,head、body同理,charset其实是一个成员变量,由于head的Lambda使用了扩展Lambda,所以里面的this就是指html函数对应的HtmlNode类;真正封装数据的是TagNode类,里面有properties和children两个成员变量,像charset这种成员变量要添加到properties这个map中是通过delegate实现的
import java.lang.StringBuilder
import kotlin.reflect.KProperty
fun main(args: Array<String>) {
//使用我们自己的dsl
val str = html {
{
head = "utf-8"
charset }
{
body {
h1 +"标题"
}
}
}.render()
(str)
println
}
//四个参数为Lambda的函数,分别对应html的标签(html、head、body、h1),可以实现"html{ }"的写法
fun html(block: HtmlNode.() -> Unit): HtmlNode {
return HtmlNode().apply { block(this) }
}
//把子节点加到HtmlNode中,要访问HtmlNode的children成员变量,所以要给HtmlNode添加扩展方法
//要像使用HeadNode的charset属性,就要把Lambda的接收者指定为HeadNode
fun HtmlNode.head(block: HeadNode.() -> Unit) {
//这里两个this是不同的,加标签以区分两个this
@head.children.add(HeadNode().apply { block(this@apply) })
this}
fun HtmlNode.body(block: BodyNode.() -> Unit) {
//不加标签其实也一样,但可读性不好
this.children.add(BodyNode().apply { block(this) })
}
fun BodyNode.h1(block: H1Node.() -> Unit) {
//前面的this也可以不写,但后面的this不写就无法使用H1Node的运算符重裁了
.add(H1Node().apply { block(this) })
children}
//html标签的基类
open class TagNode(open var name: String) {
var properties = HashMap<String, Any>()
var children = ArrayList<TagNode>()
//打印节点对应的html字符串的函数
open fun render(): String {
return StringBuilder()
.append("<$name")
.apply {
for ((key, value) in properties) {
(" $key=\"$value\"")
append}
}
.append(">")
.apply {
for (child in children) {
(child.render())
append}
}.append("</$name>").toString()
}
}
//每个具体的html标签对应的类,在各个类中可以有自己的属性
class HtmlNode : TagNode("html")
class HeadNode : TagNode("head") {
//属性最后要添加到properties这个map中,通过delegate实现
var charset by MyDelegate(properties)
}
class BodyNode : TagNode("body")
class StringNode(override var name: String) : TagNode(name) {
override fun render(): String = name
}
class H1Node : TagNode("h1") {
operator fun String.unaryPlus() {
.add(StringNode(this))
children}
}
class MyDelegate(var properties: HashMap<String, Any>) {
operator fun getValue(headNode: HeadNode, property: KProperty<*>): Any {
return properties[property.name] ?: ""
}
operator fun setValue(headNode: HeadNode, property: KProperty<*>, value: Any) {
[property.name] = value
properties}
}
以上只是一个demo,官方有很多完备DSL可以参考官方项目kotlinx.html
传统的线程的运行是抢占式的,什么时候运行哪个线程是由系统决定的,我们无法预知,而协程是可以yield
(谦让)的,它给线程运行提供了暂停(suspend
,挂起)的能力,线程可以主动把执行权让出来,从而我们可以控制协程间的运行顺序,协程常用到的几个概念
CoroutineContext:
协程的上下文,类似android的context的概念,负责管理协程,一般来说,每个模块会用不同的CoroutineContext
,如果把多个模块整合起来就要用到多个CoroutineContext
,这时就可以通过设定的key来区分不同的CoroutineContext
;如果不需要用到上下文,可以使用EmptyCoroutineContext
Continuation:
前面说过,协程提供了一种暂停的能力,而可继续执行才是最终的目的;Continuation
是一个接口,
它有两个方法,一个是resume
,它代表当前协程运行结束,程序恢复到主线程或其他协程,并把返回值返回,如果我们的程序没有任何异常,那么直接调用这个方法并传入需要返回的值;另一个是resumeWithException
,如果我们的程序出了异常,那我们可以通过调用这个方法把异常传递出去;它还有一个需要重写的属性。就是上面提到的CoroutineContext
suspend关键字:
表示要被挂起(suspend
)的代码,用于修饰函数或Lambda,用suspend
关键字修饰函数或Lambda只能运行在协程或其它suspend
函数中,被suspend
修饰的函数或Lambda可以使用startCoroutine
或createCoroutine
函数来启动协程
suspendCoroutine函数:
真正在协程中运行的代码,参数是一个Lambda表达式,该Lambda表达式为要在协程中运行的代码,函数的返回值就是通过continuation.resume
返回的返回值
下面是一个使用协程的例子
import kotlin.coroutines.experimental.*
fun main(args: Array<String>) {
(Thread.currentThread().name + "-- before coroutine")
println//启动协程(通过startCoroutine,这里通过loadImage函数进行了封装)
("127.0.0.1/img.png") {
loadImage(Thread.currentThread().name + "-- in coroutine. Before suspend.")
println//使用协程执行耗时操作
val result: ByteArray = suspendCoroutine {
//这个continuation就是我们创建的那个内部类
: Continuation<ByteArray> ->
continuation(Thread.currentThread().name + "-- in suspend block.")
println.resume(requestUrl(continuation.context[ImageUrlContext]!!.url))
continuation(Thread.currentThread().name + "-- after resume.")
println}
(Thread.currentThread().name + "-- in coroutine. After suspend. image size = ${result.size}")
println//Lambda表达式的最后一行表示返回值
result}
(Thread.currentThread().name + "-- after coroutine")
println}
//上下文,用来存放我们需要的信息
class ImageUrlContext(val url: String) : AbstractCoroutineContextElement(ImageUrlContext) {
companion object Key : CoroutineContext.Key<ImageUrlContext>
}
//用于开启协程的函数
fun loadImage(url: String, block: suspend () -> ByteArray) {
val continuation = object : Continuation<ByteArray> {
override val context: CoroutineContext = ImageUrlContext(url)
override fun resume(imageArray: ByteArray) {
(Thread.currentThread().name + "-- resume: ${imageArray.size}")
println}
override fun resumeWithException(exception: Throwable) {
(exception.toString())
println}
}
.startCoroutine(continuation)
block}
//在协程中运行的耗时操作
fun requestUrl(url: String): ByteArray {
(Thread.currentThread().name + "-- request url for $url.")
println//暂时用这个模拟耗时
.sleep(1000)
Thread//暂时用这个模拟获取的图片
return ByteArray(1024)
}
打印
main-- before coroutine
main-- in coroutine. Before suspend.
main-- in suspend block.
main-- request url for 127.0.0.1/img.png.
main-- after resume.
main-- in coroutine. After suspend. image size = 1024
main-- resume: 1024
main-- after coroutine
为什么没有在子线程中运行我们的耗时操作呢?这是因为协程不知道哪些是耗时的操作,也不知道应该分为几个线程去执行我们的代码,所以我们要自己指定
这里使用线程池来实现异步
//创建一个线程池
private val executor = Executors.newSingleThreadScheduledExecutor {
: Runnable ->
it(it, "scheduler")
Thread}
fun main(args: Array<String>) {
(Thread.currentThread().name + "-- before coroutine")
println("127.0.0.1/img.png") {
loadImage(Thread.currentThread().name + "-- in coroutine. Before suspend.")
printlnval result: ByteArray = suspendCoroutine {
: Continuation<ByteArray> ->
continuation(Thread.currentThread().name + "-- in suspend block.")
println//把协程加到线程池中
.submit {
executor.resume(requestUrl(continuation.context[ImageUrlContext]!!.url))
continuation(Thread.currentThread().name + "-- after resume.")
println}
}
.shutdown()
executor(Thread.currentThread().name + "-- in coroutine. After suspend. image size = ${result.size}")
println
result}
(Thread.currentThread().name + "-- after coroutine")
println}
此时打印
main-- before coroutine
main-- in coroutine. Before suspend.
main-- in suspend block.
main-- after coroutine
scheduler-- request url for 127.0.0.1/img.png.
scheduler-- in coroutine. After suspend. image size = 1024
scheduler-- resume: 1024
scheduler-- after resume.
这时,先执行after coroutine
,然后才执行request url
,被挂起的代码在子线程中执行了
但是这样一看,似乎和多线程没什么区别啊?其实区别还是有的,首先,协程是可以控制执行顺序的,其次,多个协程可以共享一个线程,当服务器高并发时,比如有1k用户连接,如果使用多线程,一个用户创建一个线程,那创建1k个线程是很大的开销,而且不是每个用户都同时需要数据传输,而如果使用协程,那我们可以使用少数用于承载用户数据传输的协程的线程,只有用户使用到数据传输时才去响应,其余时间挂起,这样对内存的消耗要比多线程少得多
首先把异步代码封装一下
//伪代码,模拟需要在主线程更新UI
private val imageView = ImageView()
class ImageView {
fun setBackground(imageArray: ByteArray) = println(Thread.currentThread().name + "-- setBackground")
}
//----------------------------------------------
fun main(args: Array<String>) {
(Thread.currentThread().name + "-- before coroutine")
printlnval url = "127.0.0.1/img.png"
{
suspend (Thread.currentThread().name + "-- in coroutine. Before suspend.")
printlnval result: ByteArray = suspendCoroutine { continuation: Continuation<ByteArray> ->
(Thread.currentThread().name + "-- in suspend block.")
println{
AsyncTask val byteArray = requestUrl(url)
if (byteArray.size > 0) {
//成功获取图片
.invokeLater {
SwingUtilities.resume(byteArray)
continuation}
}else{
//获取图片失败
.invokeLater {
SwingUtilities.resumeWithException(RuntimeException("error"))
continuation}
}
(Thread.currentThread().name + "-- after resume.")
println}.execute()
}
//在resume的时候切到了UI线程,所以这里在UI线程执行
.setBackground(result)//伪代码
imageView(Thread.currentThread().name + "-- in coroutine. After suspend. image size = ${result.size}")
println
result}.startCoroutine(MyContinuation())
(Thread.currentThread().name + "-- after coroutine")
println}
class AsyncTask(val block: () -> Unit) {
//创建一个线程池
private val executor by lazy {
.newCachedThreadPool()
Executors}
fun execute() {
//在线程池中执行
.execute(block)
executor//等待任务执行完成,然后关闭
.shutdown()
executor}
}
class MyContinuation(override val context: CoroutineContext = EmptyCoroutineContext) : Continuation<ByteArray> {
override fun resume(value: ByteArray) {}
override fun resumeWithException(exception: Throwable) {}
}
//在协程中运行的耗时操作
fun requestUrl(url: String): ByteArray {
(Thread.currentThread().name + "-- request url for $url.")
println//暂时用这个模拟耗时
.sleep(1000)
Thread//暂时用这个模拟获取的图片
return ByteArray(1024)
}
但是这里invokeLater
写了好多遍,所以我们希望优化一下
这里用到了wrapper
(包装)的概念
class MyContinuationWrapper<T>(val continuation: Continuation<T>) : Continuation<T> {
override val context: CoroutineContext = EmptyCoroutineContext
override fun resume(value: T) {
.invokeLater {
SwingUtilities.resume(value)
continuation}
}
override fun resumeWithException(exception: Throwable) {
.invokeLater {
SwingUtilities.resumeWithException(exception)
continuation}
}
}
这时,main
函数可以改成
fun main(args: Array<String>) {
(Thread.currentThread().name + "-- before coroutine")
printlnval url = "127.0.0.1/img.png"
{
suspend (Thread.currentThread().name + "-- in coroutine. Before suspend.")
printlnval result: ByteArray = suspendCoroutine { continuation: Continuation<ByteArray> ->
(Thread.currentThread().name + "-- in suspend block.")
println//使用wrapper帮我们调SwingUtilities.invokeLater
val continuationWrapper = MyContinuationWrapper(continuation)
{
AsyncTask val byteArray = requestUrl(url)
if (byteArray.size > 0) {
.resume(byteArray)
continuationWrapper} else {
.resumeWithException(RuntimeException("error"))
continuationWrapper}
(Thread.currentThread().name + "-- after resume.")
println}.execute()
}
.setBackground(result)//伪代码
imageView(Thread.currentThread().name + "-- in coroutine. After suspend. image size = ${result.size}")
println
result}.startCoroutine(MyContinuation())
(Thread.currentThread().name + "-- after coroutine")
println}
但是这时要定义一个MyContinuationWrapper
,不方便,我们还希望优化一下
这里用到了ContinuationInterceptor
(拦截器)的概念,我们通过拦截器帮我们创建wrapper
//拦截功能通过Context实现,所以Context要实现拦截器接口
//我们一般会把拦截器作为Context的key(AbstractCoroutineContextElement的构造函数传入key)
class MyCoroutineContext: AbstractCoroutineContextElement(ContinuationInterceptor),ContinuationInterceptor{
//不同的Continuation要用不同的拦截器,如果传入的Continuation用的拦截器不是MyCoroutineContext,则要调它的拦截器对应的拦截方法
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
return MyContinuationWrapper(continuation.context.fold(continuation) {
//cont:Continuation,element:Continuation的拦截器
, element ->
contif (element != this@MyCoroutineContext && element is ContinuationInterceptor)
.interceptContinuation(continuation)
elementelse cont
})
}
}
这时,我们要把上下文设置为MyCoroutineContext
,由于上下文有拦截器的功能,所以它可以帮我们创建wrapper
fun main(args: Array<String>) {
(Thread.currentThread().name + "-- before coroutine")
printlnval url = "127.0.0.1/img.png"
{
suspend (Thread.currentThread().name + "-- in coroutine. Before suspend.")
printlnval result: ByteArray = suspendCoroutine { continuation: Continuation<ByteArray> ->
(Thread.currentThread().name + "-- in suspend block.")
println{
AsyncTask val byteArray = requestUrl(url)
if (byteArray.size > 0) {
.resume(byteArray)
continuation} else {
.resumeWithException(RuntimeException("error"))
continuation}
(Thread.currentThread().name + "-- after resume.")
println}.execute()
}
.setBackground(result)//伪代码
imageView(Thread.currentThread().name + "-- in coroutine. After suspend. image size = ${result.size}")
println
result}.startCoroutine(MyContinuation(MyCoroutineContext()))//设置成我们的上下文
(Thread.currentThread().name + "-- after coroutine")
println}
至此,我们已经完成封装了,我们还可以把上面的代码抽象成可以执行任意任务的函数
fun <T> runtask(block: suspend () -> T) {
.startCoroutine(MyContinuation(MyCoroutineContext()))
block}
fun <T> task(block: () -> T) = suspendCoroutine { continuation: Continuation<T> ->
suspend {
AsyncTask try {
(Thread.currentThread().name + "-- in suspend block.")
println.resume(block())
continuation(Thread.currentThread().name + "-- after resume.")
println} catch (e: Exception) {
.resumeWithException(e)
continuation}
}.execute()
}
class AsyncTask(val block: () -> Unit) {
private val executor by lazy {
.newCachedThreadPool()
Executors}
fun execute() {
.execute(block)
executor.shutdown()
executor}
}
class MyContinuation<T>(override val context: CoroutineContext = EmptyCoroutineContext) : Continuation<T> {
override fun resume(value: T) {}
override fun resumeWithException(exception: Throwable) {}
}
class MyContinuationWrapper<T>(val continuation: Continuation<T>) : Continuation<T> {
override val context: CoroutineContext = EmptyCoroutineContext
override fun resume(value: T) {
.invokeLater {
SwingUtilities.resume(value)
continuation}
}
override fun resumeWithException(exception: Throwable) {
.invokeLater {
SwingUtilities.resumeWithException(exception)
continuation}
}
}
class MyCoroutineContext : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
return MyContinuationWrapper(continuation.context.fold(continuation) { cont, element ->
if (element != this@MyCoroutineContext && element is ContinuationInterceptor)
.interceptContinuation(continuation)
elementelse cont
})
}
}
调用
fun main(args: Array<String>) {
(Thread.currentThread().name + "-- before coroutine")
printlnval url = "127.0.0.1/img.png"
{
runtask (Thread.currentThread().name + "-- in coroutine. Before suspend.")
printlnval result: ByteArray = task {
val byteArray = requestUrl(url)
if (byteArray.size > 0) {
byteArray} else {
throw RuntimeException("error")
}
}
.setBackground(result)//伪代码
imageView(Thread.currentThread().name + "-- in coroutine. After suspend. image size = ${result.size}")
println}
(Thread.currentThread().name + "-- after coroutine")
println}
但是这里的url
是协程执行所需的参数,它在这里是常量,但如果在其他情况下有可能会出现线程安全问题,所以我们应该创建一个携带信息的上下文来存储
创建携带信息的Context
class UrlCoroutineContext(val url: String) : AbstractCoroutineContextElement(Key) {
companion object Key : CoroutineContext.Key<UrlCoroutineContext>
}
在startCoroutine
时通过+
添加携带Interceptor
的Context
和用户指定的Context
fun <T> runtask(context: CoroutineContext = EmptyCoroutineContext, block: suspend () -> T) {
//重裁的+,可以添加多个Context
.startCoroutine(MyContinuation(context + MyCoroutineContext()))
block}
//拓展Lambda,因为需要在协程中通过上下文获取url数据
fun <T> task(block: CoroutineContext.() -> T) = suspendCoroutine { continuation: Continuation<T> ->
suspend {
AsyncTask try {
(Thread.currentThread().name + "-- in suspend block.")
println//在执行block时,给block指定接收者
.resume(block(continuation.context))
continuation(Thread.currentThread().name + "-- after resume.")
println} catch (e: Exception) {
.resumeWithException(e)
continuation}
}.execute()
}
class AsyncTask(val block: () -> Unit) {
private val executor by lazy {
.newCachedThreadPool()
Executors}
fun execute() {
.execute(block)
executor.shutdown()
executor}
}
class MyContinuation<T>(override val context: CoroutineContext = EmptyCoroutineContext) : Continuation<T> {
override fun resume(value: T) {}
override fun resumeWithException(exception: Throwable) {}
}
class MyContinuationWrapper<T>(val continuation: Continuation<T>) : Continuation<T> {
//用户要用到Context来获取url信息,所以创建Wrapper时要使用continuation.context
override val context: CoroutineContext = continuation.context
override fun resume(value: T) {
.invokeLater {
SwingUtilities.resume(value)
continuation}
}
override fun resumeWithException(exception: Throwable) {
.invokeLater {
SwingUtilities.resumeWithException(exception)
continuation}
}
}
class MyCoroutineContext : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
return MyContinuationWrapper(continuation.context.fold(continuation) { cont, element ->
if (element != this@MyCoroutineContext && element is ContinuationInterceptor)
.interceptContinuation(continuation)
elementelse cont
})
}
}
调用
fun main(args: Array<String>) {
(Thread.currentThread().name + "-- before coroutine")
println//指定携带url的CoroutineContext
(UrlCoroutineContext("127.0.0.1/img.png")) {
runtask(Thread.currentThread().name + "-- in coroutine. Before suspend.")
printlnval result: ByteArray = task {
//这里this指CoroutineContext,它有两个key,一个是拦截器,一个是UrlCoroutineContext
val byteArray = requestUrl(this[UrlCoroutineContext]!!.url)
if (byteArray.size > 0) {
byteArray} else {
throw RuntimeException("error")
}
}
.setBackground(result)//伪代码
imageView(Thread.currentThread().name + "-- in coroutine. After suspend. image size = ${result.size}")
println}
(Thread.currentThread().name + "-- after coroutine")
println}
打印
main-- before coroutine
main-- after coroutine
AWT-EventQueue-0-- in coroutine. Before suspend.
pool-1-thread-1-- in suspend block.
pool-1-thread-1-- request url for 127.0.0.1/img.png.
pool-1-thread-1-- after resume.
AWT-EventQueue-0-- setBackground
AWT-EventQueue-0-- in coroutine. After suspend. image size = 1024
任务被挂起,先执行after coroutine
,耗时操作在线程池中运行,更新UI在UI线程运行,没有任何问题,而且写起来和普通的代码一样