#Kotlin之班门弄斧
##面向对象
##java和kotlin的交互
##协程及协程框架
<br/>
## 面向对象
1.kotlin和Dart可以说是真正的面向对象,一切皆对象,包括属性和函数,也就是说声明和引用属性和函数并不需要一个类型引用,建议把项目中出现频率高的函数定义为全局函数(也叫包级函数),属性也是如此(例子演示)
```
var name:String =""
fun globalScopeName():String{
}
// 上面声明两个成员可以不在任何一个类中, 但可以在整个library中使用
```
2.data一种封装类型 做网络结果的返回类很方便 并支持结构化声明,java 通过对相应的属性添加函数component1返回结果,在kotlin 中也可以用结构化的方式使用(例子演示)
```
data class Person(val name: String) {
var age: Int = 0
}
//使用方式和常规一样并支持结构化声明
val jack = User(name = "Jack", age = 1)
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
val olderJack = jack.copy(age = 2)
```
说到data支持结构化声明当一个lambda表达式的参数是一个data类型的数据我们也可以用结构化声明的方式
```
fun main() {
funData { (name, age) ->
}
}
fun funData(block: (N) -> Unit) {
}
data class N(var name: String, var age: Int)
```
说到这当初从java转kotlin 写kotlin的lambda总写不对总把lambda中的参数用小括号包括,错误也正是这个原因,因为在Kotlin中的lambda中参数用小括号的是支持结构化声明的对象,kotlin有哪些支持结构化的声明?
<br/>
3.sealed 对象有限的类型 和枚举很像但枚举不方便设置属性,所以在实际使用中如需要保存或标记属性值推荐用sealed
```
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// 不再需要 `else` 子句,因为我们已经覆盖了所有的情况
}
```
4.运算符
Kolin中每一个运算符都对应一个函数
```
a.plus(b)
//如自定义函数用operator修饰
fun main() {
println("")
val a1 = A("hello", 18)
val a2 = A("word", 62)
println(a1 + a2)
}
class A(var name: String, var age: Int) {
operator fun plus(a: A): A {
return A("$name-${a.name}", age + a.age)
}
override fun toString(): String {
return "[name:$name-age:$age]"
}
}
// 其他的运算符同样
```
5.扩展
扩展kotlin支持属性和方法扩展
扩展方法的语法 要扩展的对象.自定义方法名称(){}
```
fun Boolean.T(block: () -> Unit): Boolean {
if (this) {
block()
return true
}
return false
}
```
因为kotlin是完全的面向对象 当然方法也是对象可以对方法扩展方法,如Function1代表一个参数一个返回值 Function10代表10个参数10返回值以此类推,如果不知道某个函数的类型可以用print输出一下,目前最多支持一个方法有22参数,因为发现只到Function22
`Function1<T,R>.hello(){} `对Kotlin中所有一个参数的函数扩展了hello函数
扩展属性
val/var 要接收类型名称.扩展属性名称:该扩展属性的名称如:
`val <T> List<T>.lastIndex: Int`
扩展的和原有的同样的用法一样,但扩展不支持多态,扩展的不能覆盖原有的(例子演示)
```
open class C
class D: C()
fun C.foo() = "c"
fun D.foo() = "d"
printFoo(D())
fun printFoo(c: C) {
println(c.foo())
}
//这个例子会输出 "c",因为调用的扩展函数只取决于参数 c 的声明类型,该类型是 C 类。
class C {
fun foo() { println("member") }
}
fun C.foo() { println("extension") }
//调用 C().foo(1) 将输出 "extension"
```
一个简单的例子演示扩展属性的方便之处
```
import android.content.Context
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class Preference<T>(val context: Context, val name: String, val default: T, val prefName: String = "default")
: ReadWriteProperty<Any?, T>{
private val prefs by lazy {
context.getSharedPreferences(prefName, Context.MODE_PRIVATE)
}
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return findPreference(findProperName(property))
}
private fun findProperName(property: KProperty<*>) = if(name.isEmpty()) property.name else name
private fun findPreference(key: String): T{
return when(default){
is Long -> prefs.getLong(key, default)
is Int -> prefs.getInt(key, default)
is Boolean -> prefs.getBoolean(key, default)
is String -> prefs.getString(key, default)
else -> throw IllegalArgumentException("Unsupported type.")
} as T
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
putPreference(findProperName(property), value)
}
private fun putPreference(key: String, value: T){
with(prefs.edit()){
when(value){
is Long -> putLong(key, value)
is Int -> putInt(key, value)
is Boolean -> putBoolean(key, value)
is String -> putString(key, value)
else -> throw IllegalArgumentException("Unsupported type.")
}
}.apply()
}
}
// 再进一步封装,kotLint支持具体化的泛型
inline fun <reified T> TreeNode.findParentOfType(): T? {
var p = parent
while (p != null && p !is T) {
p = p.parent
}
return p as T?
}
//只有函数被inline修饰,同时泛型类型被reified修饰才能这样写,这样泛型类型和基本类型使用一样基于泛型的这种特性在此封装
inline fun <reified F, T> .pre(default: T) = Preference(AppContext, "", default, R::class.jvmName)
//使用
var name by {Preference(")}
// 这样对name赋值就会存储到sp中 获取也是从sp中获取
```
上面通过扩展又引出另外一个知识点委托
1.类型委托 一个类实现一个借口但并不实现方法通过 by 的形式接收的对象是一个接口的实现类如
```
interface Base {
fun printMessage()
fun printMessageLine()
}
class BaseImpl(val x: Int) : Base {
override fun printMessage() { print(x) }
override fun printMessageLine() { println(x) }
}
class Derived(b: Base) : Base by b {
override fun printMessage() { print("abc") }
}
fun main() {
val b = BaseImpl(10)
Derived(b).printMessage()
Derived(b).printMessageLine()
}
interface Base {
val message: String
fun print()
}
class BaseImpl(val x: Int) : Base {
override val message = "BaseImpl: x = $x"
override fun print() { println(message) }
}
class Derived(b: Base) : Base by b {
// 在 b 的 `print` 实现中不会访问到这个属性
override val message = "Message of Derived"
}
fun main() {
val b = BaseImpl(10)
val derived = Derived(b)
derived.print()
println(derived.message)
}
//
通道
组合挂起函数
协程上下文与调度器
异常处理
Select 表达式
共享的可变状态与并发
完整 Kotlin 参考(PDF)
完整 Kotlin 参考(字大PDF)
完整 Kotlin 参考(ePUB)
完整 Kotlin 参考(Mobi)
编辑本页
委托
属性委托
属性委托在单独一页中讲:属性委托。
由委托实现
委托模式已经证明是实现继承的一个很好的替代方式, 而 Kotlin 可以零样板代码地原生支持它。 Derived 类可以通过将其所有公有成员都委托给指定对象来实现一个接口 Base:
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
class Derived(b: Base) : Base by b
fun main() {
val b = BaseImpl(10)
Derived(b).print()
}
Target platform: JVMRunning on kotlin v. 1.3.11
Derived 的超类型列表中的 by-子句表示 b 将会在 Derived 中内部存储, 并且编译器将生成转发给 b 的所有 Base 的方法。
覆盖由委托实现的接口成员
覆盖符合预期:编译器会使用 override 覆盖的实现而不是委托对象中的。如果将 override fun printMessage() { print("abc") } 添加到 Derived,那么当调用 printMessage 时程序会输出“abc”而不是“10”:
interface Base {
fun printMessage()
fun printMessageLine()
}
class BaseImpl(val x: Int) : Base {
override fun printMessage() { print(x) }
override fun printMessageLine() { println(x) }
}
class Derived(b: Base) : Base by b {
override fun printMessage() { print("abc") }
}
fun main() {
val b = BaseImpl(10)
Derived(b).printMessage()
Derived(b).printMessageLine()
}
//但请注意,以这种方式重写的成员不会在委托对象的成员中调用 ,委托对象的成员只能访问其自身对接口成员实现:
```
2.属性委托
```
val 的属性 by lazy val lazyValue: String by lazy { println("computed!") "Hello"}
//lazy里面的代码之在当前对象的当前属性第一次被赋值的时候执行
class Example {
var p: String by Delegate()
}
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
// 上面的例子也讲过了 把属性委托给一个对象但该对象要实现getValue和setValue方法
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("<no name>") {
prop, old, new ->
println("$old -> $new")
}
}
fun main() {
val user = User()
user.name = "first"
user.name = "second"
}
//如果你想能够截获一个赋值并“否决”它,就使用 vetoable() 取代 observable()。 在属性被赋新值生效之前会调用传递给 vetoable 的处理程序
```
通过扩展再讲解下 函数的科理化,偏函数, 方法组合等
##Kotlin和java的交互
实际常用简单的介绍
Kotlin中的companion 的属性或方法分别用@JvmField和@JvmStatic修饰方便java直接通过类名的形式调用
Kotlin中的具名参数 加上@JvmOverloads方便java以重载的方式调用
包级函数也是通过类名的形式调用
Kotlin调用java如对对用的属性添加get set方法 如是kotlin支持结构化的声明的方式调用java的对象就对java对象用component1修饰
##协程
协程是什么东西,不知道含义也不知到怎么用,官方解释是简易版的线程很朦胧 不少用户使用完协程也是含糊 接下来通过例子讲解
需求复制一个文件夹的全部文件(大概六七百兆)到另一个文件中
用协程和线程分别执行相同的操作比较耗时
```
/**
* 获取执行线程的名称 并判断是否是主线程
*/
fun myLog(tag: Any) {
val name = Thread.currentThread().name
println("$tag:$name 是否主线程:${name == mainThread}")
}
// 获取主线程名称
val mainThread by lazy {
println("属性初始化")
Thread.currentThread().name
}
/**
*开启一个线程
* 因为线程执行的随机性只能通过 回调的形式获取执行结果
* 这种形式可阅读性不是好
*/
fun runTask(time_: Long, taslCount: Int, block: () -> Unit, endListener: (Long, Int) -> Unit) {
Thread {
block()
myLog("多线程name:")
endListener(time_, taslCount)
}.start()
}
// 线程执行完的回调
val verifyTask: (Long, Int) -> Unit = { it, taslCount ->
counter_.addAndGet(1)
println("短行程${counter_.get()}")
if (counter_.get() == taslCount) {
println("多m线程执行完成:${System.currentTimeMillis() - it}")
}
}
/**
* 开启两个线程执行复制文件的任务
* 2 表示开启的线程数目
* 大致看很不直观把
*/
runTask(currentTimeMillis, 2,
{
File("$rootfileName/ta").copyRecursively(File("$rootfileName/target_a"))
}, verifyTask)
runTask(currentTimeMillis, 2,
{
File("$rootfileName/tb").copyRecursively(File("$rootfileName/target_b"))
}, verifyTask)
//File.copyRecursively 方法是kotlin扩展的文件递归复制文件
```
协程方式实现
```
public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T
```
runBlocking是一个包级函数可以在程序任意地方直接调
第一个参数有默认实现先不管它 第二个参数是一个用suspend修饰的 lambda表达式表达式 表达式值也是次函数的返回值
suspend修饰函数被称为挂起函数 并可以操作协程的任何方法
我们调用就传一个表达式 这样就开启了一个协程很简单
```
fun main() {
myLog("-1")
runBlocking {
myLog("heolloword")
}
}
// 通过日志输出我们知道 协程中执行的表达式执行在主线程中
```
接下来我们让协程中的代码执行在子线程中
```
//子线程执行
fun runTask2(block: () -> Unit) {
Thread {
block()
}.start()
fun main() {
runTask2 {
runBlocking {
myLog("子线程执行协程")
}
}
}
// 通过日志我们知道被协程执行的代码
runTask2 {
val start_ = System.currentTimeMillis()
var start = 0L
var end = 0L
runBlocking {
myLog("线程1")
start = System.currentTimeMillis()
val j1 = launch {
myLog("协程1")
File("$rootfileName/t1").copyRecursively(File("$rootfileName/target1"))
}
val j2 = launch {
myLog("协程2")
File("$rootfileName/t2").copyRecursively(File("$rootfileName/target2"))
}
j1.join()
j2.join()
end = System.currentTimeMillis()
println("协程执行时间:${end - start}")
}
}//这是用协程执行和线程同样的任务 很直观也较简单
```
通过比较比较一个子线程中开启两个协程执行任务和两个子线程执行同样的任务发现线程的方式较快
大家有什么感想
下一个例子在两个子线程中开启了两个协程和两个子线程执行同样的任务
```
runTask2 {
val start_ = System.currentTimeMillis()
var start = 0L
var end = 0L
runBlocking {
myLog("线程1")
start = System.currentTimeMillis()
val j1 = launch(Dispatchers.IO) {
myLog("协程1")
File("$rootfileName/t1").copyRecursively(File("$rootfileName/target1"))
}
val j2 = launch(Dispatchers.IO) {
myLog("协程2")
File("$rootfileName/t2").copyRecursively(File("$rootfileName/target2"))
}
j1.join()
j2.join()
end = System.currentTimeMillis()
println("协程执行时间:${end - start}")
}
```
通过例子我们判断协程的方式比线程的方式快一点点 通过这个例子大家有什么想法
```
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
```
我们又看到一种协程的写法
launch 的方式注意 launch只能执行在某一个协程中
上面的例子我们通过 Dispatchers.IO 来执行协程中的代码执行的线程
有几个实现类
1.Dispatchers.IO 指定协程中的代码执行在线程池中
2.Dispatchers.Unconfined 也是一个线程池大家自己输出看看其执行的线程
3.Dispatchers.Default 默认
4.Dispatchers.Main 是Android特有指定运行在Android的UI线程 要使用它
除了引入官方指定库
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.0'
还要引入下面这个
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.0"
5.newSingleThreadContext("MyOwnThread") 一个单例的线程池 字符串参数为线程的名称
大家可以看它们的实现方式自己声明一个线程池
我们又看到 Job.join() Job为launch协程的返回值
在一个协程中开启的子协程的执行时随机的我们可以通过Job.join()等待当前协程执行完成
下面再介绍一种开启协程的方式
async的方式使用方式和launch一样不过它的返回值为Deferred<T> Deferred<T>.await()泛型的值也是 协程执行任务的返回值 例子演示
```
suspend fun doSomethingUsefulOne(): Int {
delay(1000L) // pretend we are doing something useful here
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(100L) // pretend we are doing something useful here, too
return 29
}
fun main() = runBlocking<Unit> {
//sampleStart
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
// some computation
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
//sampleEnd
}
```
协程的取消
协程的取消和java的线程取消很类似 我们知道线程在sleep或休眠时此时才有一定几率被取消,协程类似在delay是(协程休眠)执行Job.cancel取消 同时我们在任务执行结束的时候 在协程中用isActive判断当前协程是否被执行取消操作,如果被执行了取消操作 下面的操作就不执行了和java线程的添加标记判断很类似
```
fun main() = runBlocking {
//sampleStart
val job = launch {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancel() // cancels the job
job.join() // waits for job's completion
println("main: Now I can quit.")
//sampleEnd
}
```
在Android中使用协程 如在Activitydestroy的时候 要取消协程防止内存泄露 例子如下
```
import kotlin.coroutines.*
import kotlinx.coroutines.*
class Activity : CoroutineScope {
lateinit var job: Job
fun create() {
job = Job()
}
fun destroy() {
job.cancel()
}
// to be continued ...
// class Activity continues
override val coroutineContext: CoroutineContext
get() = Dispatchers.Default + job
// to be continued ...
// class Activity continues
fun doSomething() {
// launch ten coroutines for a demo, each working for a different time
repeat(10) { i ->
launch {
delay((i + 1) * 200L) // variable delay 200ms, 400ms, ... etc
println("Coroutine $i is done")
}
}
}
} // class Activity ends
fun main() = runBlocking<Unit> {
//sampleStart
val activity = Activity()
activity.create() // create an activity
activity.doSomething() // run test function
println("Launched coroutines")
delay(500L) // delay for half a second
println("Destroying activity!")
activity.destroy() // cancels all coroutines
delay(1000) // visually confirm that they don't work
//sampleEnd
}
```
看到 Dispatchers.Default + job这个操作 这是因为CoroutineContext实现了 如下方法
```
public operator fun plus(context: CoroutineContext): CoroutineContext =
if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
context.fold(this) { acc, element ->
val removed = acc.minusKey(element.key)
if (removed === EmptyCoroutineContext) element else {
// make sure interceptor is always last in the context (and thus is fast to get when present)
val interceptor = removed[ContinuationInterceptor]
if (interceptor == null) CombinedContext(removed, element) else {
val left = removed.minusKey(ContinuationInterceptor)
if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
CombinedContext(CombinedContext(left, element), interceptor)
}
}
}
```
想理解的自己看看 总之CoroutineContext通过再”+“Job的形式 通过一个便操作的job来取消协程,这个协程的另一种取消方式
下面再看一个较”古老”的协程的实现方式
```
fun _startCoutinue(block: suspend () -> Unit) {
block.startCoroutine(MyBaseContinuation()) // 只有suspend修饰的方法才有 startCoroutine的方法
}
suspend fun executeTask() = suspendCoroutine<String> { continuation ->
MyTask {
Thread.sleep(1000)
myLog("协程里面")
// SwingUtilities.invokeLater { 线程切换
continuation.resume("张三")
}.execute()
}
fun main() {
myLog(-1)
_startCoutinue {
val executeTask: String = executeTask()
myLog(1)
myLog(executeTask)
}
Thread.sleep(4000)
}
class MyTask(val block: () -> Unit) {
private val threadPool by lazy {
Executors.newCachedThreadPool()
}
fun execute() = threadPool.execute(block)
}
```
网上基本找不到这种方法开启协程的 官方实例也没有 但一些三方协程框架都能看到它的影子 下面介绍一种用法
没写
协程的数据安全同线程一样建议使用 如AtomicInteger原子性的操作
之前我们网络请求都是Retrofit结合Rxjava我们队Retrofit添加 retrofit2:adapter-rxjava适配 现在针对Retrofit的协程库retrofit2-kotlin-coroutines-adapter在Github也有1200多的star,使用协程结合Retrofit 会不会把网络请求写的太直观了,太简单那
今年协程10月kotlin1.3协程才成为正式成员 之前使用基本异常不会有日志输出调试很苦恼,现在已经解决
1.3中协程又添加了 channel(通道),select表达式这都是实验性的 不稳定正式库中api会改动,大家可以看kotlin官方文档说明不建议在正式项目中使用
协程讲完了,大家对协程是怎么理解?
网友评论