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
print(d1==d2)//falseval修饰的常量只是在语义上不可修改,但编译成字节码的时候仍然是使用其引用,不会宏替换,如果想使用宏替换,需要定义编译期常量,编译期常量用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 0x000FF000Char类型及String可以用转义字符
\t 制表符
\b 光标后退一个字符
\n 回车
\r 光标回到行首
\' 单引号
\" 双引号
\\ 反斜杠
\$ 美元符号,Kotlin 支持美元符号开头的字符串模板
//字符串模板用$name,或${name},对于复杂的表达式必须带大括号(如调用对象的属性时)
val name = "小明"
println("Hello ${name}")
//长字符串用三个引号包裹,长字符串无法使用转义字符,但可以用字符串模板
val blobText = """你好
\tHello
\${name}
"""
println(blobText)==与===)//==比较字符串内容,相当于JAVA的的equals,===比较是否为同一个对象,这同样适用于基本数据类型(同理也有!=和!==)
val aString = "Hello World!"
val hello = "Hello"
val bString = hello + " World!"
println(aString == bString)//true
println(aString === bString)//false
//基本数据类型会根据需要转换成JAVA的装箱类,也会出现值相同,但对象不相同的情况
//可空的基本数据类型是会自动装箱的,因为JAVA中的int等基本数据类型是不能为null的,而包装类Integer是可以为null的
val i1: Int = 128
val i2: Int? = i1//Integer.valueOf(i1)
val i3: Int? = i1//Integer.valueOf(i1)
println(i2 == i3)//true
println(i2 === i3)//false如果想像JAVA一样定义一个String,给其赋值为null,在Kotlin里面是不行的,如果一定要这样做,需要使用可空类型(类名?)
var str: String? = null但是使用的时候就要加?或!!(非空断言)
//尝试去掉length,如果str为null,则返回null
println(str?.length)//null
//确定str不为null,如果str为null,则抛异常
println(str!!.length)//报kotlin.KotlinNullPointerException如果觉得每次都这样写很烦,可以先判断不为空,这时编译器可以智能识别
if(str != null){
//因为判过是否为空,则调用时不需加?或!!
println(str.length)
}
//同样,在&&或者||右边也是可以智能识别的
if(str != null && str.length > 5){
//..
}判空的简化写法(Elvis运算符)
var str: String? = null
println(str?: "str is null")//如果str为null,则打印str is null
//如果不为空则打印长度,如果为空则打印“empty”
println(str?.length ?: "empty")这种写法在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()
println(person is Any)//true
println(person is Person)//true
println(person is Student)//false
}0..100 //相当于[0, 100],对应的类型是IntRange
0 until 100 //相当于[0, 100),对应的类型还是IntRange,其实就是[0,99]区间可用于for循环,in用在for中可以遍历区间内的元素,用在条件判断中可以判断一个数是否在该区间内(in、!in)
for(i in 1..20){
println(i)
}
//in关键字的另一个用法
if(2 in 1..10){
//...
}还可以使用downTo(倒序)和step(步长)(step无法使用浮点型)
for(i in 20 downTo 1 step 4){
println(i)//20 16 12 8 4
}基本数据类型都有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("小红", "小明", "小花", "小强", "小王")
println(charArray.joinToString(""))//abcde
println(stringArray.slice(1..3 step 2))//[小明, 小强]也可以直接调用Array的构造函数
//创建一个长度为5,内容为下标的平方的数组
val square: Array<Int> = Array(5, { i -> i * i })
square.forEach(::println)//输出0 1 4 9 16包的概念和JAVA一样,包的声明也应该在最前面
package com.myapp导入包中的某个类的时候可以给这个类取别名
import com.myapp.MyClass as A//MyClass这个类别名为A也可以在代码中给类取别名
typealias B = Boolean
var b: B = trueif与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()
myPrint(view)
}
fun myPrint(view : View){
when (view) {
is TextView -> println("TextView")
is RecyclerView -> println("RecyclerView")
is SearchView -> println("SearchView")
//甚至可以调函数
getMyView() -> println("MyView")
is 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)
println(i)
for (item in items)
println(item)
for (i in items.indices)
println(items[i])
//实现了operator componentN后可以以这种方式遍历
for ((index, value) in items.withIndex())
println("index=$index value=$value")
var lists = listOf(mapOf("name" to "小红", "age" to 14), mapOf("name" to "小明", "age" to 15))
for(map in lists){
for((key,value) in map){
print("$key = $value ")
}
println()
}for可以遍历任何提供了iterator的函数或对象
fun main(args: Array<String>) {
for (i in fibonacci()) {
if (i > 100) break
println(i)
}
}
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
second += first
first = second - first
return result
}
}
}
}while、do while和JAVA中一样
continue和break可以使用标签
loop@ for(i in 1..5) {
for(j in 1..5) {
if(i==j && i>3) break@loop
print("($i,$j) ")
}
}和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 }
//调用方式
println(getAdd3()(4, 5))
//或者
var add3 = getAdd3()
add3(4, 5)函数如果没有返回值,则返回值类型为Unit(用于返回函数时对函数类型的声明,普通函数不写返回值类型就行了)
fun getMethod(): (Int)->Unit{
return fun(i: Int){ println(i) }
}
fun method1(){
println("Hello World")
}如果想表示一个函数永远返回null,可以使用Nothing?
fun method2(): Nothing?{
println()
return 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")
method1(str = *array, num2 = 2.3)
}由于JAVA不支持默认参数,如果想在JAVA中使用Kotlin定义的默认参数函数,可以加@JvmOverloads
@JvmOverloads
fun method2(num1: Int = 0, vararg str: String, num2: Double) {
println(num1)
str.forEach { println(it) }
println(num2)
}此时会生成两个不同的重裁方法
@JvmOverloads
public static final void method2(int num1, @NotNull String[] str, double num2) {
Intrinsics.checkParameterIsNotNull(str, "str");
}
@JvmOverloads
public static void method2$default(int var0, String[] var1, double var2, int var4, Object var5) {
if ((var4 & 1) != 0) {
var0 = 0;
}
method2(var0, var1, var2);
}
@JvmOverloads
public static final void method2(@NotNull String[] str, double num2) {
method2$default(0, str, num2, 1, (Object)null);
}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) }
//和上面的写法完全等价
array.forEach { println(it) }
val map = mapOf("a" to "1","b" to "2","c" to "3")
map.forEach{
_ , 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 {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return 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){
println("x is zero!!")
return@tag 0//通过标签返回是允许的,注意带返回值的标签返回的写法
}
println(x)
//Lambda表达式最后一行是返回值,这里相当于return@tag 1(这里也不能直接用return)
1
}
innerfunc(0)
println("func1 -- end")
}输出
x is zero!!
func1 -- end
但是如果是函数(无论匿名还是具名函数)就可以使用空白的return返回,此时返回的是内部函数,外部函数并不会返回
//下面代码可以通过编译
fun func1(){
var innerfunc = fun(x: Int){
if(x == 0) {
println("x is 0!!")
return
}
println(x)
}
innerfunc(0)
println("func1 -- end")
}输出
x is 0!!
func1 -- end
当接收该Lambda表达式的函数是内联(inline)的时候,Lambda表达式中可以直接使用return返回,此时return会把定义该Lambda表达式的函数返回(非局部返回),而不是结束当前Lambda表达式
fun main(args: Array<String>) {
println(func2())
println("main: -- end")
}
fun func2(): String {
func1 { x: Int ->
if (x == 0) {
println("func2: x is 0!!")
//这里return是把func2函数返回,所以返回值类型和func2一致
return "x is zero!!"
}
println("func2: x is $x")
//Lambda表达式最后一行是返回值,这里会返回到调用该Lambda表达式的地方,也就是func1,而且不会把func1返回,所以返回值类型只要和函数参数要求一样就行了
x
}
println("func2: -- end")
return "FUNC2"
}
inline fun func1(myFunc: (Int) -> Int) {
var ret: Int = myFunc(0)
println("func1: resilt is $ret")
println("func1: -- end")
}此时输出
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>) {
items.forEach{
if(it == 0) return //返回到定义该Lambda表达式的地方,也就是结束当前Lambda表达式
println(it)//输出1 4
}
//可以使用隐式标签,隐式标签为使用该Lambda表达式的函数名
items.forEach {
if (it == 0) return@forEach//跳过此次遍历,有点像循环控制中的continue
println(it)//输出1 4 9 7
}
}由于inline会导致函数中所有参数都被内联,如果希望某些参数不被内联,可以使用noinline修饰这些参数
fun main(args: Array<String>) {
func1 { x: Int ->
if(x==0)
return@func1 0//此时只能通过标签返回,隐式标签返回会返回到使用该Lambda表达式的地方,所以后面的打印还能继续执行
x + 10
}
}
inline fun func1(noinline myFunc: (Int) -> Int) {
println(myFunc(0))
println("func1 -- end")
}输出
0
func1 -- end
在inline函数中使用crossinline修饰的参数会被禁止非局部返回
inline fun func1(noinline myFunc: (Int) -> Int) {
println(myFunc(0))//此时myFunc也不能直接使用return,只能通过标签的形式返回
}内联函数还可以使用reified关键字来具体化类型参数
fun main(args: Array<String>) {
println(isTypeof(Person(), Person::class.java))
println(isTypeof<Person>(Person()))
}
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>) {
print(Complex(1.0, 2.5) - Complex(2.0, 1.5))
}
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()
println(stu belongTo partent)
}
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()
p("bbb")
p("aaa", "bbb", "ccc", "ddd")
}
class Person(val name: String) {
operator fun invoke() {
println("my name is $name")
}
operator fun invoke(str: String) {
println("my name is $str")
}
operator fun invoke(str1: String, str2: String, str3: String, str4: String) {
println("$str1 $str2 $str3 $str4")
}
}输出
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))
destination.add(element)
return destination
}实现函数复合
fun main(args: Array<String>) {
println(compose(4, { x -> x + 5 }, { y -> y * 2 }))
}
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)
println(fg(4))//相当于 (4 + 5) * 2,输出18
}
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 }
println(multiplyBy2(add5(4)))//相当于 (4 + 5) * 2,输出18这时,如果想对任意函数实现复合,可以给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
println(add5AndMultiplyBy2(8))//相当于 (8 + 5) * 2,输出26
println(add5ComposeMultiplyBy2(8))//相当于 8 * 2 + 5,输出21函数复合的具体应用,如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会返回一个新的集合
a.forEach { print("$it ") }//1 7 5 2 0 3 9
println()
//map后会返回一个新的集合,而不会对a做任何修改
a.map { it + 10 }.forEach { print("$it ") }//11 17 15 12 10 13 19
println()
//把多个集合展开成一个
var b = arrayListOf(1..3, 2..4, 3..5)
b.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
println()
//reduce适用于累加、累乘等情况
println(a.reduce { ret, i -> ret + i })//27
//fold就是有初始值的reduce
println(a.fold(5) { ret, i -> ret + i })//32
//筛选
a.filter { it > 4 }.forEach { print("$it ") }//7 5 9
println()
//取出符合条件的,直到有不符合就不再往后取
a.takeWhile { it < 7 }.forEach { print("$it ") }//1
println()
//let中的it是a(也就是ArrayList),对it做更改就是对a做更改,let可以有任意返回值
a.let { it.add(10) }//ArrayList的add方法返回boolean,所以这里返回值类型就是Boolean
a.forEach { print("$it ") }//1 7 5 2 0 3 9 10
println()
//apply中的this是a(也就是ArrayList),在里面对a的更改是有效的,apply返回a本身
a.apply { add(11) }.forEach { print("$it ") }//1 7 5 2 0 3 9 10 11
println()
a.forEach { print("$it ") }//1 7 5 2 0 3 9 10 11
println()
//with的作用和apply一样,里面的this是a,也可以在里面对a做更改,但它可以有任意返回值
println(with(a){ removeAt(8) })//11,removeAt返回被删除的元素
a.forEach { print("$it ") }//1 7 5 2 0 3 9 10
println()
//use用于io操作,对于实现了AutoCloseable接口的io流,在use中使用,可以不写close
FileReader(File("test.iml")).use {
//Kotlin给JAVA的io添加了很多方法,readLines就是其中一个
println(it.readLines())
}
}把多参数的函数转化成一系列单参数函数就是柯里化
//原函数
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
//调用
add(4)(5)//相当于4 + 5,输出9同样,我们也可以通过给FunctionN添加拓展方法来实现对任意函数的柯里化
fun main(args: Array<String>) {
val ret = ::add.curried()(2)(3)(4)//通过函数引用调用Function3的curried方法
println(ret)
}
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)
println(add2And3(4))
println(add2And3(5))
println(add2And3(6))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(){
println("A->func1")
}
}
interface B{
val property: Int //abstract
fun func1(){
println("B->func1")
}
fun func2()
}
//继承的类要调其构造函数,而实现的接口不用,类与多个接口之间用逗号隔开
class C: A(), B{
override val property: Int = 0
override fun func1() {
println("C->func1")
}
override fun func2() {
println("C->func2")
super<A>.func1()
super<B>.func1()
func1()
}
}成员变量可以通过get、set方法实现访问控制,field只有在get和set中才能访问到
class Student {
var name: String? = null
set(value) {
println("set name")
field = value
}
get {
println("get name")
return field
}
var age: Int = 0
set(value) {
println("set age")
field = value
}
get {
println("get age")
return 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){
mName = name
}
}kotlin.properties.Delegates提供了很多有用的代理,而我们也可以自定义delegate,属性代理不需要任何接口的实现,但必须要提供getValue和setValue两个方法(对于var,要重写getValue和setValue;对于val,只需重写getValue)
import kotlin.reflect.KProperty
fun main(args: Array<String>) {
var stu = Student()
println(stu.name)
stu.name = "小明"
println(stu.name)
}
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) {
mName = value
}
}同样,类也可以有代理
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() {
println(x)
}
}
class Derived(b: Base) : Base by b
fun main(args: Array<String>) {
val b = BaseImpl(10)
Derived(b).print()//Derived的print由代理对象BaseImpl提供
}所有类都继承自Any,相当于JAVA的Object
要使类可继承,需要添加open关键字(否则默认是final的),同样,要使属性、方法可重写,也需要添加open关键字(接口、接口方法、抽象类、抽象类的抽象方法默认是open的),而且属性、方法的重写必须添加override关键字
继承使用默认构造器:父类构造器的形式
open class Person(open var name: String, open var age: Int){
open fun speak(){
println("speak")
}
}
class Man(override var name: String, override var age: Int):Person(name, age){
override fun speak(){
println("man speak")
}
}和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中调用
OverLoadTest test = new OverLoadTest();
test.func1()
test.func1(1)
test.func1(1,"aaa")可以使用类名.方法名的形式在类的外部给类添加函数
class User(var name:String){}
//扩展函数,在扩展函数里也可以使用this
fun User.print(){
print("用户名 ${this.name}")
}
fun main(arg:Array<String>){
var user = User("小明")
user.print()
}扩展函数是动态解析的
open class A
class B : A()
fun A.foo() = "a"
fun B.foo() = "b"
fun printFoo(a: A) {
println(a.foo())//扩展函数由发起函数调用的表达式的类型决定的,无论传入的a是A还是A的子类,都调用的是A的foo()
}
fun main(args: Array<String>) {
printFoo(B())//a
}如果扩展函数与类中的函数重名,则调类中的函数
class A {
fun foo() = println("member")
}
fun A.foo() = println("extension")
fun main(args: Array<String>) {
A().foo()//打印"member"
}扩展函数中的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表达式就不会执行
block(StringBuilder("Kotlin"))
}
class MyClass {
fun func() {
kotlinDSL {
append(" DSL")
//this就是block通过invoke创建的那个StringBuilder
println(this)//stringBuilder.toString()
//也可以通过隐式标签指定用哪个this
println(this@kotlinDSL)
println(this@MyClass)
}
}
}
fun main(args: Array<String>) {
MyClass().func()
}输出
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("小明")
myClass.changeName { "小红" }
println(myClass.name)
}对于属性的拓展,无法在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){
println(str)
}
}
fun main(args: Array<String>) {
DriverManager.connect("com.mysql.jdbc.Driver")
}其Kotlin字节码反编译成JAVA后的代码
public final class DriverManager {
public static final DriverManager INSTANCE;
public final void connect(@NotNull String str) {
Intrinsics.checkParameterIsNotNull(str, "str");
System.out.println(str);
}
static {
DriverManager var0 = new DriverManager();
INSTANCE = var0;
}
}在JAVA中使用该类通过INSTANCE获取实例(由此可以看出其方法并非真正的静态),通过实例掉对应方法
DriverManager dm = DriverManager.INSTANCE;
dm.connect("com.mysql.jdbc.Driver")在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) {
println(url)
}
}
//其他函数要通过对象调用
fun otherfun() {
println("otherfun")
}
}
fun main(args: Array<String>) {
DriverManager.connect(DriverManager.mysqlUrl)
var dm = DriverManager()
dm.otherfun()
}Kotlin中,内部类默认是静态的,如果想定义非静态的内部类,可以通过inner关键字实现,要访问外部类名字冲突的函数/变量,可以使用this@Outter(同理有this@Inner)
class A{
var num = 100
inner class B{
var num = 50
fun funB1(){
println("funB1 ${this@A.num} ${this@B.num}")
}
}
class C{
fun funC1(){
println("funC1")
}
}
}
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() {
println(++num)
}
}
}
fun main(args: Array<String>) {
var a: B = getB()
a.func()//1
a.func()//2
}和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或者varabstract,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)
println(p.age)
println(p.name)
println(p.toString())
println(p.hashCode())
val (name, age) = Person("小红", 13)
}每个枚举常量只有一个实例,用法和JAVA类似
enum class Color(val rgb: Int) {
RED(0xFF0000){
override fun printSelf() {
println("RED")
}
},
GREEN(0x00FF00) {
override fun printSelf() {
println("GREEN")
}
},
BLUE(0x0000FF) {
override fun printSelf() {
println("BLUE")
}
};//枚举成员与函数之间必须用分号隔开
fun printValues(){
println("RED GREEN BLUE")
}
abstract fun printSelf()
}
fun main(args: Array<String>) {
for(c in Color.values()){
println(c)
}
}密封类可以限制类的子类有限(好处在于写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 {}在多数情形中@标识是可选的。只有在注解表达式或本地声明中才必须:
myanno class MyClass {
myanno fun func(myanno num: Int): Int {
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 {
charset = "utf-8"
}
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 {
charset = "utf-8"
}
body {
h1 {
+"标题"
}
}
}.render()
println(str)
}
//四个参数为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
this@head.children.add(HeadNode().apply { block(this@apply) })
}
fun HtmlNode.body(block: BodyNode.() -> Unit) {
//不加标签其实也一样,但可读性不好
this.children.add(BodyNode().apply { block(this) })
}
fun BodyNode.h1(block: H1Node.() -> Unit) {
//前面的this也可以不写,但后面的this不写就无法使用H1Node的运算符重裁了
children.add(H1Node().apply { block(this) })
}
//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) {
append(" $key=\"$value\"")
}
}
.append(">")
.apply {
for (child in children) {
append(child.render())
}
}.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() {
children.add(StringNode(this))
}
}
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) {
properties[property.name] = value
}
}以上只是一个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>) {
println(Thread.currentThread().name + "-- before coroutine")
//启动协程(通过startCoroutine,这里通过loadImage函数进行了封装)
loadImage("127.0.0.1/img.png") {
println(Thread.currentThread().name + "-- in coroutine. Before suspend.")
//使用协程执行耗时操作
val result: ByteArray = suspendCoroutine {
//这个continuation就是我们创建的那个内部类
continuation: Continuation<ByteArray> ->
println(Thread.currentThread().name + "-- in suspend block.")
continuation.resume(requestUrl(continuation.context[ImageUrlContext]!!.url))
println(Thread.currentThread().name + "-- after resume.")
}
println(Thread.currentThread().name + "-- in coroutine. After suspend. image size = ${result.size}")
//Lambda表达式的最后一行表示返回值
result
}
println(Thread.currentThread().name + "-- after coroutine")
}
//上下文,用来存放我们需要的信息
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) {
println(Thread.currentThread().name + "-- resume: ${imageArray.size}")
}
override fun resumeWithException(exception: Throwable) {
println(exception.toString())
}
}
block.startCoroutine(continuation)
}
//在协程中运行的耗时操作
fun requestUrl(url: String): ByteArray {
println(Thread.currentThread().name + "-- request url for $url.")
//暂时用这个模拟耗时
Thread.sleep(1000)
//暂时用这个模拟获取的图片
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 {
it: Runnable ->
Thread(it, "scheduler")
}
fun main(args: Array<String>) {
println(Thread.currentThread().name + "-- before coroutine")
loadImage("127.0.0.1/img.png") {
println(Thread.currentThread().name + "-- in coroutine. Before suspend.")
val result: ByteArray = suspendCoroutine {
continuation: Continuation<ByteArray> ->
println(Thread.currentThread().name + "-- in suspend block.")
//把协程加到线程池中
executor.submit {
continuation.resume(requestUrl(continuation.context[ImageUrlContext]!!.url))
println(Thread.currentThread().name + "-- after resume.")
}
}
executor.shutdown()
println(Thread.currentThread().name + "-- in coroutine. After suspend. image size = ${result.size}")
result
}
println(Thread.currentThread().name + "-- after coroutine")
}此时打印
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>) {
println(Thread.currentThread().name + "-- before coroutine")
val url = "127.0.0.1/img.png"
suspend {
println(Thread.currentThread().name + "-- in coroutine. Before suspend.")
val result: ByteArray = suspendCoroutine { continuation: Continuation<ByteArray> ->
println(Thread.currentThread().name + "-- in suspend block.")
AsyncTask {
val byteArray = requestUrl(url)
if (byteArray.size > 0) {
//成功获取图片
SwingUtilities.invokeLater {
continuation.resume(byteArray)
}
}else{
//获取图片失败
SwingUtilities.invokeLater {
continuation.resumeWithException(RuntimeException("error"))
}
}
println(Thread.currentThread().name + "-- after resume.")
}.execute()
}
//在resume的时候切到了UI线程,所以这里在UI线程执行
imageView.setBackground(result)//伪代码
println(Thread.currentThread().name + "-- in coroutine. After suspend. image size = ${result.size}")
result
}.startCoroutine(MyContinuation())
println(Thread.currentThread().name + "-- after coroutine")
}
class AsyncTask(val block: () -> Unit) {
//创建一个线程池
private val executor by lazy {
Executors.newCachedThreadPool()
}
fun execute() {
//在线程池中执行
executor.execute(block)
//等待任务执行完成,然后关闭
executor.shutdown()
}
}
class MyContinuation(override val context: CoroutineContext = EmptyCoroutineContext) : Continuation<ByteArray> {
override fun resume(value: ByteArray) {}
override fun resumeWithException(exception: Throwable) {}
}
//在协程中运行的耗时操作
fun requestUrl(url: String): ByteArray {
println(Thread.currentThread().name + "-- request url for $url.")
//暂时用这个模拟耗时
Thread.sleep(1000)
//暂时用这个模拟获取的图片
return ByteArray(1024)
}但是这里invokeLater写了好多遍,所以我们希望优化一下
这里用到了wrapper(包装)的概念
class MyContinuationWrapper<T>(val continuation: Continuation<T>) : Continuation<T> {
override val context: CoroutineContext = EmptyCoroutineContext
override fun resume(value: T) {
SwingUtilities.invokeLater {
continuation.resume(value)
}
}
override fun resumeWithException(exception: Throwable) {
SwingUtilities.invokeLater {
continuation.resumeWithException(exception)
}
}
}这时,main函数可以改成
fun main(args: Array<String>) {
println(Thread.currentThread().name + "-- before coroutine")
val url = "127.0.0.1/img.png"
suspend {
println(Thread.currentThread().name + "-- in coroutine. Before suspend.")
val result: ByteArray = suspendCoroutine { continuation: Continuation<ByteArray> ->
println(Thread.currentThread().name + "-- in suspend block.")
//使用wrapper帮我们调SwingUtilities.invokeLater
val continuationWrapper = MyContinuationWrapper(continuation)
AsyncTask {
val byteArray = requestUrl(url)
if (byteArray.size > 0) {
continuationWrapper.resume(byteArray)
} else {
continuationWrapper.resumeWithException(RuntimeException("error"))
}
println(Thread.currentThread().name + "-- after resume.")
}.execute()
}
imageView.setBackground(result)//伪代码
println(Thread.currentThread().name + "-- in coroutine. After suspend. image size = ${result.size}")
result
}.startCoroutine(MyContinuation())
println(Thread.currentThread().name + "-- after coroutine")
}但是这时要定义一个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的拦截器
cont, element ->
if (element != this@MyCoroutineContext && element is ContinuationInterceptor)
element.interceptContinuation(continuation)
else cont
})
}
}这时,我们要把上下文设置为MyCoroutineContext,由于上下文有拦截器的功能,所以它可以帮我们创建wrapper
fun main(args: Array<String>) {
println(Thread.currentThread().name + "-- before coroutine")
val url = "127.0.0.1/img.png"
suspend {
println(Thread.currentThread().name + "-- in coroutine. Before suspend.")
val result: ByteArray = suspendCoroutine { continuation: Continuation<ByteArray> ->
println(Thread.currentThread().name + "-- in suspend block.")
AsyncTask {
val byteArray = requestUrl(url)
if (byteArray.size > 0) {
continuation.resume(byteArray)
} else {
continuation.resumeWithException(RuntimeException("error"))
}
println(Thread.currentThread().name + "-- after resume.")
}.execute()
}
imageView.setBackground(result)//伪代码
println(Thread.currentThread().name + "-- in coroutine. After suspend. image size = ${result.size}")
result
}.startCoroutine(MyContinuation(MyCoroutineContext()))//设置成我们的上下文
println(Thread.currentThread().name + "-- after coroutine")
}至此,我们已经完成封装了,我们还可以把上面的代码抽象成可以执行任意任务的函数
fun <T> runtask(block: suspend () -> T) {
block.startCoroutine(MyContinuation(MyCoroutineContext()))
}
suspend fun <T> task(block: () -> T) = suspendCoroutine { continuation: Continuation<T> ->
AsyncTask {
try {
println(Thread.currentThread().name + "-- in suspend block.")
continuation.resume(block())
println(Thread.currentThread().name + "-- after resume.")
} catch (e: Exception) {
continuation.resumeWithException(e)
}
}.execute()
}
class AsyncTask(val block: () -> Unit) {
private val executor by lazy {
Executors.newCachedThreadPool()
}
fun execute() {
executor.execute(block)
executor.shutdown()
}
}
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) {
SwingUtilities.invokeLater {
continuation.resume(value)
}
}
override fun resumeWithException(exception: Throwable) {
SwingUtilities.invokeLater {
continuation.resumeWithException(exception)
}
}
}
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)
element.interceptContinuation(continuation)
else cont
})
}
}调用
fun main(args: Array<String>) {
println(Thread.currentThread().name + "-- before coroutine")
val url = "127.0.0.1/img.png"
runtask {
println(Thread.currentThread().name + "-- in coroutine. Before suspend.")
val result: ByteArray = task {
val byteArray = requestUrl(url)
if (byteArray.size > 0) {
byteArray
} else {
throw RuntimeException("error")
}
}
imageView.setBackground(result)//伪代码
println(Thread.currentThread().name + "-- in coroutine. After suspend. image size = ${result.size}")
}
println(Thread.currentThread().name + "-- after coroutine")
}但是这里的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
block.startCoroutine(MyContinuation(context + MyCoroutineContext()))
}
//拓展Lambda,因为需要在协程中通过上下文获取url数据
suspend fun <T> task(block: CoroutineContext.() -> T) = suspendCoroutine { continuation: Continuation<T> ->
AsyncTask {
try {
println(Thread.currentThread().name + "-- in suspend block.")
//在执行block时,给block指定接收者
continuation.resume(block(continuation.context))
println(Thread.currentThread().name + "-- after resume.")
} catch (e: Exception) {
continuation.resumeWithException(e)
}
}.execute()
}
class AsyncTask(val block: () -> Unit) {
private val executor by lazy {
Executors.newCachedThreadPool()
}
fun execute() {
executor.execute(block)
executor.shutdown()
}
}
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) {
SwingUtilities.invokeLater {
continuation.resume(value)
}
}
override fun resumeWithException(exception: Throwable) {
SwingUtilities.invokeLater {
continuation.resumeWithException(exception)
}
}
}
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)
element.interceptContinuation(continuation)
else cont
})
}
}调用
fun main(args: Array<String>) {
println(Thread.currentThread().name + "-- before coroutine")
//指定携带url的CoroutineContext
runtask(UrlCoroutineContext("127.0.0.1/img.png")) {
println(Thread.currentThread().name + "-- in coroutine. Before suspend.")
val result: ByteArray = task {
//这里this指CoroutineContext,它有两个key,一个是拦截器,一个是UrlCoroutineContext
val byteArray = requestUrl(this[UrlCoroutineContext]!!.url)
if (byteArray.size > 0) {
byteArray
} else {
throw RuntimeException("error")
}
}
imageView.setBackground(result)//伪代码
println(Thread.currentThread().name + "-- in coroutine. After suspend. image size = ${result.size}")
}
println(Thread.currentThread().name + "-- after coroutine")
}打印
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线程运行,没有任何问题,而且写起来和普通的代码一样