关于kotlin:一文带你吃透Kotlin类与对象

公众号「罕见猿诉」        原文链接 一文带你吃透Kotlin类与对象Kotlin是多范式通用编程语言,对面向对象编程(OOP)天然也提供了全方位的反对。通过先前一篇文章,学习了应用Kotlin进行根本面向对象编程的办法,本文将在前文根底之上持续深刻的学习面向对象编程的高级个性,以可能写出更加合乎OO的代码,并可能从容应对一些简单的OOP场景。 留神结构的程序在结构对象过程中,有三个中央能够对成员进行初始化:1)是在首构造方法(Primary constructor);2)是在申明成员的同时进行初始化,或者是在初始化代码块(init {...})中;3)是在主要构造方法(Secondary constructor)中。 要留神它们之间的区别和执行程序,首构造方法是最先执行的,但它不能运行代码,只能进行赋值;成员申明和初始化代码块(init {...})是首构造方法的一部分,因而要先于主要构造方法。主要构造方法是最初执行,并且主要构造方法肯定要委托到首构造方法。成员申明和初始化代码块之间则依赖于书写的程序,从上到下执行。 尽管编译器有它的规定来保障程序,但为了可读性和可维护性,咱们不应该齐全依赖编译器。这里倡议的形式是: 把类的最外围的成员放在首构造方法,如必须要依赖的参数,公开的成员,类型体系中的核心成员等,这些应该间接放在首构造方法中,并按重要的程序进行申明,这样也能不便进行依赖注入和测试Mock对象替换。公有成员应该在类中申明,并且在申明时进行初始化,如果无奈初始化就标记为提早初始(late init)。初始化代码块,应该做一些简单的初始化过程,或者成员之间有关联的初始化,或者做一些结构实现之后的操作。比方像在ViewModel中,结构之后,可能执行拉取数据,这就非常适合放在init {...}之中。不倡议应用主要构造方法,能够用给首构造方法的参数设置默认值的形式来进行成员参数上的重载。初始化代码块要放在所有成员申明之后,以保障执行程序。扩大浏览Classes和Properties。 妙用late init通常成员的初始化能够在申明时实现,比方像汇合或者一些简略的原始类型对象(Int, Float, String等)。但如果初始化过程比较复杂,或者初始值较难取得,这种状况下,就适宜标记为提早初始化late init,而后在适合的机会对成员进行初始化(比方零碎框架层的回调中,或者依赖注入等等)。应用一个未初始化的late init成员时会抛出一个叫做UninitializedPropertyAccessException的异样,能够在应用成员变量前用.isInitialized来判断成员变量是否初始化过: if (foo::bar.isInitialized) { println(foo.bar)}能够发现,对于Android 开发来说late init相对十分有用,因为对于零碎组件,咱们无奈在其构造方法中进行成员初始化,通常都是在第一个回调(如onCreate)中进行初始化,而这些变量全都应该用late init来标记。 另外,须要留神的是,成员是否有被初始化与成员是否是非法值(如null)并不是同一回事,初始化是第一次对成员对象赋值,赋的什么值(失常对象or null)虚拟机并不关怀,但只有有过赋值后变量就初始化过了。因而,用late init能够帮忙缩小null查看。 还须要留神的是,提早初始化late init与属性委托也不是同一回事,late init通常用于外部公有的成员变量,而属性委托通常用于对外开放的公开成员。 扩大浏览Properties。 函数式接口 接口(interfaces)是更高级别的形象,专一于行为的形象,用以实现对象间契约式行为交互。这一部分不打算具体解说interface的应用,而是重点关注函数式接口(function interface)。Kotlin中的接口与Java 8中的接口是一样的,不再全是形象办法了,能够有默认办法,也就是对接口的办法增加默认的实现,没有默认实现的办法就是形象办法了(Abstract method)。只有一个形象办法的接口称之为函数式接口(functional interface),或者单个形象办法接口(Single Abstract Method interface)。用fun interface来申明,如: fun interface IntPredict { fun accept(i: Int): Boolean}函数式接口的最大劣势在于,实现接口时能够简化到只用一个lambda,如: val isEnv = IntPredict { it % 2 == 0 }留神,只有用fun interface申明的含有一个形象办法的接口才是函数式接口,能力用lambda。对于一般接口,如果它仅含有一个形象办法,能够转化为函数式接口,比方原接口是酱紫的: interface Printer { fun print()}那么,能够间接定义一个fun interface Printer就能够了: ...

February 27, 2024 · 1 min · jiezi

关于kotlin:专家之路上的Flow高级秘籍

公众号「罕见猿诉」        原文链接 专家之路上的Flow高级秘籍 『君不见,黄河之水天上来,奔流到海不复回。』 学习与河流一样,一方面学无止境,又是逆水行舟,逆水行舟,因为其他人都在卷。前文一篇文章讲了Flow的根底,大多数状况下够用了,然而不能进行卷,因为你不卷,就会被他人卷。一旦波及到简单的利用场景,就须要用到一些高级的API。明天就来学习一下Flow的高级个性,当遇到问题时也能更从容的应答。 上下文切换Flow是基于协程的,是用协程来实现并发,后面也提到过像[flow {...}](),在上游生产数据,以及中游做变幻时,都是能够间接调用suspend,耗时甚至是阻塞的函数的。而终端操作符如[collect]()则是suspend的,调用者(也就是消费者)须要负责确保collect是在协程中调用。咱们还晓得Flow是是冷流,消费者终端才会触发上游生产者生产,所以对于flow {...}来说,它的上游和中游运行的上下文来自于终端调用者的上下文,这个叫做『上下文保留』(context preservation),咱们能够用一个 来验证一下: fun main() = runBlocking { // Should be main by default simple().collect { log("Got: $it") } // Collect in a specified context withContext(Dispatchers.Default) { simple().collect { log("Now got: $it") } }}private fun simple(): Flow<Int> = flow { log("Started the simple flow") for (i in 1..3) { delay(100) log("Producing $i") emit(i) }}输入如下: [main @coroutine#1] Started the simple flow[main @coroutine#1] Producing 1[main @coroutine#1] Got: 1[main @coroutine#1] Producing 2[main @coroutine#1] Got: 2[main @coroutine#1] Producing 3[main @coroutine#1] Got: 3[DefaultDispatcher-worker-1 @coroutine#1] Started the simple flow[DefaultDispatcher-worker-1 @coroutine#1] Producing 1[DefaultDispatcher-worker-1 @coroutine#1] Now got: 1[DefaultDispatcher-worker-1 @coroutine#1] Producing 2[DefaultDispatcher-worker-1 @coroutine#1] Now got: 2[DefaultDispatcher-worker-1 @coroutine#1] Producing 3[DefaultDispatcher-worker-1 @coroutine#1] Now got: 3从这个 能够分明的看到,Flow的context是来自于终端调用者的。 ...

February 20, 2024 · 3 min · jiezi

关于kotlin:DataBinding系列之基础使用

1.前言DataBinding, 又名数据绑定,是Android开发中十分重要的根底技术,它能够将UI组件和数据模型连接起来,使得在数据模型发生变化时,UI组件自动更新,从而节俭了大量的代码和工夫。DataBinding的原理是通过编写XML布局文件,在其中应用特定的标签和语法,将UI组件和数据模型连接起来。当布局文件被加载时,DataBinding会主动生成绑定代码,从而将UI组件和数据模型关联起来。 通过学习DataBinding基础知识,能够让你的代码速度翻倍,进步开发效率和代码品质。因而,如果你心愿在Android开发中取得更高的成功率和更快的倒退速度,那么请务必学习DataBinding技术,把握其基础知识,让本人成为一名高效率的Android开发者! 那么话不多说,让咱们间接直奔主题。接下来我将从实用性的角度,来逐个解说DataBinding的根底应用,文章开端会给出示例代码的链接地址,心愿能给你带来启发。 2.筹备工作2.1 启用1.DataBinding启用 android { dataBinding { enabled = true }}2.ViewBinding启用 android { buildFeatures { viewBinding true } }2.2 快捷方式在你的布局中找到最外层的布局,将光标放在如图地位。 Windows 请按快捷键 Alt + 回车Mac 请按快捷键 option + 回车 3.DataBinding绑定3.1 数据类型通常咱们在DataBinding中绑定的数据类型是ViewModel或者是AndroidViewModel,它俩都是生命周期可感知的,惟一的区别是AndroidViewModel能够获取到利用的上下文Application。 3.2 数据创立ViewModel的创立通常是通过ViewModelProvider进行创立和获取。 ViewModelProvider(this).get(Xxx::class.java)而在ViewModel中,通常应用MutableLiveData作为可变UI响应数据类型。相比拟LiveData而言,它凋谢了批改值的接口,上面是一个ViewModel的简略例子: class RecyclerViewRefreshState(application: Application) : AndroidViewModel(application) { val title = MutableLiveData("RecyclerView的刷新和加载更多演示") val isLoading = MutableLiveData(false) val sampleData = MutableLiveData<List<SimpleItem>>(arrayListOf()) val loadState = MutableLiveData(LoadState.DEFAULT) val layoutStatus = MutableLiveData(Status.DEFAULT)}当然了,如果你有一个LiveData会随着一个或多个LiveData的变动而变动,这个时候你可能就须要应用MediatorLiveData,即合并LiveData。 这里我简略利用MediatorLiveData实现一个组合的LiveData--CombinedLiveData。 open class CombinedLiveData<T>(vararg liveData: LiveData<*>, block: () -> T) : MediatorLiveData<T>() { init { value = block() liveData.forEach { addSource(it) { val newValue = block() if (value != newValue) { value = newValue } } } }}fun <R, T1, T2> combineLiveData( liveData1: LiveData<T1>, liveData2: LiveData<T2>, block: (T1?, T2?) -> R) = CombinedLiveData(liveData1, liveData2) { block(liveData1.value, liveData2.value) }这个时候,咱们就能够通过combineLiveData办法将两个LiveData组合起来,造成一个新的LiveData。上面我简略给出一个示例代码: ...

June 29, 2023 · 3 min · jiezi

关于kotlin:从-Java-到-Kotlin-介绍-Kotlin

B站视频:https://www.bilibili.com/vide...语法层面可空对象(和 C# 的 <Nullable>enabled</Nullable> 类似) Int 和 Int? 是两种不同的类型;String和 String? 也是两种不同的类型(前者是后者的子类型) var a: Int = 0;var b: Int? = 0;a = b; // ⇐ 不能将 Int? 赋值给 Intb = a; 不可变类型/可变类型 val 申明不可变变量,不可再赋值;var 申明可变变量,能再赋值。 var a = 0;val b = 1;a = b;b = a; // ⇐ 不能对 val 变量赋值字符串插值 val PI = 3.1415926val s1 = "PI is ${PI}"; // PI is 3.1415926val s2 = "PI is ${String.format("%.2f", PI)}"; // PI is 3.14字符串插值语法不反对设置格局(这点不如 C# 不便)对函数式编程的反对,一切都是表达式 ...

November 28, 2022 · 2 min · jiezi

关于kotlin:Kotlin协程Flow浅析

Kotlin协程中的Flow次要用于解决简单的异步数据,以一种”流“的形式,从上到下顺次解决,和RxJava的解决形式类型,然而比后者更加弱小。 Flow基本概念Flow中基本上有三个概念,即 发送方,解决中间层,接管方,能够类比水利发电站中的上游,发电站,上游的概念, 数据从上游开始发送”流淌“至两头站被”解决“了一下,又流淌到了上游。示例代码如下 flow { // 发送方、上游 emit(1) // 挂起函数,发送数据 emit(2) emit(3) emit(4) emit(5)}.filter { it > 2 } // 中转站,解决数据.map { it * 2 }.take(2).collect{ // 接管方,上游 println(it)}输入内容:68通过下面代码咱们能够看到,基于一种链式调用api的形式,流式的进行解决数据还是很棒的,接下来具体看一下下面的组成: flow{},是个高阶函数,次要用于创立一个新的Flow。在其Lambda函数外部应用了emit()挂起函数进行发送数据。filter{}、map{}、take{},属于两头解决层,也是两头数据处理的操作符,Flow最大的劣势,就是它的操作符跟汇合操作符高度一致。只有会用List、Sequence,那么就能够疾速上手 Flow 的操作符。collect{},上游接管方,也成为终止操作符,它的作用其实只有一个:终止Flow数据流,并且接管这些数据。其余创立Flow的形式还是flowOf()函数,示例代码如下 fun main() = runBlocking{aassssssssaaaaaaaas flowOf(1,2,3,4,5).filter { it > 2 } .map { it * 2 } .take(2) .collect{ println("flowof: $it") }}咱们在看一下list汇合的操作示例 listOf(1,2,3,4,5).filter { it > 2 } .map { it * 2 } .take(2) .forEach{ println("listof: $it") }通过以上比照发现,两者的基本操作简直统一,Kotlin也提供了两者互相转换的API,Flow.toList()、List.asFlow()这两个扩大函数,让数据在 List、Flow 之间来回转换,示例代码如下: ...

November 27, 2022 · 2 min · jiezi

关于kotlin:Kotlin协程Channel浅析

论断后行Kotlin协程中的Channel用于解决多个数据组合的流,随用随取,时刻筹备着,就像自来水一样,关上开关就有水了。 Channel应用示例fun main() = runBlocking { logX("开始") val channel = Channel<Int> { } launch { (1..3).forEach{ channel.send(it) logX("发送数据: $it") } // 敞开channel, 节俭资源 channel.close() } launch { for (i in channel){ logX("接收数据: $i") } } logX("完结")}示例代码 应用Channel创立了一组int类型的数据流,通过send发送数据,并通过for循环取出channel中的数据,最初channel是一种协程资源,应用完结后应该及时调用close办法敞开,免得节约不必要的资源。 Channel的源码public fun <E> Channel( capacity: Int = RENDEZVOUS, onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND, onUndeliveredElement: ((E) -> Unit)? = null): Channel<E> = when (capacity) { RENDEZVOUS -> {} CONFLATED -> {} UNLIMITED -> {} else -> {} }能够看到Channel的构造函数蕴含了三个参数,别离是capacity、onBufferOverflow、onUndeliveredElement. ...

November 23, 2022 · 2 min · jiezi

关于kotlin:Kotlin-Json-序列化

Kotlin能够间接调用 Java(也就能够间接应用 Jackson, Gson 等json库),不过我看到有 official 的序列化库,于是钻研了一下。 重点看了下多态的序列化,的确十分简洁,先上代码感受一下: @Serializablesealed interface Reply@Serializable@SerialName("text")class TextReply(val content: String): Reply@Serializable@SerialName("card")class CardReply(val content: String, val cardId: Long = 1L): Replyfun main() { val reply: Reply = CardReply("A card", 2L) // 序列化 val jsonString = Json.encodeToString(reply) println(jsonString) // 反序列化,隐式 val decodedReply: Reply = Json.decodeFromString(jsonString) println(decodedReply) // 显式 println( Json.decodeFromString<Reply>(jsonString) )}Output: {"type":"card","content":"A card","cardId":2}[email protected][email protected]CardReply & TextReply 实现了 Reply,能够看到序列化后果主动加上了类型标识"type": "card"。 再来看看泛型的序列化,同样非常简洁: fun main() { // 列表序列化 val jsonList = Json.encodeToString( listOf(CardReply("A card"), TextReply("A text")) ) println(jsonList) // 列表反序列化 val replies: List<Reply> = Json.decodeFromString(jsonList) println(replies)}Output: ...

October 27, 2022 · 1 min · jiezi

关于kotlin:微服务开发系列为什么选择-kotlin

我的项目中采纳的开发语言是 kotlin 。 开发人员能够依据本人须要采纳 java 语言开发,然而很显著的,我的项目中各种工具类曾经为了 kotlin 做出了很多批改,如果大量采纳 java 开发, 后果可能是比纯 java 开发的速度还要慢。 至于为什么要保持采纳 kotlin 开发,之前我有发表过一些文章来形容 kotlin 好用的中央,但还是在应用过程中总会时不时的被 kotlin 惊艳一下。 kotlin 除了齐全兼容 java 之外,还可能在架构中施展不少劣势,除了上面形容的中央,前面的介绍也会波及。 极少的代码量kotlin 将 java8 中的 stream 一系列的操作,都通过扩大个性,做了极大的简化。 例如 list 转 map users.associate { it.name to it},就省略了大堆繁琐的代码。 编写代码过程中的智能推断变量类型,可能帮忙你防止重复的推断类型,从而把本人绕晕。 对于传入匿名函数,可能防止手动创立函数对象。 对于空指针的 “?” 操作,可能极大简化对于空变量的判断解决。 等等,诸如此类的操作,如果可能齐全把握,就可能缩小数倍的代码量,学习过概率学的应该明确,更少的动作达到雷同的目标,天然产生问题的概率更小。 设计利器缩小代码量并不是 kotlin 的起点。 如果一个框架齐全由 kotlin 搭建,只利用其个性来督促个别开发者写出更好的业务代码就太节约了。 kotlin 还可能帮忙你补全框架设计中的一些遗憾。 异样抛出以框架设计中必不可少的业务异样抛出为例。 if (user.id.isNullOrEmpty()) { throw SomeException("user id cannot be empty")}这样的代码不可胜数,然而利用简略的扩大个性,就可能以新鲜并且正当的形式抛出异样。 fun Boolean.ifSome(message: String?) = throw SomeException(message)user.id.isNullOrEmpty().Boolean.ifSome("user id cannot be empty")这样除非在非凡状况下,否则框架中的任何中央都应用这种形式来抛出异样,即节俭代码,又领有更高的可读性。 ...

September 19, 2022 · 1 min · jiezi

关于kotlin:Kotlin协程解析系列上协程调度与挂起

vivo 互联网客户端团队- Ruan Wen本文是Kotlin协程解析系列文章的开篇,次要介绍Kotlin协程的创立、协程调度与协程挂起相干的内容 一、协程引入Kotlin 中引入 Coroutine(协程) 的概念,能够帮忙编写异步代码。 在应用和剖析协程前,首先要理解一下: 协程是什么? 为什么须要协程? 协程最为人称道的就是能够用看起来同步的形式写出异步的代码,极大进步了代码的可读性。在理论开发中最常见的异步操作莫过于网络申请。通常咱们须要通过各种回调的形式去解决网络申请,很容易就陷入到天堂回调中。 WalletHttp.target(VCoinTradeSubmitResult.class).setTag(tag) .setFullUrl(Constants.VCOIN_TRADE_SUBMIT_URL).setParams(params) .callback(new HttpCallback<VCoinTradeSubmitResult>() { @Override public void onSuccess(VCoinTradeSubmitResult vCoinTradeSubmitResult) { super.onSuccess(vCoinTradeSubmitResult); if (mView == null) { return; } //...... } }).post();上述示例是一个我的项目开发中常见的一个网络申请操作,通过接口回调的形式去获取网络申请后果。理论开发中也会常常遇到间断多个接口申请的状况,例如咱们我的项目中的集体核心页的逻辑就是先去异步获取。 本地缓存,获取失败的话就须要异步刷新一下账号token,而后网络申请相干集体核心的其余信息。这里简略举一个领取示例,进行领取时,可能要先去获取账号token,而后依赖该token再去做领取。 申请操作,依据领取返回数据再去查问领取后果,这种状况通过回调就可能演变为“天堂回调”。 //获取账号tokenWalletHttp.target(Account.class).setTag(tag) .setFullUrl(Constants.ACCOUNT_URL).setParams(params) .callback(new HttpCallback<Account>() { @Override public void onSuccess(Account account) { super.onSuccess(account); //依据账号token进行领取操作 WalletHttp.target(Pay.class).setFullUrl(Constants.PAY_URL).addToken(account.getToken()).callback(new HttpCallback<Pay>() { @Override public void onSuccess(Pay pay){ super.onSuccess(pay); //依据领取操作返回查问领取后果 WalletHttp.target(PayResult.class).setFullUrl(Constants.RESULT_URL).addResultCode(pay.getResultCode()).callback(new HttpCallback<PayResult>() { @Override public void onSuccess(PayResult result){ super.onSuccess(result); //...... } }).post(); } }).post(); } }).post();对于这种场景,kotlin协程“同步形式写出异步代码”的这个个性就能够很好的解决上述问题。若上述场景用kotlin 协程代码实现呢,可能就为: ...

August 16, 2022 · 21 min · jiezi

关于kotlin:Kotlin组件化-打造自己的AI语音助手

download:Kotlin+组件化 打造本人的AI语音助手Golang | 模块引入与疾速实现表格的读写业务介绍在很多管理系统下都有不少让后端进行表格进行操作的业务需要,本期就带大家了解一下Golang中如何使用模块引入的,以及讲解怎么疾速的使用excelize库,对表格进行读写创建的。注释配置模块引入环境咱们在期望在vscode终端中也可能使用模块引入,它是 Go 1.11后新版模块治理形式。go env -w GO111MODULE=auto复制代码其 GO111MODULE 可能传送: auto:在其外层且根目录里有 go.mod 文件时,则开启模块反对,否者无模块反对。 on:开启模块反对。 off:无模块反对。 而后,初始化这个我的项目,就会生成一个 go.mod 文件。go mod init excel-demo复制代码 go.mod 是Go 1.11版本引入的官网的包管理工具(之前为 gopath 来治理),它可能理解为前端开发中的 npm 的作用,次要是为理解决没有记录依赖包具体版本查阅艰巨的问题,也极大程度上便利了依赖包的治理。 引入excelize库excelize 是一个用于读写 Microsoft Excel™2007 及更高版本生成的电子表格文档(XLAM / XLSM / XLSX / XLTM / XLTX)的 Go 语言库,而且更新保护频繁且非常好用。引入excelizego get github.com/xuri/excelize/v2复制代码这里因为站点是国外的所以常常会因无法访问而超时。此时,不要慌,咱们换一个国内的代理就好了。go env -w GOPROXY=https://goproxy.cn复制代码创建表格package main import ( "fmt""github.com/xuri/excelize/v2") func createExcel(){ // 创建表格文件f := excelize.NewFile()// 在Sheet1设置A1项的值f.SetCellValue("Sheet1", "A1", "这是Sheet1的A1项")// 创建新的Sheet,命名为Sheet2selectIndex := f.NewSheet("Sheet2")// 在Sheet2设置B2项的值f.SetCellValue("Sheet2", "B2", "这是Sheet2的B2项")// 切换到Sheet2f.SetActiveSheet(selectIndex)// 保存文件if err := f.SaveAs("test.xlsx"); err != nil { fmt.Println(err)}} ...

July 23, 2022 · 2 min · jiezi

关于kotlin:从-Stream-到-Kotlin-再到-SPL

JAVA开发中常常会遇到不方便使用数据库,但又要进行结构化数据计算的场景。JAVA晚期没有提供相干类库,即便排序、分组这种根本计算也要硬写代码,开发效率很低。起初JAVA8推出了Stream库,凭借Lambda表达式、链式编程格调、汇合函数,才终于解决了结构化数据计算类库从无到有的问题。 Stream能够简化结构化数据的计算比方排序: Stream<Order> result=Orders.sorted((sAmount1,sAmount2)->Double.compare(sAmount1.Amount,sAmount2.Amount)).sorted((sClient1,sClient2)->CharSequence.compare(sClient2.Client,sClient1.Client));下面代码中的sorted是汇合函数,可不便地进行排序。"(参数)->函数体"的写法即Lambda表达式,能够简化匿名函数的定义。两个sorted函数连在一起用属于链式编程格调,能够使多步骤计算变得直观。 Stream计算能力还不够强依然以下面的排序为例,sorted函数只须要晓得排序字段和程序/逆序就够了,参考SQL的写法"…from Orders order by Client desc, Amount",但实际上还要额定输出排序字段的数据类型。程序/逆序用asc/desc(或+/-)等符号就能够简略示意了,但这里却要用compare函数。另外,理论要排序的字段程序和代码写进去的程序是相同的,有些反直觉。再比方分组汇总: Calendar cal=Calendar.getInstance();Map<Object, DoubleSummaryStatistics> c=Orders.collect(Collectors.groupingBy( r->{ cal.setTime(r.OrderDate); return cal.get(Calendar.YEAR)+"_"+r.SellerId; }, Collectors.summarizingDouble(r->{ return r.Amount; }) )); for(Object sellerid:c.keySet()){ DoubleSummaryStatistics r =c.get(sellerid); String year_sellerid[]=((String)sellerid).split("_"); System.out.println("group is (year):"+year_sellerid[0]+"\t (sellerid):"+year_sellerid[1]+"\t sum is:"+r.getSum()+"\t count is:"+r.getCount()); }下面代码中,所有呈现字段名的中央,都要先写上表名,即"表名.字段名",而不能像SQL那样省略表名。匿名函数语法简单,随着代码量的减少,复杂度迅速增长。两个匿名函数造成嵌套,代码更难解读。实现一个分组汇总性能要用多个函数和类,包含groupingBy、collect、Collectors、summarizingDouble、DoubleSummaryStatistics等,学习老本不低。分组汇总的后果是Map,而不是结构化数据类型,如果要持续计算,通常要定义新的结构化数据类型,并进行转换类型,处理过程很繁琐。两个分组字段在结构化数据计算中很常见,但函数grouping只反对一个分组变量,为了让一个变量代表两个字段,就要采取一些变通技巧,比方新建一个两字段的结构化数据类型,或者把两个字段用下划线拼起来,这让代码变得更加繁琐。 Stream计算能力有余,起因在于其根底语言JAVA是编译型语言,无奈提供业余的结构化数据对象,短少来自底层的无力反对。 JAVA是编译型语言,返回值的构造必须当时定义,遇到较多的两头步骤时,就要定义多个数据结构,这不仅让代码变得繁琐,还导致参数解决不灵便,要用一套简单的规定来实现匿名语法。解释性语言则人造反对动静构造,还能够不便地将参数表达式指定为值参数或函数参数,提供更简略的匿名函数。 在这种状况下,Kotlin应运而生。Kotlin是基于JAVA的古代开发语言,所谓古代,重点体现在对JAVA语法尤其是Stream的改良上,即Lambda表达式更加简洁,汇合函数更加丰盛。 Kotlin计算能力强于Stream比方排序: var resutl=Orders.sortedBy{it.Amount}.sortedByDescending{it.Client}下面代码毋庸指明排序字段的数据类型,毋庸用函数表白程序/逆序,间接援用it作为匿名函数的默认参数,而不是刻意定义,整体比Stream简短不少。 Kotlin改良并不大,计算能力依然有余依然以排序为例,Kotlin尽管提供了it这个默认参数,但实践上只有晓得字段名就够了,没必要带上表名(it)。排序函数只能对一个字段进行排序,不能动静接管多个字段。 再比方分组汇总: data class Grp(var OrderYear:Int,var SellerId:Int)data class Agg(var sumAmount: Double,var rowCount:Int)var result=Orders.groupingBy{Grp(it.OrderDate.year+1900,it.SellerId)} .fold(Agg(0.0,0),{ acc, elem -> Agg(acc.sumAmount + elem.Amount,acc.rowCount+1) }).toSortedMap(compareBy<Grp> { it. OrderYear}.thenBy { it. SellerId})result.forEach{println("group fields:${it.key.OrderYear}\t${it.key.SellerId}\t aggregate fields:${it.value.sumAmount}\t${it.value.rowCount}") }下面代码中,一个分组汇总的动作,须要用到多个函数,包含简单的嵌套函数。用到字段的中央要带上表名。分组汇总的后果不是结构化数据类型。要当时定义两头后果的数据结构。 ...

July 11, 2022 · 1 min · jiezi

关于kotlin:Android-Kotlin语言学习第一课基本知识

Android Kotlin语言学习第一课:基本知识一:Kotlin的类的定义(1)如果一个类容许被继承必须应用open关键字润饰(2)抽象类默认是open润饰抽象类简介 : 抽象类不能被实例化 , 在 class 关键字前应用 abstract 润饰 ;抽象类 , 默认应用 open 关键字润饰 , 能够间接继承 ;形象办法 , 默认应用 open 关键字润饰 , 能够间接 override 重写 ; 抽象类总结 :① 申明 : 抽象类中应用 abstract 申明 ;② 成员 : 抽象类中既能够定义失常属性和办法 , 又能够定义形象的属性和办法 ;③ 继承 : 抽象类能够继承抽象类 , 抽象类也能够继承失常类 , 失常类能够继承抽象类 ;④ 重写 : 抽象类中能够应用形象办法重写失常办法 , 也能够进行失常的办法重写 ;⑤ 特色 : 形象办法只能定义在抽象类中 , 失常类中不能有形象办法 ; /** * @author zhiqiangRuan * @ClassName * @Date 2022/6/8 */abstract class BaseActivity : AppCompatActivity() { /** * kotlin 容许一个类继承另一个类 * kotlin 所有的类都继承自Any类(Any 不是 java.lang.Object) * Any类是所有类的超类,对于没有超类型申明的类是默认超类 * Kotlin规定一个类能够给继承,必须应用open关键字润饰 * */ /** * 抽象类:关键字为abstract *形象函数: abstract fun initView() *形象属性:abstract var name:String */ /** *变量能够定义为可变(var)和不可变(val) * 常量定义:val相当于被final 润饰 var相当于可变非final润饰 *等价于Java:public static final String TAG=BaseActivity.class.getSimpleName()*/ //定义标记 val TAG: String = this.javaClass.simpleName //初始化布局View abstract fun initView() //初始化数据 abstract fun initData() //初始化获取布局id,带返回值的形象办法 abstract fun getLayoutId(): Int /** * 语法定义 * fun 办法名 (参数名 :参数类型):返回值类型{ * * return 返回值 * * } * * 无返回值能够应用Unit 代替返回值类型 ?代表可空 * Kotlin 是null平安的语言 Byte ,Short,Int ,Long型的变量都是不能承受null值,如果要存储null值须要应用Byte?,Short?,Int?,Long? * * override fun onCreate(savedInstanceState: Bundle?) :Unit{ * * } **/ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(getLayoutId()) initView() initData() } override fun onDestroy() { super.onDestroy() }}二:变量和函数val(value的简写)用来申明一个不可变的变量,这种变量在初始赋值之后就再也不能从新赋值,对应Java中的final变量;var(variable的简写)用来申明一个可变的变量,这种变量在初始赋值会后依然能够再被从新赋值,对应Java中的非final变量; ...

June 9, 2022 · 4 min · jiezi

关于kotlin:KotlinKCP的应用第一篇

前言KCP的利用打算分两篇,本文是第一篇 本文次要记录从发现问题到应用KCP解决问题的折腾过程,下一篇记录KCP的利用 背景Kotlin 号称百分百兼容 Java ,所以在 Kotlin 中一些修饰符,比方 internal ,在编译后放在纯 Java 的我的项目中应用(没有Kotlin环境),Java 依然能够拜访被 internal 润饰的类、办法、字段等 在应用 Kotlin 开发过程中须要对外提供 SDK 包,在 SDK 中有一些 API 不想被内部调用,并且曾经增加了 internal 润饰,然而受限于上诉问题且第三方应用 SDK 的环境不可控(不能要求第三方必须应用Kotlin) 带着问题Google一番,查到以下几个解决方案: 应用 JvmName 注解设置一个不合乎 Java 命名规定的标识符1应用 在 Kotlin 中把一个不非法的标识符强行合法化1应用 JvmSynthetic 注解2以上计划能够满足大部分需要,然而以上计划都不满足暗藏构造方法,可能会想什么情景下须要暗藏构造方法,例如: class Builder(internal val a: Int, internal val b: Int) { /** * non-public constructor for java */ internal constructor() : this(-1, -1)}为此我还提了个Issue3,冀望官网把 JvmSynthetic 的作用域扩大到构造方法,不过官网如同没有打算实现 为解决暗藏构造方法,能够把构造方法私有化,对外裸露动态工厂办法: class Builder private constructor (internal val a: Int, internal val b: Int) { /** * non-public constructor for java */ private constructor() : this(-1, -1) companion object { @JvmStatic fun newBuilder(a: Int, b: Int) = Builder(a, b) }}解决方案说完了,大家散了吧,散了吧~ ...

May 10, 2022 · 5 min · jiezi

关于kotlin:Kotlin-高阶函数介绍

什么是高阶函数将函数作为参数或者返回值的,称高阶函数。 定义高阶函数action是一个高阶函数,(Int) -> Int示意是函数的类型,(Int)示意函数入参类型为Int,前面的Int示意函数返回类型。 private fun init() { val action: ((Int) -> Int) = {//函数进行加100运算 it + 100 } }函数作为参数以下代码,init函数调用doSomething函数,将Int类型 0 以及action函数传入doSomething。doSomething函数先对入参0进行加200运算,而后调用高阶函数action(进行加100运算),最初打印后果actionResult。 是不是有点策略设计模式的滋味?是的,齐全能够用它来实现策略设计模式。 private fun init() { val action: ((Int) -> Int) = {//函数进行加100运算 it + 100 } doSomething(0, action)//将0以及action传给doSomething } private fun doSomething(d: Int, action: (Int) -> Int) { val data = d + 200//先对d进行加200运算 val actionResult = action(data)//将加200后的后果传给action,后果保留在actionResult中 Log.e("test", "actionResult=${actionResult}")//打印后果 }函数作为返回值以下代码,依据不同的类型type,返回对应的action,接着对参数0进行运算,最初打印后果。 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val action = init("计划1") //获取到对应的action,传入参数0进行运算 val result = action?.invoke(0) //打印后果 Log.e("test", "result=${result}") } private fun init(type: String): ((Int) -> Int)? { val action1: ((Int) -> Int) = {//加100运算 it + 100 } val action2: ((Int) -> Int) = {//加200运算 it + 200 } //依据类型type,返回对应的action return when (type) { "计划1" -> action1 "计划2" -> action2 else -> null } }lamdba表达式也是高阶函数,只不过函数是匿名的。以下代码性能跟上述一样,只不过用amdba表达式代替了定义action函数。 ...

April 21, 2022 · 1 min · jiezi

关于kotlin:Kotlin扩展函数与扩展属性

扩大函数Kotlin扩大函数可能对⼀个类扩大新性能⽽⽆需继承该类或者使⽤像装璜者这样的设计模式。利用他能够缩小很多代码,晋升咱们的开发效率。 扩大函数的申明申明⼀个函数,而后让被扩大的类型作为函数的前缀就能够了,比方上面对TextView的扩大 fun TextView.setDrawableLeft(drawableId: Int?) { val drawable = ContextCompat.getDrawable(this.context, drawableId) if (drawable == null) { setCompoundDrawables( null, compoundDrawables[1], compoundDrawables[2], compoundDrawables[3] ) } else { drawable.apply { setBounds(0, 0, minimumWidth, minimumHeight) setCompoundDrawables( drawable, compoundDrawables[1], compoundDrawables[2], compoundDrawables[3] ) } }}而后咱们就能够应用TextView的实例调用setDrawableLeft()办法。尽管看上去如同咱们为TextView新增了一个办法,然而其实扩大是动态的,他并没有为扩大类型中插入新的办法 那么他是如何实现的呢,咱们反编译以下class文件,查看对应的java文件就明确了 扩大函数原理public static final void setDrawableLeft(@NotNull TextView $this$setDrawableLeft, @Nullable Integer drawableId){ //省略其余代码}原来是给咱们生成了一个对应的java静态方法,第一个参数就是接受者类型的对象,所以在办法外部能够拜访这个类中的成员。 既然是生成了java代码,那么这个办法就能够被其余java代码拜访 TextViewKt.setDrawableLeft(textview,drawableId)扩大属性和扩大函数相似,这种扩大也是动态的,并没有给原来的类增加新的属性 扩大属性的申明val StringBuilder.lastChar: String get() { if (this.isEmpty()) { return "" } return this.substring(length - 1) }申明形式和扩大函数也相似,须要一个接受者类型,在作用域类,this就代表这个接受者对象,因为这里lastChar咱们定义的是val,所以只用定义get()办法就能够了,如果是var类型,还须要定义set()办法,扩大字段的应用和失常字段一样。 ...

April 16, 2022 · 1 min · jiezi

关于kotlin:Kotlin对象比较注意点

背景现有一个StateFlow及其监听private val stateFlow = MutableStateFlow(kotlin.Pair<String, ArrayList<String>>("abc", ArrayList()))GlobalScope.launch { stateFlow.collect { // do something }}更新ArrayList并尝试emitGlobalScope.launch { stateFlow.value.second.add("test") stateFlow.emit(stateFlow.value)}实际上,collect并不会被调用 起因MutableStateFlow真正的实现者是StateFlowImpl, emit办法代码如下: override suspend fun emit(value: T) { this.value = value}查看value的set办法: public override var value: T get() = NULL.unbox(_state.value) set(value) { updateState(null, value ?: NULL) }private fun updateState(expectedState: Any?, newState: Any): Boolean { var curSequence = 0 var curSlots: Array<StateFlowSlot?>? = this.slots // benign race, we will not use it synchronized(this) { val oldState = _state.value if (expectedState != null && oldState != expectedState) return false // CAS support if (oldState == newState) return true // Don't do anything if value is not changing, but CAS -> true _state.value = newState curSequence = sequence ... 省略局部代码 }}其中"if (oldState == newState) return true"因emit前后是同一个对象,导致条件为true,那么,如果emit前后不是同一个对象,即可解决这个问题? ...

April 14, 2022 · 2 min · jiezi

关于kotlin:Kotlin语言基础入门到熟悉Lambda-表达式

什么是 Lambda 表达式?Lambda 表达式,其实就是匿名函数。而函数其实就是性能(function),匿名函数,就是匿名的性能代码了。在 Kotlin 当中,函数也是作为类型的一种呈现的,只管在以后的版本中,函数类型的灵活性还不如 Python 这样的语言,不过它也是能够被赋值和传递的,这次要就体现在 Lambda 表达式上。 咱们先来看一个 Lambda 表达式的例子: fun main(args: Array<String>) { val lambda = { left: Int, right: Int -> left + right } println(lambda(2, 3)) } 大家能够看到咱们定义了一个变量 lambda,赋值为一个 Lambda 表达式。Lambda 表达式用一对大括号括起来,前面先顺次写下参数及其类型,如果没有就不写,接着写下 -> ,这表明前面的是函数体了,函数体的最初一句的表达式后果就是 Lambda 表达式的返回值,比方这里的返回值就是参数求和的后果。 前面咱们用 () 的模式调用这个 Lambda 表达式,其实这个 () 对应的是 invoke 办法,换句话说,咱们在这里也能够这么写: println(lambda.invoke(2,3)) 这两种调用的写法是齐全等价的。 毫无疑问,这段代码的输入应该是 5。 简化 Lambda 表达式咱们再来看个例子: fun main(args: Array<String>) { args.forEach { if(it == "q") return println(it) } println("The End") } args 是一个数组,咱们曾经见过 for 循环迭代数组的例子,不过咱们其实有更现代化的伎俩来迭代一个数组,比方下面这个例子。这没什么可怕的,一旦撕下它的面具,你就会发现你早就意识它了: ...

February 18, 2022 · 4 min · jiezi

关于kotlin:官方回答来了Java-和-Kotlin-哪个是未来你想知道的都在这里

前言这几年,Google 大力发展基于 Kotlin 的 Androidx 库、Jetpack 库、Compose 库,很多新个性都是为 Kotlin 优化的。能够说,不懂 kotlin,今后在 Android 开发畛域规范库的倒退上将很受妨碍,Android 开发由 Java 转 Kotlin 早已势不可挡。 做 Android 的应该没有几个不晓得扔物线朱凯的,他是中国惟一的 Android GDE 和 Kotlin GDE(谷歌官网认证开发专家,这样的人在全世界仅 18 位),很多人的自定义 View 和 Kotlin 都是通过他的技术视频带进门的。凯哥的技术视频既轻松搞笑又浅显易懂,同时又有惊人的技术深度。 分享一下凯哥采访 Kotlin 和 Android 官网的视频! 凯哥跟 Android 和 Kotlin 官网聊了什么?灵魂拷问之一:Kotlin会被Android摈弃吗? 这个二货,居然当面问出如此犀利的问题!我也是醉了…… 那么,Kotlin 到底会被 Android 摈弃吗?Android 官网对此的回复是: 绝!对!不!会! 并且说道,Google 本人在外部也有 55 个 app 都曾经在用 Kotlin 开发了。而且这位 Google 大佬还跟凯哥走漏,Android 团队还会参加 Kotlin 的开发与决策过程。这么看来,Kotlin 真的是很平安了。 但……二货就是二货,他问了更狠的凯哥的问题没有最犀利,只有更犀利: 灵魂拷问之二:Java会被Android摈弃吗? 如此直白提问,除了光头扔物线,大略也没谁了吧…… 顺便也说一下 Android 官网对于这个问题的答复:Java 也是不会摈弃的,因为 Android 的零碎源码就是 Java 写的,没必要摈弃。但 Google 大佬和凯哥也都在视频里倡议,不要把「Android 抛不摈弃 Java 作为用不必 Kotlin 的判断根据」,因为 Kotlin 「是将来」。 ...

January 19, 2022 · 1 min · jiezi

关于kotlin:Kotlin之Flow实战

Flow异步流 意识 个性构建器和上下文启动勾销与勾销检测缓冲操作符 过渡操作符末端操作符组合展平异样 异样解决实现如何示意多个值?挂起函数能够异步的返回单个值,然而如何异步返回多个计算好的值呢? 计划 汇合序列挂起函数Flow用汇合,返回多个值,但不是异步的。 private fun createList() = listOf<Int>(1, 2, 3)@Testfun test_list() { createList().forEach { println(it) }}用序列,返回一个整数序列 private fun createSequence(): Sequence<Int> { return sequence { for (i in 1..3) { Thread.sleep(1000) // 伪装在计算,此处是阻塞,不能做其余事件了 // delay(1000) 这里不能用挂起函数 yield(i) } }}@Testfun test_sequence() { createSequence().forEach { println(it) }}看下源码 public fun <T> sequence(@BuilderInference block: suspend SequenceScope<T>.() -> Unit): Sequence<T> = Sequence { iterator(block) }传入的是一个SequenceScope的扩大函数。 @RestrictsSuspension@SinceKotlin("1.3")public abstract class SequenceScope<in T> internal constructor()而RestrictsSuspension限度只能应用外面提供的已有的挂起函数,如yield,yieldAll等。createSequence返回了多个值,然而也是同步的。 ...

January 5, 2022 · 8 min · jiezi

关于kotlin:Kotlin实战消除静态工具类顶层函数和属性

1.引入咱们都晓得, Java 作为 门面向对象的语言,须要所有的代码都写作类的函数。大多数状况下,这种形式还能行得通。但事实上,简直所有的大型项目,最终都有很多的代码并不能归属到任何 个类中。有时一个操作对应两个不同的类的对象,而且重要性相差无几。有时存在 个根本的对象,但你不想通过实例函数来增加操作,让它的 API 持续收缩。后果就是,最终这些类将不蕴含任何的状态或者实例函数,而是仅仅作为一堆动态函数的容器。在 JDK 中, 最适宜的例子应该就是 Collections了。看看你本人的代码,是不是也有一些类自身就以Util作为后缀命名。 在kotlin中,基本就不须要去创立这些无意义的类, 相同,能够把这些函数接放到代码文件的顶层,不必从属于任何的类。这些放在文件顶层的函数仍然是包内的成员,如果你须要从包外拜访它,则须要 import 但不再须要额定包一层。 2.顶层函数见名知意,原来在Java中,类处于顶层,类蕴含属性和办法,在Kotlin中,函数站在了类的地位,咱们能够间接把函数放在代码文件的顶层,让它不从属于任何类。就像上面这样,咱们在一个Test.kt文件中写入如下的Kotlin代码。 package com.smartcentury.agriculturalmarket.utilsfun getKotlin():String{ return "Kotlin"}当初咱们看一下如何在其余包中援用它: import com.smartcentury.agriculturalmarket.utils.getKotlingetKotlin()咱们只须要导入包,而后间接调用就能够了。 然而咱们可能会有个疑难,如果咱们想要在Java中调用这个办法应该如何调用呢? 在Java中调用Kotlin顶层函数。 要想晓得如何在Java中调用顶层函数其实很简略,咱们只有将Kotlin反编译一下就能够了。上面介绍下如何反编译Kotlin代码 。 第一步:在IDE中关上你须要查看反编译的代码文件,而后关上顶部的"Tools",抉择"Kotlin",再抉择"Show Kotlin ByteCode" 第二步:点击右侧“Decompile” 咱们会失去另外一个文件 通过以上的代码能够总结出两点内容: 顶层文件会反编译成一个容器类。(类名个别默认就是顶层文件名+"Kt"后缀,留神容器类名能够自定义)顶层函数会反编译成一个static动态函数,如代码中的getKotlin函数当初咱们应该晓得如何在java代码中如何调用了吧。 TestKt.getKotlin();可能有时候你感觉Kotlin为你主动生成的这个类名不好,那你能够通过@file:JvmName注解来自定义类名,就像上面这样。 @file:JvmName("MyKotlin")package com.smartcentury.agriculturalmarket.utils/** * @Author: Simon * @CreateDate: 2019/5/16 16:04 * @Description: */fun getKotlin():String{ return "Kotlin"}而且要留神,这个注解必须放在文件的结尾,包名的后面。 于是咱们在Java文件中能够这样调用 MyKotlin.getKotlin();3.顶层属性理解了顶层函数,上面再看看顶层属性。顶层属性也就是把属性间接放在文件顶层,不依附于类。咱们能够在顶层定义的属性包含var变量和val常量,就像上面这样。 @file:JvmName("MyKotlin")package com.smartcentury.agriculturalmarket.utils/** * @Author: Simon * @CreateDate: 2019/5/16 16:04 * @Description: */val name:String="kotlin"var type:String="language"用法和顶层办法一样,这里就不赘述了。 ...

December 3, 2021 · 1 min · jiezi

关于kotlin:Room-Kotlin-符号的处理

△ 图片来自 Unsplash 由 Marc Reichelt 提供 Jetpack Room 库在 SQLite 上提供了一个形象层,可能在没有任何样板代码的状况下,提供编译时验证 SQL 查问的能力。它通过解决代码注解和生成 Java 源代码的形式,实现上述行为。 注解处理器十分弱小,但它们会减少构建工夫。这对于用 Java 写的代码来说通常是能够承受的,但对于 Kotlin 而言,编译工夫耗费会非常明显,这是因为 Kotlin 没有一个内置的注解解决管道。相同,它通过 Kotlin 代码生成了存根 Java 代码来反对注解处理器,而后将其输送到 Java 编译器中进行解决。 因为并不是所有 Kotlin 源代码中的内容都能用 Java 示意,因而有些信息会在这种转换中失落。同样,Kotlin 是一种多平台语言,但 KAPT 只在面向 Java 字节码的状况下失效。 意识 Kotlin 符号解决随着注解处理器在 Android 上的宽泛应用,KAPT 成为了编译时的性能瓶颈。为了解决这个问题,Google Kotlin 编译器团队开始钻研一个代替计划,来为 Kotlin 提供一流的注解解决反对。当这个我的项目诞生之初,咱们十分冲动,因为它将帮忙 Room 更好地反对 Kotlin。从 Room 2.4 开始,它对 KSP 有了实验性的反对,咱们发现编译速度进步了 2 倍,特地是在全量编译的状况下。 本文内容重点不在注解的解决、Room 或者 KSP。而在于重点介绍咱们在为 Room 增加 KSP 反对时所面临的挑战和所做的衡量。为了了解本文您并不需要理解 Room 或者 KSP,但必须相熟注解解决。 ...

November 4, 2021 · 4 min · jiezi

关于kotlin:Android-app-中这样用flow更方便巧用flow实现polling

背景在app开发过程中,实现polling逻辑也是很常见的。当然在挪动端利用应用polling解决会影响利用的性能。比方polling解决减少了网络申请的次数,服务端压力减少。polling解决也耗费了更多的网络流量。然而利用polling的场景还是有的。有时是否抉择polling要思考很多综合的因素,比方咱们能够应用长连贯代替polling,然而长连贯在服务端和客户端的开发成本绝对要更高些,如果polling只是实现相似的跟帖等性能,咱们齐全能够应用polling实现,而不是抉择代价更高的长连贯计划。上面会分应用flow和不应用flow两种形式实现polling并比照两种形式的优缺点。 不应用flow咱们应用线程解决polling申请,首先咱们定义了一个polling thread。 class PollingThread: Thread() { override fun run() { var successBlock : (PollingData)->Unit = { Log.d("PollingThread","successBlock $it") } var failBlock:(Exception)->Unit ={ Log.d("PollingThread","failBlock $it") } while (isInterrupted) { pollingApi.call(successBlock, failBlock) Thread.sleep(5000) } } }在run办法中实现了polling接口的调用,并且接口的调用在while循环中。这里假如polling的工夫距离是5秒钟,所以这里调用线程的sleep办法暂停线程的执行,5秒后再次调用polling接口。polling接口的调用是异步过程,所以这里设置了两个回调,一个用于接管胜利的数据,一个用于接管失败的异样。如果在回调中更新了画面,咱们还要思考如何保障回调在ui线程执行,并且回调中不更新隐没的页面元素。 class PollingThread(val lifecycleOwner: LifecycleOwner): Thread() { override fun run() { var successBlock : (PollingData)->Unit = { Handler(Looper.getMainLooper()).post { if(lifecycleOwner.lifecycle.currentState >= Lifecycle.State.RESUMED) { Log.d("PollingThread", "successBlock $it") } } } var failBlock:(Exception)->Unit ={ Handler(Looper.getMainLooper()).post { if(lifecycleOwner.lifecycle.currentState >= Lifecycle.State.RESUMED) { Log.d("PollingThread", "failBlock $it") } } } while (isInterrupted) { pollingApi.call(successBlock, failBlock) Thread.sleep(5000) } } }这段代码减少了回调的线程切换和ui画面无效判断。应用Handler切换线程到ui线程,lifecycler判断ui画面的有效性。 ...

October 26, 2021 · 1 min · jiezi

关于kotlin:Android-app中这样用flow更方便刷新token获取数据

背景挪动app中展现的数据少数都是通过服务器接口获取的,当接口数据与用户相干时,服务端接口会要求客户端把用户信息通过接口发送到服务器。广泛的做法是把用户登录后的token数据发送给服务器的接口。思考到平安问题,token都有过期工夫,token过期后服务端就不能通过这个token查问用户的具体信息了。为了刷新过期token,服务端会提供一个刷新token的接口给客户端应用。 问题剖析因为要求上传token的服务端接口会有很多,所以这些接口的调用都须要思考token过期生效问题。这些接口调用的异样解决中须要减少token过期解决,在token过期的状况下触发token刷新解决。token刷新后触发接口的重试申请。如何应用flow实现 首先咱们实现一个刷新token的flow。 private val refreshTokenFlow = flow { if (expiredToken) { cachedToken = serverApi.refreshToken("token-0") expiredToken = false } emit(cachedToken) }expiredToken变量代表token是否过期,理论开发过程中这个变量的值应该依据过期工夫计算得出的。cachedToken变量保留着最新的token值,申请用户相干信息的接口时能够把这个token传递给服务端用于查问。咱们能够看到cachedToken的值是通过调用服务端提供的刷新接口获取的。这个flow最终发射的是最新的token值,同时咱们也看到这个flow调用刷新接口的逻辑只有token过期时才会被调用。第二步定义申请用户相干数据的flow private fun getDataFlow(token: String) = flow { emit(serverApi.getDataViaToken(token)) }因为申请用户数据的接口依赖token的值,所以这个flow是通过办法生成的。flow的生成也比较简单,它间接调用服务端的接口并将数据发射进来。 第三步将刷新token的flow和申请用户数据的flow开展 private val userDataFlow = refreshTokenFlow.flatMapConcat{token-> getDataFlow(token) }flatMapConcat办法将后面定义的两个流拼接在一起,这时咱们要是收集拼接后的userDataFlow,refreshTokenFlow会被收集,flatMapConcat办法接管到 refreshTokenFlow发射的token后开始收集getDataFlow办法返回的flow。这样连个flow的依赖关系通过flatMapConcat完满实现了。 第四步实现token过期重试机制 private val userDataFlow = refreshTokenFlow.flatMapConcat{token-> getDataFlow(token) }.retryWhen { cause, attempt -> if (attempt > 1) { false } else if(cause is InvalidTokenException) { expiredToken = true true }else{ false } }.catch { msgView.text = it.message }基于第三步的flow拼接咱们增加了retryWhen和cach两个块。retryWhen块用于实现重试机制,参数cause是后面流程产生的异样,参数attempt代表重试的次数,返回值true代表进行重试,返回值false代表不进行重试。在retryWhen块中咱们能够通过cause的类型来判断是否要重试,当cause为InvalidTokenException时代表token过期,所以进行重试并且重置了token过期expiredToken。为了防止有限地进行重试,这里限度重试次数为一次。catch块解决不进行重试时的逻辑,个别会将出错的信息显示到界面上。 ...

October 26, 2021 · 1 min · jiezi

关于kotlin:使用-Kotlin-Symbol-Processing-10-缩短-Kotlin-构建时间

作者 / 软件工程师 Ting-Yuan Huang 和 Jiaxiang Chen Kotlin Symbol Processing (KSP)——用于在 Kotlin 中构建轻量级编译器插件的全新工具现已推出稳固版本!其与 Kotlin 注解解决工具 (KAPT) 的性能类似,但速度进步了 2 倍,同时还能够间接拜访 Kotlin 语言构造并反对多个平台指标。 在过来的几个月里,KSP 共公布了 32 个版本,超过 162 个来自社区反馈的谬误问题被修复。如果您正期待着利用该工具,那当初是时候去尝试了。 为何要构建 KSP在 Android 团队中,咱们常常会向开发者提出这样一个问题: 就现阶段而言,开发利用时遇到的最大痛点是什么?其中呈现最频繁的问题就是构建速度。多年来,咱们始终在稳步优化 Android 构建工具链,当初咱们非常高兴可能通过 KSP 来实现这些优化。KSP 是应用 Kotlin 进行正文解决的新一代工具: 它将大幅提高 Kotlin 开发者的构建速度,而且不同于 KAPT,该工具提供了对 Kotlin/Native 和 Kotlin/JS 的反对。 为 Room 增加 KSP 反对不仅能晋升编译速度,还能让 Room 更好地了解 Kotlin 代码,比方应用 KAPT 无奈实现的泛型的可空性。KSP 还解锁了如生成 Kotlin 代码等全新可能性,这让 Room 在未来会有更棒的 Kotlin 用户体验。 -- Android 软件工程师 Yigit Boyar ...

October 8, 2021 · 1 min · jiezi

关于kotlin:Android中Handler的消息机制分析一

ps:浏览原文可获取 demo 源码,文章开端有原文链接 ps:源码是基于 android api 27 来剖析的,demo 是用 kotlin 语言写的。 Handler 是 Android 线程之间的音讯机制,次要的作用是将一个工作切换到指定的线程中去执行,与 Handler 一起协同工作的有 Looper、Message 和 MessageQueue;上面咱们以 Handler 在主线程解决音讯为例,对 Handler 发送音讯(实质上是将音讯插入链表)和解决音讯(顺便将音讯移除链表)相干的源码进行剖析。 咱们看一下 ActivityThread 中的 main 办法; public static void main(String[] args) { ...... //1、 Looper.prepareMainLooper(); ...... //2、 Looper.loop(); ......} 正文1 其实它外部是初始化 Looper 和 MessageQueue,咱们点击正文1 相干的代码看看; public static void prepareMainLooper() { //3、 prepare(false); ......} 咱们点击进去看看正文3 中的代码,也就是 Looper 的 prepare 办法; private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } //4、 sThreadLocal.set(new Looper(quitAllowed));} ...

September 19, 2021 · 5 min · jiezi

关于kotlin:Java-老兵不死Kotlin-蓄势待发

本文链接:https://www.oreilly.com/radar/where-programming-languages-are-headed-in-2020/ 作者:Zan McQuade & Amanda Quinn 编译:徐九在进入新的十年之际,各行各业都在进行盘点与瞻望。SegmentFault 作为开发者社区与科技行业的垂直媒体,始终关注行业的倒退与相干动静,近期已陆续为大家整顿了各大平台、社区针对技术畛域作出的预测与盘点。 明天,持续为大家粗译O'Reilly 公布的编程语言倒退瞻望 —— 《Where programming languages are headed in 2020》。该盘点及剖析由数位编程专家整顿得出,蕴含了大量他们对于某些经典编程语言以及新兴编程语言的思考以及基于行业的剖析。 Python 往年 Python 的最大新闻是,Python 之父吉多·范·罗苏姆(Guido van Rossum)正式退休,并将 Python 交给了 Python 领导委员会。到目前为止,这次势力转移并没有呈现“阵痛”,正如《Python Crash Course》的作者 Eric Matthes 所认为的那样,这是很失常的,因为“ Guido 在很长一段时间里仍将放弃本人在社区中的角色。” 此外,2020 年还将终止对 Python 2.7 的反对,这很可能导致保持应用 Python 2.7 的人变得很好受。 但不管怎样,Python 依然是数据迷信的首选语言。 对于 Matthes 而言,Python 令人兴奋的一个方面是“来自一个社区的各种乏味且要害的我的项目曾经诞生了,而社区曾经如此无意识地建设了这么长时间。” Python 领导委员会成员和 CPython 的外围开发人员 Carol Willing 也庆贺了这些我的项目,例如 Binder 服务,该服务通过在 Jupyter Notebook 中创立可执行环境来促成可反复的钻研,尤其是当它们超出其最后的指标时。 她指出,“活页夹去年在许多 Python 会议上被宽泛用于教学讲习班和教程。” Willing 还向 CircuitPython 和 Mu 我的项目大声疾呼,问道:“谁会不喜爱硬件呢,闪动的 LED、传感器,以及应用 Mu 的用户敌对的编辑器,这对成年人和孩子来说不都是很棒的抉择?” ...

September 7, 2021 · 2 min · jiezi

关于kotlin:Kotlin-coroutine-原理

CoroutinelifecycleScope.launch { Log.d("testCoroutineScope","testCoroutineScope start $this") delay(2000) Log.d("testCoroutineScope","testCoroutineScope middle1") delay(2000) Log.d("testCoroutineScope","testCoroutineScope middle2") delay(2000) Log.d("testCoroutineScope","testCoroutineScope end") }上边的代码展现了启动协程的办法,通常在协程体中会调用到suspend函数。咱们都理解kotlin中协程的反对除了利用到kotlin的一些语法个性,同时针对协程还进行了编译器的批改,使得咱们在应用协程时更加直观不便。然而这也带来了另一个问题,咱们更难了解协程的具体工作细节。上面咱们从最让人费解的协程体开始动手。 一、摸索协程体到底是什么?这里认为通过launch、async启动的block块就是协程体。 协程体在通过编译器编译后会生成一个新的对象,具体对象的实现是什么样的呢?看看上面反编译后的代码与原代码的比拟: //原来的kotlin代码private fun testCoroutineScope() { val block: suspend CoroutineScope.() -> Unit = { Log.d("testCoroutineScope", "testCoroutineScope start") delay(2000) Log.d("testCoroutineScope", "testCoroutineScope end") } lifecycleScope.launch(block = block) }为了不便察看,我把协程体独自定义一个block变量。kotlin的协程体只是简略的调用delay挂起办法并在办法调用前后增加了log。 //反编译后失去的代码private final void testCoroutineScope() { Function2 block = (Function2)(new Function2((Continuation)null) { int label; @Nullable public final Object invokeSuspend(@NotNull Object $result) { Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED(); switch(this.label) { case 0: ResultKt.throwOnFailure($result); Log.d("testCoroutineScope", "testCoroutineScope start"); this.label = 1; if (DelayKt.delay(2000L, this) == var2) { return var2; } break; case 1: ResultKt.throwOnFailure($result); break; default: throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine"); } Log.d("testCoroutineScope", "testCoroutineScope end"); return Unit.INSTANCE; } @NotNull public final Continuation create(@Nullable Object value, @NotNull Continuation completion) { Intrinsics.checkNotNullParameter(completion, "completion"); Function2 var3 = new <anonymous constructor>(completion); return var3; } public final Object invoke(Object var1, Object var2) { return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE); } }); BuildersKt.launch$default((CoroutineScope)LifecycleOwnerKt.getLifecycleScope(this), (CoroutineContext)null, (CoroutineStart)null, block, 3, (Object)null); }通过反编译后的协程体是一个实现Function2接口类型的对象,Function2反对两个形参的invoke办法。除了invoke办法还有create和invokeSuspend两个办法,所以协程体更精确的形容应该是实现了Function2接口的对象。反编译的代码能够帮咱们理解大体的思路,然而一些细节还是有问题的。咱们在网上搜寻对于协程体对象的介绍,介绍中说协程体是SuspendLambda类的子类。那么它真的是SuspendLambda的子类吗?咱们能够批改代码简略验证下: ...

September 6, 2021 · 6 min · jiezi

关于kotlin:Kotlin组件化-打造自己的AI语音助手

Kotlin+组件化 打造本人的AI语音助手一.Kotlin数组 kotlin为数组减少了一个Array类,为元素是根本类型的数组减少了xxArray类(其中xx也就是Byte,Short, Int等根本类型)Kotlin创立数组大抵有如下两种形式: 1.应用arrayOf(), arrayOfNulls(),emptyArray()工具函数。 2.应用Array(size: Int, init:(Int) -> T) 首先先介绍第一种 Array第二种用到了函数的常识,原本想到函数的到前面函数那个章节在讲,不过怕忘了,就先简略的说说吧。 Array(size: Int, init: (Int) -> T) 第一个参数就是对数组设置的大小很好了解。那么第二个参数是什么。 其实在kotlin里中参数也能够定义某个办法的类型的。哇,感觉很神奇吧!咱们在写java的时候基本上就是参数要不传一般类型要不就是对象类型,没有据说过能够传办法类型的。 因为在kotlin里中办法其实也是有类型的。所以第二个参数 init:(Int) -> T 代表这这个办法返回的类型是T只能有一个参数类型是Int型。尽管没有看源码,然而从它的字面是就能够看出这个参数其实就是对array的每个索引进行初始化的。Int就是该array的所对应的索引。上面看一下这个代码: 看过我以前的文章的敌人应该晓得这个it代表什么了吧,就是代表(Int)的参数传入的值,也就是array的下标的索引。arrayInit的后果就是每个下标的索引所对应的值为下标索引 * 2, arrayInitTwo下标索引对应的值就是索引值。在接下来的Kotlin值函数的文章中我会具体讲讲kotlin函数的用法。 其实有些敌人兴许会有这样的疑虑,为什么在写数组的时候有的时候须要指定泛型,有的时候不须要呢。其实你记住一点就能够了,在初始化的时候如果你是曾经将参数写进去了,kotlin用自动识别的就不须要指定泛型了,如果只是指定大小没有写入参数的话。因为kotlin不晓得须要什么类型,所以须要指定泛型。kotlin还提供了一个emptyArray()函数,用法基本一致,这种形式创立了一个长度为0的空数组。 如果仔细的敌人应该留神到,我在写kotlin数组的时候,取值或者赋值也用到了get,set办法。所以koltin中不只有汇合能够用,数组也能够用。 遍历数组用 for in就好。 kotlin里数组提供了很多工具办法,例如 asList() 将该数组转成list的汇合。 arr.all({it > 20}) 判断是否数组里的值都大于20,如果是返回true,不是返回false arr.any({it > 20})判断是否数组里的值其中有一个大于20,如果是返回true,不是返回false 依据数组元素来计算k, var arrMap = arr.associate({it + 2 to it + 10}) 将数组arr的第5个元素(包含)到底7个元素(不包含)赋值为1 arr.fill(1, 4, 6) 还有很多工具办法,能够去kotlin官网查看,在这里我就不一一列举啦。还有多维数组,跟java的多维数组一样。 2.Kotlin汇合 kotlin汇合类同样有两个接口派生:Collection和Map。但Kotlin的联合被分成两个大类,可变汇合和不可变汇合。只有可变汇合才能够增加批改,删除等解决操作。不可变汇合只能读取元素。 上图为kotlin提供的汇合,通过上图能够看出kotlin的汇合实现类比java更少,他只是提供了HashSet, LinkedHashSet, ArrayList这三个常见的实现类,如果开发者须要应用TreeSet, LinkedList汇合实现类仍然能够应用java汇合框架提供的类。 纵观Kotlin汇合体系,不难发现kotlin只提供了HashSet,HashMap, LinkedHashSet, LinkedHashMap, ArrayList这5个汇合实现类,而且他们都是可变汇合,那么说好的不可变汇合呢。kotlin的不可变汇合类并没有裸露进去,咱们只能通过函数来创立不可变汇合。 ...

August 29, 2021 · 1 min · jiezi

关于kotlin:Kotlin-高阶函数第一行代码-Kotlin-学习笔记

高阶函数详解从本章的 Kotlin 课堂起,咱们就将辞别基础知识,开始转向 Kotlin 的高级用法,从而进一步晋升你的 Kotlin 程度。 那么就从高阶函数开始吧。 定义高阶函数高阶函数和 Lambda 的关系是密不可分的。在第 2 章疾速入门 Kotlin 编程的时候,咱们曾经学习了 Lambda 编程的基础知识,并且把握了一些与汇合相干的函数式 API 的用法,如 map、filter 函数等。另外,在第 3 章的 Kotlin 课堂中,咱们又学习了 Kotlin 的规范函数,如 run、apply 函数等。 你有没有发现,这几个函数有一个独特的特点:它们都会要求咱们传入一个 Lambda 表达式作为参数。像这种接管 Lambda 参数的函数就能够称为具备函数式编程格调的 API,而如果你想要定义本人的函数式 API,那就得借助高阶函数来实现了,这也是咱们本节 Kotlin 课堂所要重点学习的内容。 首先来看一下高阶函数的定义。如果一个函数接管另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数就称为高阶函数。 这个定义可能有点不太好了解,一个函数怎么能接管另一个函数作为参数呢?这就波及另外一个概念了:函数类型。咱们晓得,编程语言中有整型、布尔型等字段类型,而 Kotlin 又减少了一个函数类型的概念。如果咱们将这种函数类型增加到一个函数的参数申明或者返回值申明当中,那么这就是一个高阶函数了。 接下来咱们就学习一下如何定义一个函数类型。不同于定义一个一般的字段类型,函数类型的语法规定是有点非凡的,根本规定如下: (String, Int) -> Unit忽然看到这样的语法规定,你肯定一头雾水吧?不过不必放心,急躁听完我的解释之后,你就可能轻松了解了。 既然是定义一个函数类型,那么最要害的就是要申明该函数接管什么参数,以及它的返回值是什么。因而,-> 右边的局部就是用来申明该函数接管什么参数的,多个参数之间应用逗号隔开,如果不接管任何参数,写一对空括号就能够了。而 -> 左边的局部用于申明该函数的返回值是什么类型,如果没有返回值就应用 Unit,它大抵相当于 Java 中的 void。 当初将上述函数类型增加到某个函数的参数申明或者返回值申明上,那么这个函数就是一个高阶函数了,如下所示: fun example(func: (String, Int) -> Unit) { func("hello", 123)}能够看到,这里的 example() 函数接管了一个函数类型的参数,因而 example() 函数就是一个高阶函数。而调用一个函数类型的参数,它的语法相似于调用一个一般的函数,只须要在参数名的前面加上一对括号,并在括号中传入必要的参数即可。 ...

July 26, 2021 · 4 min · jiezi

关于kotlin:Kotlin-扩展函数和运算符重载第一行代码-Kotlin-学习笔记

扩大函数和运算符重载不少古代高级编程语言中有扩大函数这个概念,Java 却始终以来都不反对这个十分有用的性能,这多少会让人有些遗憾。但值得快乐的是,Kotlin 对扩大函数进行了很好的反对,因而这个知识点是咱们无论如何都不能错过的。 大有用处的扩大函数首先看一下什么是扩大函数。扩大函数示意即便在不批改某个类的源码的状况下,依然能够关上这个类,向该类增加新的函数。 为了帮忙你更好地了解,咱们先来思考一个性能。一段字符串中可能蕴含字母、数字和特殊符号等字符,当初咱们心愿统计字符串中字母的数量,你要怎么实现这个性能呢?如果依照个别的编程思维,可能大多数人会很天然地写出如下函数: object StringUtil { fun letterCount(str: String) : Int { var count = 0 for (char in str) { if (char.isLetter()) { count ++ } } return count } }这里先定义了一个 StringUtil 单例类,而后在这个单例类中定义了一个 lettersCount() 函数,该函数接管一个字符串参数。在 lettersCount() 办法中,咱们应用 for-in 循环去遍历字符串中的每一个字符。如果该字符是一个字母的话,那么就将计数器加 1,最终返回计数器的值。 当初,当咱们须要统计某个字符串中的字母数量时,只须要编写如下代码即可: val str = "ABC123xyz!@#"val count = StringUtil.lettersCount(str)这种写法相对能够失常工作,并且这也是 Java 编程中最规范的实现思维。然而有了扩大函数之后就不一样了,咱们能够应用一种更加面向对象的思维来实现这个性能,比如说将 lettersCount() 函数增加到 String 类当中。 上面咱们先来学习一下定义扩大函数的语法结构,其实非常简单,如下所示: fun ClassName.methodName(param1: Int, param2: Int): Int { return 0}相比于定义一个一般的函数,定义扩大函数只须要在函数名的后面加上一个 ClassName. 的语法结构,就示意将该函数增加到指定类当中了。 ...

July 25, 2021 · 3 min · jiezi

关于kotlin:Kotlin-延迟初始化和密封类第一行代码-Kotlin-学习笔记

theme: fancy highlight: a11y-dark提早初始化和密封类本节的 Kotlin 课堂,咱们就来学习提早初始化和密封类这两局部内容。 对变量缩短初始化后面咱们曾经学习了 Kotlin 语言的许多个性,包含变量不可变,变量不可为空,等等。这些个性都是为了尽可能地保障程序平安而设计的,然而有些时候这些个性也会在编码时给咱们带来不少的麻烦。 比方,如果你的类中存在很多全局变量实例,为了保障它们可能满足 Kotlin 的空指针查看语法规范,你不得不做许多的非空判断爱护才行,即便你十分确定它们不会为空。 上面咱们通过一个具体的例子来看一下吧,就应用刚刚的 UIBestPractice 我的项目来作为例子。如果你仔细观察 MainActivity 中的代码,会发现这里适配器的写法稍微有点非凡: class MainActivity : AppCompatActivity(), View.OnClickListener { private var adapter: MsgAdapter? = null override fun onCreate(savedInstanceState: Bundle?) { ... adapter = MsgAdapter(msgList) ... } override fun onClick(v: View?) { ... adapter?.notifyItemInserted(msgList.size - 1) ... } }这里咱们将 adapter 设置为了全局变量,然而它的初始化工作是在 onCreate() 办法中进行的,因而不得不先将 adapter 赋值为 null,同时把它的类型申明成 MsgAdapter?。 尽管咱们会在 onCreate() 办法中对 adapter 进行初始化,同时能确保 onClick() 办法必然在 onCreate() 办法之后才会调用,然而咱们在 onClick() 办法中调用 adapter 的任何办法时依然要进行判空解决才行,否则编译必定无奈通过。 ...

July 24, 2021 · 3 min · jiezi

关于kotlin:Kotlin-标准函数和静态方法第一行代码-Kotlin-学习笔记

规范函数和静态方法学完了 Kotlin 的基础知识而已,明天咱们来学习 Kotlin 的规范函数和静态方法。 规范函数 with、run 和 applyKotlin 的规范函数指的是 Standard.kt 文件中定义的函数,任何 Kotlin 代码都能够自在地调用所有的规范函数。 在 疾速入门 kotlin 编程 中,咱们曾经学习了 let 这个规范函数,它的次要作用就是配合 ?. 操作符来进行辅助判空解决,这里就不再赘述了。 with 规范函数with 函数接管两个参数:第一个参数能够是一个任意类型的对象,第二个参数是一个 Lambda 表达式。with 函数会在 Lambda 表达式中提供第一个参数对象的上下文,并应用 Lambda 表达式中的最初一行代码作为返回值返回。示例代码如下: val result = with(obj) { // 这里是 obj 的上下文 "value" // with 函数的返回值}那么这个函数有什么作用呢?它能够在间断调用同一个对象的多个办法时让代码变得更加精简,上面咱们来看一个具体的例子。 比方有一个水果列表,当初咱们想吃完所有水果,并将后果打印进去,就能够这样写: val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")val builder = StringBuilder()builder.append("Start eating fruits.\n")for (fruit in list) { builder.append(fruit).append("\n")}builder.append("Ate all fruits.")val result = builder.toString()println(result)仔细观察上述代码,你会发现咱们间断调用了很屡次 builder 对象的办法。其实这个时候就能够思考应用 with 函数来让代码变得更加精简,如下所示: ...

July 23, 2021 · 3 min · jiezi

关于kotlin:快速入门-kotlin-编程第一行代码-Kotlin-学习笔记

疾速入门 Kotlin 编程面向对象编程不同于面向过程的语言(比方 C 语言),面向对象的语言是能够创立类的。类就是对事物的一种封装。 简略概括一下,就是将事物封装成具体的类,而后将事物所领有的属性和能力别离定义成类中的字段和函数,接下来对类进行实例化,再依据具体的编程需要调用类中的字段和办法即可。 类与对象class Person { var name = "" var age = 0 fun eat() { println(name + " is eating. He is " + age + " years old.") }}val p = Person()Kotlin 中也是应用 class 关键字来申明一个类的应用 var 关键字创立了 name 和 age 这两个字段,这是因为咱们须要在创建对象之后再指定具体的姓名和年龄,而如果应用 val 关键字的话,初始化之后就不能再从新赋值了Kotlin 中实例化一个类的形式和 Java 是根本相似的,只是去掉了 new 关键字而已继承与构造函数能够让 Student 类去继承 Person 类,这样 Student 就主动领有了 Person 中的字段和函数,另外还能够定义本人独有的字段和函数。想要让 Student 类继承 Person 类,咱们得做两件事才行 在 Person 类的后面加上 open 关键字使其能够被继承在 Java 中继承的关键字是 extends,而在 Kotlin 中变成了一个冒号 ...

July 22, 2021 · 7 min · jiezi

关于kotlin:从-LiveData-迁移到-Kotlin-数据流

LiveData 的历史要追溯到 2017 年。彼时,观察者模式无效简化了开发,但诸如 RxJava 一类的库对老手而言有些太过简单。为此,架构组件团队打造了 LiveData: 一个专用于 Android 的具备自主生命周期感知能力的可察看的数据存储器类。LiveData 被无意简化设计,这使得开发者很容易上手;而对于较为简单的交互数据流场景,建议您应用 RxJava,这样两者联合的劣势就施展进去了。 DeadData?LiveData 对于 Java 开发者、初学者或是一些简略场景而言仍是可行的解决方案。而对于一些其余的场景,更好的抉择是应用 Kotlin 数据流 (Kotlin Flow)。虽说数据流 (相较 LiveData) 有更平缓的学习曲线,但因为它是 JetBrains 力挺的 Kotlin 语言的一部分,且 Jetpack Compose 正式版行将公布,故两者配合更能施展出 Kotlin 数据流中响应式模型的后劲。 此前一段时间,咱们探讨了 如何应用 Kotlin 数据流 来连贯您的利用当中除了视图和 View Model 以外的其余局部。而当初咱们有了 一种更平安的形式来从 Android 的界面中取得数据流,曾经能够创作一份残缺的迁徙指南了。 在这篇文章中,您将学到如何把数据流裸露给视图、如何收集数据流,以及如何通过调优来适应不同的需要。 数据流: 把简略复杂化,又把简单变简略LiveData 就做了一件事并且做得不错: 它在 缓存最新的数据 和感知 Android 中的生命周期的同时将数据裸露了进去。稍后咱们会理解到 LiveData 还能够 启动协程 和 创立简单的数据转换,这可能会须要花点工夫。 接下来咱们一起比拟 LiveData 和 Kotlin 数据流中绝对应的写法吧: #1: 应用可变数据存储器裸露一次性操作的后果 这是一个经典的操作模式,其中您会应用协程的后果来扭转状态容器: △ 将一次性操作的后果裸露给可变的数据容器 (LiveData) ...

June 29, 2021 · 4 min · jiezi

关于kotlin:FAQ-使用-Kotlin-进行-Android-开发

自从 2017 年咱们发表反对 Kotlin 以来,收到了很多对于应用 Kotlin 进行 Android 开发的问题: 大家想晓得当初是否适宜学习 Kotlin,是否要在利用开发中引入 Kotlin,学习 Kotlin 的最佳课程或教程是什么,Google 外部是否在应用 Kotlin,以及咱们对 Java 编程语言的布局是怎么的?本文将一一作答。 问: 是否应该学习 Kotlin 进行 Android 开发?最常提及的问题大都围绕同一个话题: "对于初学者,应该抉择学习 Kotlin 还是 Java 编程语言?""如果曾经把握了 Java 基础知识,当初适宜改用 Kotlin 进行 Android 开发吗?""对于资深 Java 开发者如果学习 Android 开发,举荐间接上手 Kotlin 还是应用 Java 入门呢?"简略来说: 是的!开始学习和应用 Kotlin 吧! 开展来答复: Kotlin 与 Android2017 年,咱们在 Google I/O 大会上发表反对 Kotlin。从那时起,咱们开始着手确保咱们的 API、文档和示例实用于 Kotlin。2019 年,Kotlin 成为 Android 开发的首选语言,这让咱们开始更加依赖于 Kotlin 的性能。例如,协程成为咱们实现异步操作的举荐计划。 咱们还做了以下工作: Kotlin 优先库 首先在若干 Android Jetpack API (如 Room、LiveData、ViewModel 和 WorkManager) 中,咱们减少了对 Kotlin 协程的一流反对,从而转变了在 Android 上执行异步操作的形式。Firebase Android SDK 和许多 Jetpack 库都具备 Kotlin 扩大库 (KTX),通过 Kotlin 应用起来更加晦涩。 ...

June 23, 2021 · 2 min · jiezi

关于kotlin:Kotlin-中使用-Hilt-的开发实践

Hilt 是基于 Dagger 开发的全新的依赖项注入代码库,它简化了 Android 利用中 Dagger 的调用形式。本文通过简短的代码片段为您展现其外围性能以帮忙开发者们疾速入门 Hilt。 配置 Hilt如需在利用中配置 Hilt,请先参考 Gradle Build Setup。 实现装置全副的依赖和插件当前,仅需在您的 Application 类之前增加 @HiltAndroidApp 注解即可开始应用 Hilt,而无需其它操作。 @HiltAndroidAppclass App : Application()定义并且注入依赖项当您写代码用到依赖项注入的时候,有两个要点须要思考: 您须要注入依赖项的类;能够作为依赖项进行注入的类。而上述这两点并不互斥,而且在很多状况下,您的类既能够注入依赖项同时也蕴含依赖。 使依赖项可注入 如果须要在 Hilt 中使某个类变得可注入,您须要通知 Hilt 如何创立该类的实例。该过程叫做绑定 (bindings)。 在 Hilt 中定义绑定有三种形式: 在构造函数上增加 @Inject 注解;在模块上应用 @Binds 注解;在模块上应用 @Provides 注解。⮕ 在构造函数上应用 @Inject 注解 任何类的构造函数都能够增加 @Inject 注解,这样该类在整个工程中都能够作为依赖进行注入。 class OatMilk @Inject constructor() { ... }⮕ 应用模块 在 Hilt 中另外两种将类转为可注入的办法是应用模块。 Hilt 模块 就如同 "菜谱",它能够通知 Hilt 如何创立那些不具备构造函数的类的实例,比方接口或者零碎服务。 此外,在您的测试中,任何模块都能够被其它模块所代替。这有利于应用 mock 替换接口实现。 模块通过 @InstallIn 注解被装置在特定的 Hilt 组件 中。这一部分我会在前面具体介绍。 ...

June 4, 2021 · 3 min · jiezi

关于kotlin:使用-Kotlin-提高生产力

Kotlin 以其简洁的个性而闻名,而在咱们的实际中,更加简洁就意味着更加高效。事实上,在应用 Kotlin 的业余 Android 开发者中,有多达 67% 的人示意 Kotlin 曾经帮忙他们晋升了生产力。在接下来的内容中,我会分享一些 Kotlin 帮忙咱们的合作伙伴工程师们进步生产力的形式,并为您介绍有助于此的 Kotlin 性能。 在应用 Kotlin 的业余 Android 开发者中,有多达 67% 的人示意 Kotlin 曾经帮忙他们晋升了生产力 简洁、简略且高效Kotlin 的简洁性对开发的各个阶段都有影响: 作为代码作者: 您能够专一于须要解决的问题 (而不是语法)。更少的代码意味着更少地测试、更少地调试以及更少写出 Bug 的机会。作为审阅和维护者: 您须要浏览的代码变少了,从而更容易了解代码的作用,也因而更容易审阅和保护代码。以下例子来自 Flipkart 的团队: "在一次外部考察中,50% 的开发人员提到,对于应用 Kotlin 编写的模块,预估实现性能所需的工夫会有所缩小。" ——Flipkart Kotlin 的性能与生产力因为 Kotlin 的简洁与高可读性,大多数 Kotlin 的性能都能够进步生产力。上面让咱们来看一些最罕用的性能。 默认参数与构建器在 Java 编程语言中,当您的构造函数中的某些参数是可选参数时,您通常会采纳上面两种办法之一: 增加多个构造函数;实现 构建器模式。在应用 Kotlin 时,因为默认参数性能的存在,您无需应用这两种办法。默认参数使您无需额定的样板代码便能实现函数重载。 对 Kotlin 的应用使得 Cash App 团队能够革除诸多构建器,从而缩小了他们须要编写的代码量。在某些状况下,代码量被缩小了 25% 之多。 举个例子,上面的代码是一个 Task 对象别离应用构建器及默认参数的实现形式。该 Task 惟一的必须参数是工作名 (name): /* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */3- public class Task {- private final String name;- private final Date deadline;- private final TaskPriority priority;- private final boolean completed;-- private Task(String name, Date deadline, TaskPriority priority, boolean completed) {- this.name = name;- this.deadline = deadline;- this.priority = priority;- this.completed = completed;- }-- public static class Builder {- private final String name;- private Date deadline;- private TaskPriority priority;- private boolean completed;-- public Builder(String name) {- this.name = name;- }-- public Builder setDeadline(Date deadline) {- this.deadline = deadline;- return this;- }-- public Builder setPriority(TaskPriority priority) {- this.priority = priority;- return this;- }-- public Builder setCompleted(boolean completed) {- this.completed = completed;- return this;- }-- public Task build() {- return new Task(name, deadline, priority, completed);- }- }-}+ data class Task(+ val name: String,+ val deadline: Date = DEFAULT_DEADLINE,+ val priority: TaskPriority = TaskPriority.LOW,+ val completed: Boolean = false+)您能够通过咱们的这篇 Kotlin Vocabulary | Kotlin 默认参数 理解无关默认参数的更多信息。 ...

May 6, 2021 · 2 min · jiezi

关于kotlin:关于Kotlin中日志的使用方法

1 引言想必学过Java的人都晓得一个@Slf4j应用得如许的难受: @Slf4jpublic class TestController{ @GetMapping("/test") public String test(){ log.debug("debug"); return "test"; }}然而很可怜在Kotlin中并没有这种注解,因而,本文给出了一种相似@Slf4j注解在Kotlin中的应用办法,以及介绍一个100%应用Kotlin编写的日志库。 2 入手写@Slf4j很简略,先上代码: import org.slf4j.Loggerimport org.slf4j.LoggerFactory@Target(AnnotationTarget.CLASS)@Retention(AnnotationRetention.RUNTIME)annotation class Slf4j{ companion object{ val <reified T> T.log: Logger inline get() = LoggerFactory.getLogger(T::class.java) }}逐行解释如下: @Target:与Java中的@Target相似,注解的指标,这里是类@Retention:与Java中的@Retention相似,运行时保留annotation class:申明一个注解companion object:伴生对象val <reified T> T.log:Logger:申明一个Logger类型的泛型对象inline get() = LoggerFactory.getLogger(T::class.java):申明getter为内联,申明为内联能力应用T,这样能力传递给前面的getLogger,T::class.java相当于Java中的T.class,也就是getLogger(T::class.java)相当于getLogger(SomeClass.class)应用很简略: @RestController@Slf4jclass TestController { @GetMapping("/test") fun test():String{ log.warn("cc") return "test" }}间接类上加一个注解,就能够应用log.info/log.warn之类的办法了。 3 kotlin-logging下面介绍了注解的应用办法,如果不想应用注解的话,能够应用他人的库,比方kotlin-logging。 kotlin-logging是一个100%应用Kotlin编写的轻度封装了slf4j的开源日志库,曾经播种1.4k的star: 依赖如下: <dependency> <groupId>io.github.microutils</groupId> <artifactId>kotlin-logging-jvm</artifactId> <version>2.0.6</version></dependency>Gradle: implementation 'io.github.microutils:kotlin-logging-jvm:2.0.6'引入时,只须要在对应的类中创立一个属性即可: private val logger = KotlinLogging.logger {}应用时,间接调用其中的info/debug/error等即可: import mu.KotlinLoggingprivate val logger = KotlinLogging.logger {} class FooWithLogging { val message = "world" fun bar() { logger.debug { "hello $message" } }}4 两者联合应用当然,也能够将注解与kotlin-logging联合一下应用,首先,笔者简略地看了一下KotlinLogging的接口: ...

March 17, 2021 · 1 min · jiezi

关于kotlin:kotlin语言中的out和in

ps:浏览原文,能够获取源码 在 kotlin 语言中,out 示意协变,in 示意逆变;协变和逆变并不是 kotlin 独有的概念,像 Java、C#都有这样的概念;为了可能了解 kotlin 语言中的 out 和 in,咱们先用 Java 的泛型来举例,咱们须要用泛型,是因为它的益处就是在编译的时候可能查看类型平安,并且所有的强制转换都是主动和隐式的。 1、Java 中的 ? extends T 和 ? super T 1、1 ? extends T ps:代码是在 AndroidStudio 工具上写的 建设一个 Java 文件鸟类 Birds; public class Birds { private String name; public Birds(String name) { this.name = name; } public void flight() { System.out.println("我是" + name + ",属于鸟类,我能航行"); }}建设一个 Java 文件乌鸦类 Crow 并继承 Birds; public class Crow extends Birds { public Crow(String name) { super(name); }}新建一个 Java 文件的泛型类 TestBirds 并限度泛型 T 是 Birds 的子类; ...

March 14, 2021 · 4 min · jiezi

关于kotlin:kotlin语言中的out和in

在 kotlin 语言中,out 示意协变,in 示意逆变;协变和逆变并不是 kotlin 独有的概念,像 Java、C#都有这样的概念;为了可能了解 kotlin 语言中的 out 和 in,咱们先用 Java 的泛型来举例,咱们须要用泛型,是因为它的益处就是在编译的时候可能查看类型平安,并且所有的强制转换都是主动和隐式的。 1、Java 中的 ? extends T 和 ? super T 1、1 ? extends T ps:代码是在 AndroidStudio 工具上写的 建设一个 Java 文件鸟类 Birds; public class Birds { private String name; public Birds(String name) { this.name = name; } public void flight() { System.out.println("我是" + name + ",属于鸟类,我能航行"); }}建设一个 Java 文件乌鸦类 Crow 并继承 Birds; public class Crow extends Birds { public Crow(String name) { super(name); }}新建一个 Java 文件的泛型类 TestBirds 并限度泛型 T 是 Birds 的子类; ...

March 14, 2021 · 4 min · jiezi

关于kotlin:kotlin语言的基础语法

这是我微信公众号的一篇文章,这里转载分享一下。

March 8, 2021 · 1 min · jiezi

关于kotlin:开始切换到-Kotlin-谷歌工程师给初学者的知识点总结

在 2019 年的 I/O 大会上,咱们曾发表 Kotlin 将会是 Android 利用开发的首选语言,然而,局部开发者们反馈仍不分明如何切换到 Kotlin,如果团队中没有人相熟 Kotlin,一开始间接应用 Kotlin 进行我的项目开发还是会令人生畏。 在 Android Studio Profiler 团队外部,咱们是通过几个步骤克服了这个问题,第一步是要求所有的单元测试应用 Kotlin 编写。这么做无效防止了咱们犯的任何渺小谬误间接影响到生产环境中的代码,因为单元测试与生产环境的代码是离开的。 我收集了咱们团队在历次 Code Review 中遇到过的常见问题并整顿出了这篇文章,心愿这篇文章对宽广 Android 社区的敌人们有所帮忙。 留神: 本文的指标读者是 Kotlin 的初学者,如果您的团队曾经纯熟应用 Kotlin 进行我的项目开发,本文对您的帮忙可能不大。但如果您感觉咱们脱漏了一些应该被提及的内容,请在本文留言区留言通知咱们。IDE 性能: 把 Java 文件转换成 Kotlin 文件如果您应用 Android Studio 开发程序,学习 Kotlin 的最简略办法是应用 Java 语言编写单元测试,而后在Android Studio 的菜单栏中点击 Code -> Convert Java File to Kotlin File 按钮将 Java 文件转换成 Kotlin 文件。 这个操作可能会提醒您 "Some code in the rest of your project may require corrections after performing this conversion. Do you want to find such code and correct it too?",它的意思是说我的项目中的其余代码可能会受到此次转换的影响,而且有可能会导致谬误,请问是否须要定位出错的代码,并对相干代码进行批改。我倡议抉择 "No",这样您就能够将代码的批改集中在一个文件上。 ...

December 5, 2020 · 4 min · jiezi

关于kotlin:大众点评用-Kotlin-打造灵活稳定兼备的应用-Android-开发者故事

https://www.bilibili.com/vide... 公众点评是寰球最早的生产点评网站之一,成立于 2003 年。进入挪动互联网时代后,用户能够在手机上应用公众点评 APP 不便地查问任何一个城市里的餐厅、影院、商场、景点和酒店等信息,并且理解其余用户写下的评估。利用也会根据用户评估数据和专家评估来推出各种榜单,比方 "必系列" 榜单和 "黑珍珠" 系列餐厅评估体系,还能依据用户的爱好为其在信息流中举荐可能感兴趣的餐厅和景点等信息。 △ "必系列" 榜单 △ "黑珍珠" 系列餐厅评估体系 为了保障用户良好的应用体验和继续迭代新的性能,利用稳定性和开发效率是点评技术团队关注的重中之重。 "点评 Android 利用的开发合作模式是壳工程依赖于一系列根底和业务组件,利用到 Kotlin 的局部扩散在十余支业务团队,近四十个业务仓库中,涵盖了首页、商户页、直播、榜单等外围业务。"—— 程康阳,Android 开发工程师 Kotlin 现代化的语言个性,比方扩大函数和 lambda 表达式,帮忙团队缩小了近 30% 的代码量,晋升了近 20% 的需要开发效率。也因而,目前点评团队曾经有 15% 左右的依赖库在应用 Kotlin 进行开发和保护。 △ Kotlin 扩大函数 Kotlin 另一个让开发团队拍案叫绝的性能是空安全性,这和 Kotlin 与 Java 良好的互操作性也有关系——只须要在 Java 代码中写好 @Nullable 和 @NonNull 等注解,就能确保 Kotlin 代码取得正确的可空性推断。如此便捷弱小的空平安个性也帮忙团队将利用的 NPE 从日均 3 个升高至 0。 △ 在 Java 代码中应用空平安注解能够确保 Kotlin 代码取得正确的可空性推断 ...

December 5, 2020 · 1 min · jiezi

关于kotlin:Kotlin-Android-Extensions-的未来计划

作者 / 产品经理 David Winer Android Kotlin Extensions Gradle 插件 (请勿与 Android KTX 混同) 公布于 2017 年,为应用 Kotlin 进行 Android 开发带来了两项新的便当性能: Synthetic 视图 : 您能够将调用 findViewById 替换为应用 kotlinx.android.synthetic 进行 UI 交互。@Parcelize 注解: 帮忙您移除样板代码并通过 @Parcelize 注解轻松创立 Parcelable。咱们随后公布了 实用于 Android 的视图绑定 组件,它是一个与 Android 构建工具链深度集成并提供与 Kotlin synthetic 相似性能的官网反对库。咱们尽管仍举荐应用 Parcelize,但 Kotlin synthetic 却存在一些弊病: 净化全局命名空间不能裸露可空性信息仅反对 Kotlin 代码Android Kotlin Extensions 插件最后由 JetBrains 开发,咱们也独特探讨了持续保留 synthetic 的利弊: 咱们尽力确保在可行范畴内对 API 的长期反对,但咱们也心愿为开发者提供领导,帮忙开发者保护衰弱的代码库并最终博得用户的称心。 在接下来的一年里,咱们的团队将独特弃用 synthetics,并持续反对咱们倡议的选项——"视图绑定 (View Binding)"。这意味着: ...

December 4, 2020 · 1 min · jiezi

关于kotlin:Kotlin-Vocabulary-揭秘协程中的-suspend-修饰符

Kotlin 协程把 suspend 修饰符引入到了咱们 Android 开发者的日常开发中。您是否好奇它的底层工作原理呢?编译器是如何转换咱们的代码,使其可能挂起和复原协程操作的呢? 理解这些将会帮您更好地了解挂起函数 (suspend function) 为什么只会在所有工作实现后才会返回,以及如何在不阻塞线程的状况下挂起代码。 本文概要: Kotlin 编译器将会为每个挂起函数创立一个状态机,这个状态机将为咱们治理协程的操作! ???? 如果您是 Android 平台上协程的初学者,请查阅上面这些协程 codelab: 在 Android 利用中应用协程协程的进阶应用: Kotlin Flow 和 Live Data协程 101协程简化了 Android 平台的异步操作。正如官网文档 《利用 Kotlin 协程晋升利用性能》 所介绍的,咱们能够应用协程治理那些以往可能阻塞主线程或者让利用卡死的异步工作。 协程也能够帮咱们用命令式代码替换那些基于回调的 API。例如,上面这段应用了回调的异步代码: // 简化的只思考了根底性能的代码fun loginUser(userId: String, password: String, userResult: Callback<User>) { // 异步回调 userRemoteDataSource.logUserIn { user -> // 胜利的网络申请 userLocalDataSource.logUserIn(user) { userDb -> // 保留后果到数据库 userResult.success(userDb) } }}下面的回调能够通过应用协程转换为顺序调用: suspend fun loginUser(userId: String, password: String): User { val user = userRemoteDataSource.logUserIn(userId, password) val userDb = userLocalDataSource.logUserIn(user) return userDb}在前面这段代码中,咱们为函数增加了 suspend 修饰符,它能够通知编译器,该函数须要在协程中执行。作为开发者,您能够把挂起函数看作是一般函数,只不过它可能会在某些时刻挂起和复原而已。 ...

December 2, 2020 · 4 min · jiezi

关于kotlin:Kotlin-Vocabulary-内联类-inline-class

*特定条件和状况这篇博客形容了一个 Kotlin 试验性功能,它还在调整之中。本文基于 Kotlin 1.3.50 撰写。类型平安帮忙咱们防止出现谬误以及防止回过头去调试谬误。对于 Android 资源文件,比方 String、Font 或 Animation 资源,咱们能够应用 androidx.annotations,通过应用像 @StringRes、@FontRes 这样的注解,就能够让代码查看工具 (如 Lint) 限度咱们只能传递正确类型的参数: fun myStringResUsage(@StringRes string: Int){ } // 谬误: 须要 String 类型的资源myStringResUsage(1)扩大浏览: 利用正文改良代码查看如果咱们的 ID 对应的不是 Android 资源,而是 Doggo 或 Cat 之类的域对象,那么就会很难辨别这两个同为 Int 类型的 ID。为了实现类型平安,须要将 ID 包装在一个类中,从而使狗与猫的 ID 编码为不同的类型。这样做的毛病是您要付出额定的性能老本,因为原本只须要一个原生类型,然而却实例化进去了一个新的对象。 通过 Kotlin 内联类 您能够创立包装类型 (wrapper type),却不会有额定的性能耗费。这是 Kotlin 1.3 中增加的试验性功能。内联类只能有一个属性。在编译时,内联类会在可能的中央被替换为其外部的属性 (勾销装箱),从而升高惯例包装类的性能老本。对于包装对象是原生类型的状况,这尤其重要,因为编译器曾经对它们进行了优化。所以将一个原始数据类型包装在内联类里就意味着,在可能的状况下,数据值会以原始数据值的模式呈现。 inline class DoggoId(val id: Long)data class Doggo(val id: DoggoId, … ) // 用法val goodDoggo = Doggo(DoggoId(doggoId), …)fun pet(id: DoggoId) { … }}内联内联类的惟一作用是成为某种类型的包装,因而 Kotlin 对其施加了许多限度: ...

November 29, 2020 · 3 min · jiezi

关于kotlin:在-Android-开发中使用协程-代码实战

本文是介绍 Android 协程系列中的第三局部,这篇文章通过发送一次性申请来介绍如何应用协程解决在理论编码过程中遇到的问题。在浏览本文之前,建议您先浏览本系列的前两篇文章,对于在 Android 开发中应用协程的 背景介绍 和 上手指南。 应用协程解决理论编码问题前两篇文章次要是介绍了如何应用协程来简化代码,在 Android 上保障主线程平安,防止工作透露。以此为背景,咱们认为应用协程是在解决后台任务和简化 Android 回调代码的绝佳计划。 目前为止,咱们次要集中在介绍协程是什么,以及如何治理它们,本文咱们将介绍如何应用协程来实现一些理论工作。协程同函数一样,是在编程语言个性中的一个罕用个性,您能够应用它来实现任何能够通过函数和对象能实现的性能。然而,在理论编程中,始终存在两种类型的工作非常适合应用协程来解决: 一次性申请 (one shot requests) 是那种调用一下就申请一下,申请获取到后果后就完结执行;流式申请 (streaming request) 在发出请求后,还始终监听它的变动并返回给调用方,在拿到第一个后果之后它们也不会完结。协程对于解决这些工作是一个绝佳的解决方案。在这篇文章中,咱们将会深刻介绍一次性申请,并摸索如何在 Android 中应用协程实现它们。 一次性申请一次性申请会调用一次就申请一次,获取到后果后就完结执行。这个模式同调用惯例函数很像 —— 调用一次,执行,而后返回。正因为同函数调用类似,所以绝对于流式申请它更容易了解。 一次性申请会调用一次就申请一次,获取到后果后就完结执行。 举例来说,您能够把它类比为浏览器加载页面。当您点击了这篇文章的链接后,浏览器向服务器发送了网络申请,而后进行页面加载。一旦页面数据传输到浏览器后,浏览器就有了所有须要的数据,而后进行同后端服务的对话。如果服务器起初又批改了这篇文章的内容,新的更改是不会显示在浏览器中的,除非您被动刷新了浏览器页面。 只管这样的形式短少了流式申请那样的实时推送个性,然而它还是十分有用的。在 Android 的利用中您能够用这种形式解决很多问题,比方对数据的查问、存储或更新,它还很实用于解决列表排序问题。 问题: 展现一个有序列表咱们通过一个展现有序列表的例子来摸索一下如何构建一次性申请。为了让例子更具体一些,咱们来构建一个用于商店员工应用的库存利用,应用它可能依据上次进货的工夫来查找相应商品,并可能以升序和降序的形式排列。因为这个仓库中存储的商品很多,所以对它们进行排序要花费将近 1 秒钟,因而咱们须要应用协程来防止阻塞主线程。 在利用中,所有的数据都会存储到 Room 数据库中。因为不波及到网络申请,因而咱们不须要进行网络申请,从而专一于一次性申请这样的编程模式。因为无需进行网络申请,这个例子会很简略,尽管如此它依然展现了该应用怎么的模式来实现一次性申请。 为了应用协程来实现此需要,您须要在协程中引入 ViewModel、Repository 和 Dao。让咱们一一进行介绍,看看如何把它们同协程整合在一起。 class ProductsViewModel(val productsRepository: ProductsRepository): ViewModel() { private val _sortedProducts = MutableLiveData<List<ProductListing>>() val sortedProducts: LiveData<List<ProductListing>> = _sortedProducts /** * 当用户点击相应排序按钮后,UI 进行调用 */ fun onSortAscending() = sortPricesBy(ascending = true) fun onSortDescending() = sortPricesBy(ascending = false) private fun sortPricesBy(ascending: Boolean) { viewModelScope.launch { // suspend 和 resume 使得这个数据库申请是主线程平安的,所以 ViewModel 不须要关怀线程平安问题 _sortedProducts.value = productsRepository.loadSortedProducts(ascending) } }}ProductsViewModel 负责从 UI 层承受事件,而后向 repository 申请更新的数据。它应用 LiveData 来存储以后排序的列表数据,以供 UI 进行展现。当呈现某个新事件时,sortProductsBy 会启动一个新的协程对列表进行排序,当排序实现后更新 LiveData。在这种架构下,通常都是应用 ViewModel 启动协程,因为这样做的话能够在 onCleared 中勾销所启动的协程。当用户来到此界面后,这些工作就没必要持续进行了。 ...

November 28, 2020 · 4 min · jiezi

关于kotlin:Kotlin-Vocabulary-枚举和-R8-编译器

学习或应用一门新的编程语言时,理解这门语言所提供的性能,以及理解这些性能是否有相关联的开销,都是非常重要的环节。 这方面的问题在 Kotlin 中显得更加乏味,因为 Kotlin 最终会编译为 Java 字节码,然而它却提供了 Java 所没有的性能。那么 Kotlin 是怎么做到的呢?这些性能有没有额定开销?如果有,咱们能做些什么来优化它吗? 接下来的内容与 Kotlin 中枚举 (enums) 和 when 语句 (java 中的 switch 语句) 无关。我会探讨一些和 when 语句相干的潜在开销,以及 Android R8 编译器是如何优化您的利用并缩小这些开销的。 编译器首先,咱们讲一讲 D8 和 R8。 事实上,有三个编译器参加了 Android 利用中 Kotlin 代码的编译。 1. Kotlin 编译器 Kotlin 编译器将会首先运行,它会把您写的代码转换为 Java 字节码。尽管听起来很棒,但惋惜的是 Android 设施上并不运行 Java 字节码,而是被称为 DEX 的 Dalvik 可执行文件。Dalvik 是 Android 最后所应用的运行时。而 Android 当初的运行时,则是从 Android 5.0 Lollipop 开始应用的 ART (Android Runtime),不过 ART 仍然在运行 DEX 代码 (如果替换后的运行时无奈运行原有的可执行文件的话,就毫无兼容性可言了)。 ...

November 27, 2020 · 2 min · jiezi

关于kotlin:在-Android-开发中使用协程-上手指南

本文是介绍 Android 协程系列中的第二局部,这篇文章次要会介绍如何应用协程来解决工作,并且能在工作开始执行后放弃对它的追踪。 放弃对协程的追踪本系列文章的第一篇,咱们探讨了协程适宜用来解决哪些问题。这里再简略回顾一下,协程适宜解决以下两个常见的编程问题: 解决耗时工作 (Long running tasks),这种工作经常会阻塞住主线程;保障主线程平安 (Main-safety) ,即确保安全地从主线程调用任何 suspend 函数。协程通过在惯例函数之上减少 suspend 和 resume 两个操作来解决上述问题。当某个特定的线程上的所有协程被 suspend 后,该线程便可腾出资源去解决其余工作。 协程本身并不可能追踪正在解决的工作,然而有成千盈百个协程并对它们同时执行挂起操作并没有太大问题。协程是轻量级的,但解决的工作却不肯定是轻量的,比方读取文件或者发送网络申请。 应用代码来手动追踪上千个协程是十分艰难的,您能够尝试对所有协程进行跟踪,手动确保它们都实现了或者都被勾销了,那么代码会臃肿且易出错。如果代码不是很完满,就会失去对协程的追踪,也就是所谓 "work leak" 的状况。 工作透露 (work leak) 是指某个协程失落无奈追踪,它相似于内存透露,但比它更加蹩脚,这样失落的协程能够复原本人,从而占用内存、CPU、磁盘资源,甚至会发动一个网络申请,而这也意味着它所占用的这些资源都无奈失去重用。 透露协程会节约内存、CPU、磁盘资源,甚至发送一个无用的网络申请。 为了可能防止协程透露,Kotlin 引入了 结构化并发 (structured concurrency) 机制,它是一系列编程语言个性和实际指南的联合,遵循它能帮忙您追踪到所有运行于协程中的工作。 在 Android 平台上,咱们能够应用结构化并发来做到以下三件事: 勾销工作 —— 当某项工作不再须要时勾销它;追踪工作 —— 当工作正在执行时,追踪它;收回谬误信号 —— 当协程失败时,收回谬误信号表明有谬误产生。接下来咱们对以上几点一一进行探讨,看看结构化并发是如何帮忙可能追踪所有协程,而不会导致透露呈现的。 借助 scope 来勾销工作在 Kotlin 中,定义协程必须指定其 CoroutineScope 。CoroutineScope 能够对协程进行追踪,即便协程被挂起也是如此。同 第一篇文章 中讲到的调度程序 (Dispatcher) 不同,CoroutineScope 并不运行协程,它只是确保您不会失去对协程的追踪。 为了确保所有的协程都会被追踪,Kotlin 不容许在没有应用 CoroutineScope 的状况下启动新的协程。CoroutineScope 可被看作是一个具备超能力的 ExecutorService 的轻量级版本。它能启动新的协程,同时这个协程还具备咱们在第一局部所说的 suspend 和 resume 的劣势。 ...

November 26, 2020 · 3 min · jiezi

关于kotlin:网易云音乐的-Kotlin-乐章-Android-开发者故事

https://www.bilibili.com/vide... "音乐是灵魂之间的美妙交换,是带着情绪的艺术品。网易云音乐要做的,就是帮忙用户发现和分享好音乐,用音乐连贯用户和音乐人,让用户去感触音乐人想表白的情绪,让更多的人用音乐取暖、发光、取得力量。"—— 郭元,网易云音乐产品经理 网易云音乐是网易旗下一款专一于发现和分享的音乐产品,依靠业余音乐人、DJ、好友举荐及社区性能,为用户打造全新的音乐生存。目前,网易云音乐用户数已超过 8 亿,曲库数超 4,000 万 (近期更新数据),入驻原创音乐人超 20 万,是中国最沉闷的音乐社区和中国最大的原创音乐平台。 △ 用网易云音乐和好友分享音乐 Android 客户端开发团队 2019 年 8 月引入 Kotlin 之后,很快就发现学习 Kotlin 是一件比拟轻松的事件: 刚开始的一段时间,常常能够在团队成员的周报中看到对 Kotlin 个性的探讨,团队中也不时会有共事撰写 Kotlin 的学习总结文章并发进去分享。大家根本都能够很快上手开发。而且通过 Kotlin 官方网站、Github 上 Kotlin 我的项目中的文档,以及 Android Studio 提供的将 Kotlin 代码反编译为 Java 代码的性能,都能够帮忙团队成员们更加深刻地理解 Kotlin 语言。 "Kotlin 作为 Android 开发的新语言新技术,集成了很多其它语言中的先进设计思维。与 Java 代码兼容和相互调用的个性,也极大地缩小了咱们在外部推广 Kotlin 的阻力。"—— 贾斌,网易云音乐资深 Android 开发工程师 目前在网易云音乐的 Android 工程中,Kotlin 文件比例大概占 23%,而且新增的性能大部分都是应用 Kotlin 进行编写。团队也同时引入了 KTX 和协程等库来进步开发效率,让工程师更专一于性能自身的实现。 应用 Kotlin 带来的第一个直观益处是简洁。团队本人有做过统计: ...

November 26, 2020 · 1 min · jiezi

关于kotlin:Kotlin-Vocabulary-类型别名-typealias

作者 / David Winer, Kotlin 产品经理 有时候一些可读性差、不够明确或者名字太长的类型申明会烦扰代码的 "自我表白"。这种状况下,能够应用 Kotlin 特地针对这个问题提供的个性: Typealias (本文下称 "类型别名")。类型别名能够使您在不减少新类型的状况下,为现有类或函数类型提供代替名称。 类型别名的应用应用类型别名为函数类型命名: typealias TeardownLogic = () -> Unitfun onCancel(teardown : TeardownLogic){ }private typealias OnDoggoClick = (dog: Pet.GoodDoggo) -> Unitval onClick: OnDoggoClick不过要留神这种用法会暗藏传入参数,使可读性变差: typealias TeardownLogic = () -> Unittypealias TeardownLogic = (exception: Exception) -> Unitfun onCancel(teardown : TeardownLogic){ // 无奈轻易通晓能够从 TeardownLogic 失去什么信息}类型别名有助于缩短较长的泛型类名: typealias Doggos = List<Pet.GoodDoggo>fun train(dogs: Doggos){ ... }应用类型别名时,须要思考是否有必要这么做: 在这里应用类型别名真的会让您的代码意义更明确、可读性更好吗? 思考一下,应用类型别名是否使您的代码变得更易懂 如果您正应用的某个类名称很长,您能够应用类型别名来缩短它: typealias AVD = AnimatedVectorDrawable在此示例中,应用 导入别名 (import alias) 会更加适合: ...

November 25, 2020 · 1 min · jiezi

关于kotlin:Kotlin-Vocabulary-密封类-sealed-class

咱们常常须要在代码中申明一些无限汇合,如: 网络申请可能为胜利或失败;用户账户是高级用户或普通用户。 咱们能够应用枚举来实现这类模型,但枚举本身存在许多限度。枚举类型的每个值只容许有一个实例,同时枚举也无奈为每个类型增加额定信息,例如,您无奈为枚举中的 "Error" 增加相干的 Exception 类型数据。 当然也能够应用一个抽象类而后让一些类继承它,这样就能够随便扩大,但这会失去枚举所带来的无限汇合的劣势。而 sealed class (本文下称 "密封类" ) 则同时蕴含了后面两者的劣势 —— 抽象类示意的灵活性和枚举里汇合的受限性。持续浏览接下来的内容能够帮忙大家更加深刻地理解密封类,您也能够点击观看 视频。 密封类的根本应用和抽象类相似,密封类可用于示意层级关系。子类能够是任意的类: 数据类、Kotlin 对象、一般的类,甚至也能够是另一个密封类。但不同于抽象类的是,您必须把层级申明在同一文件中,或者嵌套在类的外部。 // Result.ktsealed class Result<out T : Any> { data class Success<out T : Any>(val data: T) : Result<T>() data class Error(val exception: Exception) : Result<Nothing>()}尝试在密封类所定义的文件外继承类 (内部继承),则会导致编译谬误: Cannot access ‘<init>’: it is private in Result遗记了一个分支?在 when 语句中,咱们经常须要解决所有可能的类型: when(result) { is Result.Success -> { } is Result.Error -> { }}然而如果有人为 Result 类增加了一个新的类型: InProgress: ...

November 24, 2020 · 2 min · jiezi

关于kotlin:Kotlin-Vocabulary-Collection-和-Sequence

在很多场景中咱们会应用到汇合,Kotlin 规范库 (Kotlin Standard Library) 中提供了十分多杰出的对于汇合的实用函数。其中,Kotlin 提供了基于不同执行形式的两种汇合类型: 立刻执行 (eagerly) 的 Collection 类型,提早执行 (lazily) 的 Sequence 类型。本篇文章将向您介绍两者的区别,并向您介绍这两种类型别离该在哪种状况下应用,以及它们的性能体现。获取更多相干信息能够查看如下视频: https://www.bilibili.com/vide... Collection 和 Sequence 的比照立刻执行和提早执行的区别在于每次对汇合进行转换时,这个操作会在何时真正执行。 Collection (也称汇合) 是在每次操作时立刻执行的,执行后果会存储到一个新的汇合中。作用于 Collection 的转换操作是 内联函数。例如,map 的实现形式,能够看到它是一个创立了新 ArrayList 的内联函数: public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> { return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)}Sequence (也称序列) 是提早执行的,它有两种类型: 两头操作 (intermediate) 和末端操作 (terminal)。两头操作不会立刻执行,它们只是被存储起来,仅当末端操作被调用时,才会依照程序在每个元素上执行两头操作,而后执行末端操作。两头操作 (比方 map、distinct、groupBy 等) 会返回另一个Sequence,而末端操作 (比方 first、toList、count 等) 则不会。 Sequence 是不会保留对汇合我的项目的援用的。它基于原始汇合的迭代器 (iterator) 创立,并且保留要执行的所有两头操作的援用。 与在 Collection 中执行转换操作不同,Sequence 执行的两头转换不是内联函数,因为内联函数无奈存储,而 Sequence 须要存储它们。咱们能够通过下列代码看到像 map 这样的两头操作是如何实现的,能够看到转换函数会存储在一个新的 Sequence 实例中: ...

November 21, 2020 · 2 min · jiezi

关于kotlin:算法-Notes|LeetCode-14-最长公共前缀-easy

前言对于算法,集体感觉,有的只是本人辛苦,私下多思考,多画图,多了解。 没有最好的形式,也没有最快捷的法子,有的只是本人摸索中一直前行,附上之前刷题笔记: 算法 Notes|LeetCode 349. 两个数组的交加 - easy多想,多画,多练,多思考,多了解,多分享,莫慌,莫怕,一步一足迹前行。 加油~ GitHub 地址如下: https://github.com/HLQ-Strugg...14. 最长公共前缀编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀,返回空字符串 ""。 示例 1: 输出: ["flower","flow","flight"]输入: "fl"示例 2: 输出: ["dog","racecar","car"]输入: ""解释: 输出不存在公共前缀。阐明: 所有输出只蕴含小写字母 a-z 。暴力解法一 入上草图,说思路: 根本校验,输出数组为空间接 return,输出数组只有一个时,同理间接 return;取数组中第一个为基数,顺次判断数组中 char 是否相等,同时更新数组比对后果,也就是基数内容;当基数为空时,间接 return。这里首要明确,最长公共,也就是从数组中的每一个开始比对,如果呈现不一样则进行比对。当初我就是这里犯了嘀咕。 这里附上代码: fun longestCommonPrefix(strs: Array<String>): String { var resultStr = "" if (strs.isEmpty()) { return resultStr } if (strs.size == 1) { return strs[0] } resultStr = strs[0] for(str in strs){ var j = 0 while (j < resultStr.length && j < str.length){ if(str.toCharArray()[j] != resultStr.toCharArray()[j]){ break } j++ } resultStr = resultStr.substring(0,j) if(resultStr.isEmpty()){ break } } return resultStr}耗费状况: ...

November 20, 2020 · 2 min · jiezi

关于kotlin:算法-Notes-|LeetCode-349-两个数组的交集-easy

对于算法而言,生疏而神秘,却是成长经验中必经之路。 已经无数次各种找材料,寻求各种所谓的七天搞定算法秘籍,可后果都是无终而返。 其实换句话来讲,我也是搞 Android 的,有时候看到所谓 7 天让你成为 Android 大牛,也是等闲视之的。没有久远的积攒,哪儿来的大牛?已经折腾很久的货色,现在 easy 一批,说白了,还是工夫久了,写的多了。 一个人,难免会走进各种误区。经验了职场 PUA,顿悟过去,别无他求,自我积攒为上。 还是鸡老大的那句话: Just do it now.送给屏幕前的你我,共勉。 针对算法,将采取暴力 Study 法,勤能补拙!多学多练。 集体是个算法白痴,尽量欠缺每一步,不足之处,欢送吊打~ 欢送各位大佬吊打~ 附上 GitHub 地址: https://github.com/HLQ-Strugg...349. 两个数组的交加给定两个数组,编写一个函数来计算它们的交加。 示例 1: 输出:nums1 = [1,2,2,1], nums2 = [2,2]输入:[2]示例 2: 输出:nums1 = [4,9,5], nums2 = [9,4,9,8,4]输入:[9,4] 阐明: 输入后果中的每个元素肯定是惟一的。咱们能够不思考输入后果的程序。交加概念回顾 以上图为例,A、B 之间重合局部为交加 C。 例如,现有 A、B 如下: A:1 3 4 5 6B:2 3 3 4重合局部的内容为: 3 4可得出交加 C 等于 3 4。 交加的概念,用大白话的形式就是,你有我也有,这是交加。此处衍生并集则是,你的加我的,去掉反复的,就是并集。 ...

November 20, 2020 · 2 min · jiezi

关于kotlin:官方-Kotlin-课程-学习使用-Kotlin-进行-Android-开发的最佳时机

作者 / Android 开发技术推广工程师 Kat Kuan 现在,越来越多的人心愿思考可能反对近程办公的职业,而从事利用开发或者能够实现。对于心愿取得新机遇的人而言,即便过来没有编程教训,也能够立刻开始学习 Android。 咱们于 2016 年公布了 Android 基础知识课程,该课程专为零编程教训的学员打造,并且好评如潮。数万名学员一边构建本人的利用,一边学习着 Android 开发和编程概念。尔后,Android 平台产生重大变动,咱们不仅公布了四个重要的 Android 版本,新增了对 Kotlin 编程语言的反对,还推出了 Jetpack,这一整套库可帮忙开发者用更少的代码更轻松地编写优质利用。有了这些最新更新,是时候为初学者公布下一代培训内容了。 当初,咱们发表推出 Android Kotlin 基础知识 (Android Basics in Kotlin),这是专为想要学习如何构建 Android 利用的零编程教训者打造的全新线上课程。此课程将传授 Kotlin,这种古代编程语言既简洁又高效,备受开发者的青眼。Kotlin 在业内倒退迅速。Indeed Hiring Lab 发现,2018 年至 2019 年这一年期间,Kotlin 语言的职位需要增幅达到 76% (源自 Indeed.com 上美国的科技职位招聘数据)。 自 Google 发表将 Kotlin 作为 Android 开发的首选语言 (Kotlin-first),60% 的业余 Android 开发者曾经采纳了该编程语言。在 Google Play 利用商店排名前 1,000 的利用中,有 70% 的开发语言都采纳了 Kotlin。为了与时俱进并迎接将来倒退,当初正是学习应用 Kotlin 进行 Android 开发的绝佳机会。 课程简介:https://youtu.be/oSim9fBFy-E从头开始学习编程可能让您望而却步,但技术背景并不是学习的必要条件。近期的 Stack Overflow 开发者调查结果 显示,近 40% 的业余开发者并非计算机科学或软件工程业余科班出身。 ...

November 18, 2020 · 1 min · jiezi

关于kotlin:减少崩溃提升体验-使用-Kotlin-打造优质应用

作者 / Florina Muntenescu, Android Developer Advocate 每一个用户都心愿从利用中取得无缝体验。解体会导致差评减少、利用卸载,甚至有损品牌认可度。与社区交换后,咱们理解到开发者采纳 Kotlin 的次要起因之一是为了更平安的代码。我将在本文中讲述 Kotlin 进步开发者代码稳定性的几种形式,也会通过 Google Play 商店统计的后果,看看应用 Kotlin 与解体数量之间是否有相关性 (剧透一下: 当然有!)。 利用品质利用品质不仅影响着用户体验,利用的大量解体还会影响一些其余方面: 利用曝光度  - Google Play 商店举荐由人工策动和算法计算共同完成,其中品质是最大的考量因素之一。品牌  - 产品体现会影响评分和评论,从而影响品牌认可度。更多的 (参加) 用户数量  - 更好的天然搜寻数据和品牌认可度能够带来更好的用户获取和留存,这也会影响参与度和升高漏斗指标。应用 Kotlin 构建的利用呈现解体的可能性升高了 20%。 Kotlin 在其中表演了什么角色?咱们钻研了 Google Play 排名前 1,000 的利用,发现应用 Kotlin 的利用与不应用 Kotlin 的利用相比,其用户解体率低 20%。 比方 Kotlin 的空安全性就让点评的 Android 利用团队拍案叫绝,而且团队开发者还能够通过在 Java 代码中应用 @Nullable 和 @NonNull 等注解来确保 Kotlin 代码取得正确的可空性推断。整体上看,Kotlin 的空平安个性帮忙点评 Android 利用将空指针导致的解体从日均 3 个升高至 0。 ...

November 11, 2020 · 1 min · jiezi

关于kotlin:R8-编译器-为-Kotlin-库和应用-瘦身

作者 / Morten Krogh-Jespeersen, Mads Ager R8 是 Android 默认的程序缩减器,它能够通过移除未应用的代码和优化其余代码的形式升高 Android 利用大小,R8 同时也反对缩减 Android 库大小。除了生成更小的库文件,库压缩操作还能够暗藏开发库里的新个性,等到这些个性绝对稳固或者能够面向公众的时候再对外开放。 Kotlin 对于编写 Android 利用和开发库来说是十分棒的开发语言。不过,应用 Kotlin 反射来缩减 Kotlin 开发库或者利用就没那么简略了。Kotlin 应用 Java 类文件中的元数据 来辨认 Kotlin 语言中的构造。如果程序缩减器没有保护和更新 Kotlin 的元数据,相应的开发库或者利用就无奈失常工作。 R8 当初反对维持和重写 Kotlin 的元数据,从而全面反对应用 Kotlin 反射来压缩 Kotlin 开发库和利用。该个性实用于 Android Gradle 插件版本 4.1.0-beta03。欢送大家踊跃尝试,并在 Issue Tracker 页面 向咱们反馈整体应用感触和遇到的问题。 本文接下来的内容为大家介绍了 Kotlin 元数据的相干信息以及 R8 中对于重写 Kotlin 元数据的反对。 Kotlin 元数据Kotlin 元数据 是存储在 Java 类文件的注解中的一些额定信息,它由 Kotlin JVM 编译器生成。元数据确定了类文件中的类和办法是由哪些 Kotlin 代码形成的。比方,Kotlin 元数据能够通知 Kotlin 编译器类文件中的一个办法实际上是 Kotlin 扩大函数。 ...

November 9, 2020 · 3 min · jiezi

关于kotlin:在-Kotlin-中使用-Dagger-会遇到的陷阱和优化方法

Dagger 在 Android 开发中相当风行,它是一个提供齐全动态和在编译时生成代码的依赖注入框架,它解决了很多基于反射而实现的计划中所遇到的开发和性能问题。 为了让您更好地理解 Dagger 的工作原理,咱们于 2019 年公布了一个 新的教程。本文将重点介绍如何 在 Kotlin 中应用 Dagger ,包含优化构建工夫的 最佳实际 以及一些可能会遇到的问题。 Dagger 是通过 Java 的注解模型实现的,而 Kotlin 中注解的编写形式同 Java 的并不是一一对应的,这篇文章会重点介绍它们之间的不同之处,并且会介绍怎么轻松地将 Dagger 同 Kotlin 联合起来应用。 本文的写作灵感来自 Dagger issue 中的一些倡议,这些倡议间接代表了在 Kotlin 中应用 Dagger 的最佳实际和一些痛点。在此要感激所有的 issue 贡献者。 进步构建效率为了缩短构建工夫,Dagger 在 v2.18 版本中新增了 对 gradle 增量注解解决  (gradle’s incremental annotation processing) 的反对。在 Dagger v2.24 版本中这个性能是默认启用的。如果您应用的是较低版本,您须要增加以下几行代码来激活该性能。 另外,您能够配置 Dagger 让它不要格式化生成的代码。这一选项是在 Dagger v2.18 版本中增加的,并且是 v2.23 版本中的默认行为 (不再生成格式化代码)。如果您应用的是较低的版本,同样能够增加上面的代码来禁用格式化代码以缩短构建工夫。 在 build.gradle 中增加以下编译参数来进步 Dagger 在构建时的性能: allprojects { afterEvaluate { extensions.findByName('kapt')?.arguments { arg("dagger.formatGeneratedSource", "disabled") arg("dagger.gradle.incremental", "enabled") } }}另外,如果您应用的是 Kotlin DSL 脚本文件,那么您须要在 build.gradle.kts 文件中蕴含以下内容: ...

November 1, 2020 · 2 min · jiezi

关于kotlin:kotlin-简要总结

为适应Android的倒退,打算当前的我的项目逐渐采样Kotlin开发,现对kotlin应用做一个小结,内容摘自《第一行代码》及菜鸟教程-Kotlin。 1、变量Kotlin定义一个变量,只容许在变量前申明两种关键字:val和var。val用来申明一个不可变的变量,var用来申明一个可变的变量。 var <标识符> : <类型> = <初始化值> var a: Int = 1val <标识符> : <类型> = <初始化值> val b = 1编译器反对主动类型判断,申明时能够不指定类型。Tips:优先应用val来申明一个变量,当val没法满足需要时再应用var。2、函数语法规定如下 fun methodName(param1: Int, param2: Int): Int { return 0}fun是定义函数的关键字,参数括号那局部用于申明函数会返回什么类型的数据,如不需返回任何数据能够间接不写。当函数中只有一行代码时,不用编写函数题,能够将惟一的一行代码写在函数定义的尾部,两头用等号连贯即可。 fun largeNumber(num1: Int, num2: Int) = max(num1, num2)3、逻辑管制when条件语句when语句容许传入一个任意类型的参数,而后能够在when的构造体定义一系列的条件:匹配值 -> { 执行逻辑 } fun getScore(name: String) = when(name) { "Tom" -> 86 "Jim" -> 77 else -> 0}

September 5, 2020 · 1 min · jiezi

关于kotlin:从Lombok迁移到Kotlin

原文地址: https://dzone.com/articles/mi...更短的代码不是目标,只有更可读的代码才是 作为一个Java开发者,最常见的埋怨是对Java语言简短的埋怨。而其中呈现最多的就是数据类。 数据类,或者元祖,或者record记录类,将来在Java语言可能会隐没,但在那天之前,任何工夫创立一个rest dto, jpa实体,畛域对象,或者任何相似的,Java的冗余就呈现了。在这篇文章里,我会介绍如何从Lombok迁徙到Kotlin,以及从迁徙中能取得的收益。 // 40 Lines of Java code for a class with 2 propertiesimport java.time.LocalDate;import java.util.Objects;public class Person { private String name; private LocalDate dateOfBirth; public Person(String name, LocalDate dateOfBirth) { this.name \= name; this.dateOfBirth \= dateOfBirth; } public String getName() { return name; } public LocalDate getDateOfBirth() { return dateOfBirth; } @Override public boolean equals(Object o) { if (this \== o) return true; if (o \== null || getClass() != o.getClass()) return false; Person person \= (Person) o; return Objects.equals(name, person.name) && Objects.equals(dateOfBirth, person.dateOfBirth); } @Override public int hashCode() { return Objects.hash(name, dateOfBirth); } @Override public String toString() { return "Person{" + "name='" + name + '\\'' + ", dateOfBirth=" + dateOfBirth + '}'; }}要无效的应用数据类,你常常须要一组属性;一个构造函数,一组getter;兴许也会有equals; hashcode和toString办法;另外在一些状况下,还有邪恶的setter到处都是。因为这是个常见问题,一些解决方案呈现了 - Lombok是比拟出名的,但其余还有AutoValue与Immutables。 ...

August 2, 2020 · 2 min · jiezi

关于kotlin:从Lombok迁移到Kotlin

原文地址: https://dzone.com/articles/mi...更短的代码不是目标,只有更可读的代码才是 作为一个Java开发者,最常见的埋怨是对Java语言简短的埋怨。而其中呈现最多的就是数据类。 数据类,或者元祖,或者record记录类,将来在Java语言可能会隐没,但在那天之前,任何工夫创立一个rest dto, jpa实体,畛域对象,或者任何相似的,Java的冗余就呈现了。在这篇文章里,我会介绍如何从Lombok迁徙到Kotlin,以及从迁徙中能取得的收益。 // 40 Lines of Java code for a class with 2 propertiesimport java.time.LocalDate;import java.util.Objects;public class Person { private String name; private LocalDate dateOfBirth; public Person(String name, LocalDate dateOfBirth) { this.name \= name; this.dateOfBirth \= dateOfBirth; } public String getName() { return name; } public LocalDate getDateOfBirth() { return dateOfBirth; } @Override public boolean equals(Object o) { if (this \== o) return true; if (o \== null || getClass() != o.getClass()) return false; Person person \= (Person) o; return Objects.equals(name, person.name) && Objects.equals(dateOfBirth, person.dateOfBirth); } @Override public int hashCode() { return Objects.hash(name, dateOfBirth); } @Override public String toString() { return "Person{" + "name='" + name + '\\'' + ", dateOfBirth=" + dateOfBirth + '}'; }}要无效的应用数据类,你常常须要一组属性;一个构造函数,一组getter;兴许也会有equals; hashcode和toString办法;另外在一些状况下,还有邪恶的setter到处都是。因为这是个常见问题,一些解决方案呈现了 - Lombok是比拟出名的,但其余还有AutoValue与Immutables。 ...

August 2, 2020 · 2 min · jiezi

关于kotlin:从Lombok迁移到Kotlin

原文地址: https://dzone.com/articles/mi...更短的代码不是目标,只有更可读的代码才是 作为一个Java开发者,最常见的埋怨是对Java语言简短的埋怨。而其中呈现最多的就是数据类。 数据类,或者元祖,或者record记录类,将来在Java语言可能会隐没,但在那天之前,任何工夫创立一个rest dto, jpa实体,畛域对象,或者任何相似的,Java的冗余就呈现了。在这篇文章里,我会介绍如何从Lombok迁徙到Kotlin,以及从迁徙中能取得的收益。 // 40 Lines of Java code for a class with 2 propertiesimport java.time.LocalDate;import java.util.Objects;public class Person { private String name; private LocalDate dateOfBirth; public Person(String name, LocalDate dateOfBirth) { this.name \= name; this.dateOfBirth \= dateOfBirth; } public String getName() { return name; } public LocalDate getDateOfBirth() { return dateOfBirth; } @Override public boolean equals(Object o) { if (this \== o) return true; if (o \== null || getClass() != o.getClass()) return false; Person person \= (Person) o; return Objects.equals(name, person.name) && Objects.equals(dateOfBirth, person.dateOfBirth); } @Override public int hashCode() { return Objects.hash(name, dateOfBirth); } @Override public String toString() { return "Person{" + "name='" + name + '\\'' + ", dateOfBirth=" + dateOfBirth + '}'; }}要无效的应用数据类,你常常须要一组属性;一个构造函数,一组getter;兴许也会有equals; hashcode和toString办法;另外在一些状况下,还有邪恶的setter到处都是。因为这是个常见问题,一些解决方案呈现了 - Lombok是比拟出名的,但其余还有AutoValue与Immutables。 ...

August 2, 2020 · 2 min · jiezi

关于kotlin:kotlin中的使用小技巧总结

1.kotlin中lateinit和by lazy的区别lazy { ... }只能被用在被val润饰的变量上,而lateinit只能被用var润饰的变量上,因为被lateinit润饰的字段无奈被编译为一个final字段、因而无奈保障它的不可变性。被lateinit润饰的变量有一个幕后字段用来存储它的值,而by lazy { ... }创立了一个蕴含by lazy { ... }中代码返回值的实例对象,实例对象持有这个值并生成一个能够在实例对象中调用的这个值的getter。所以如果你须要在代码中应用幕后字段的话,应用lateinit被lateinit润饰的变量能够在对象(代码)的任何中央进行初始化,而且同一个类的不同对象能够对这个变量进行屡次的初始化(赋值)。然而,对于by lazy { ... }润饰的变量,只领有惟一一个申明在{}中的初始化结构器,如果你想要批改它,你只能通过在子类中覆写的形式来批改它的值。所以,如果你想要你的属性在其余中央以不是你当时定义好的值初始化的话,应用lateinit by lazy { ... }的初始化默认是线程平安的,并且能保障by lazy { ... }代码块中的代码最多被调用一次。而lateinit var默认是不保障线程平安的,它的状况齐全取决于使用者的代码。Lazy实例是有值的,这个值能够被存储、传递和应用。然而,被lateinit var润饰的变量不存储任何多余的运行时状态,只有值还未被初始化的null值。如果你持有一个Lazy实例的援用,你能够应用它的isInitialized()办法来判断它是否曾经被初始化。从Kotlin1.2开始,你也能够应用办法援用的形式来获取这个值。by lazy { ... }中传递的lambda表达式可能会捕捉它的闭包中应用的上下文的援用,援用会始终被持有直到变量被初始化。因而这样可能会导致内存透露,所以认真思考你在lambda表达式中应用的值是否正当。原文链接:https://stackoverflow.com/que... Here are the significant differences between lateinit var and by lazy { ... } delegated property:lazy { ... } delegate can only be used for val properties, whereas lateinit can only be applied to vars, because it can't be compiled to a final field, thus no immutability can be guaranteed;lateinit var has a backing field which stores the value, and by lazy { ... } creates a delegate object in which the value is stored once calculated, stores the reference to the delegate instance in the class object and generates the getter for the property that works with the delegate instance. So if you need the backing field present in the class, use lateinit;In addition to vals, lateinit cannot be used for non-nullable properties and Java primitive types (this is because of null used for uninitialized value);lateinit var can be initialized from anywhere the object is seen from, e.g. from inside a framework code, and multiple initialization scenarios are possible for different objects of a single class. by lazy { ... }, in turn, defines the only initializer for the property, which can be altered only by overriding the property in a subclass. If you want your property to be initialized from outside in a way probably unknown beforehand, use lateinit.Initialization by lazy { ... } is thread-safe by default and guarantees that the initializer is invoked at most once (but this can be altered by using another lazy overload). In the case of lateinit var, it's up to the user's code to initialize the property correctly in multi-threaded environments.A Lazy instance can be saved, passed around and even used for multiple properties. On contrary, lateinit vars do not store any additional runtime state (only null in the field for uninitialized value).If you hold a reference to an instance of Lazy, isInitialized() allows you to check whether it has already been initialized (and you can obtain such instance with reflection from a delegated property). To check whether a lateinit property has been initialized, you can use property::isInitialized since Kotlin 1.2.A lambda passed to by lazy { ... } may capture references from the context where it is used into its closure.. It will then store the references and release them only once the property has been initialized. This may lead to object hierarchies, such as Android activities, not being released for too long (or ever, if the property remains accessible and is never accessed), so you should be careful about what you use inside the initializer lambda.Also, there's another way not mentioned in the question: Delegates.notNull(), which is suitable for deferred initialization of non-null properties, including those of Java primitive types.在我的项目中的理论使用示例: ...

July 24, 2020 · 5 min · jiezi

关于kotlin:除了Android开发Kotlin-还能做什么六款优质Kotlin项目分享

Kotlin 语言 2011 年由 JetBrains 推出,2012 年开源,2017 年成为 Android 官网开发语言,并于 2019 年成为 Andoid 开发官网首选语言。凭借其原生反对 Java 以及更少代码量的劣势,也有越来越多的开发者投向 Kotlin 的怀抱,同时 Kotlin 在其余畛域的利用也越来越宽泛,明天就为大家介绍六款优质的 Kotlin 我的项目。 1.AndroidZdog我的项目作者:prostory 开源许可协定:MIT 我的项目地址:https://gitee.com/prostory/AndroidZdog Android平台上的伪3D图形动画引擎Zdog,应用kotlin编写。 2.HiWeather我的项目作者:ZhiyuanLing 开源许可协定:GPL-3.0 我的项目地址:https://gitee.com/vitoling/HiWeather 一个应用 Kotlin 语言开发的天气网站,其余应用的技术包含 SpringBoot、Webmagic等。 3.wechat-miniprogram-plugin我的项目作者:zxy 开源许可协定:MulanPSL-1.0 我的项目地址:https://gitee.com/zxy_c/wechat-miniprogram-plugin 基于JetBrains平台的微信小程序插件。 4.bk-ci我的项目作者:腾讯蓝鲸智云 开源许可协定:MIT 我的项目地址:https://gitee.com/Tencent-BlueKing/bk-ci bk-ci是一个收费并开源的CI服务,可助你自动化构建-测试-公布工作流,继续、疾速、高质量地交付你的产品。 5.Twobbble我的项目作者:生存以上生存以下 开源许可协定:Apache-2.0 我的项目地址:https://gitee.com/550609334/Twobbble 这是一个齐全应用Kotlin开发,小而美的Dribbble客户端。 6.OKBook我的项目作者:Xiaolei123 开源许可协定:Apache-2.0 我的项目地址:https://gitee.com/xcode_xiao/OKBook kotlin + 协程 + MVVM 模式来编写的看小说APP。 以上六款开源我的项目不晓得是否让大家更理解 Kotlin,如果你还想在 Gitee 上看到更多 Kotlin 我的项目,那么就点击前面的链接去看看吧:https://gitee.com/explore/all?lang=Kotlin&order=starred

July 23, 2020 · 1 min · jiezi

关于kotlin:kotlin基础

记录一下与java相比的一些根底重要的点 1.基础知识 1.1 根本类型 kotlin中没有java根本类型的int、float、double等,所有货色都是对象,这与java相似。然而kotlin对数字没有隐式拓宽转换,须要显示转换;数字字面量不反对八进制。 1.2 包与导入 应用import关键字,性能上与java差不多。import不限于导入类,还能够导入申明如枚举常量。不同的是 没有相似import static的性能。 1.3 控制流 应用if、when、for、while when取代了switch性能,然而比switch弱小,能够多条件一起,逗号分隔 when (x) { 0, 1 -> print("x == 0 or x == 1") else -> print("otherwise")}能够应用任意表达式,而不只是常量;能够检测一个值是否在一个区间 when (x) { in 1..10 -> print("x is in the range") in validNumbers -> print("x is valid") !in 10..20 -> print("x is outside the range") else -> print("none of the above")}for/while的应用跟java有点相似,for循环能够对迭代器对象进行遍历,与in应用。 1.4 返回和跳转 return、continue、break 与java的性能一样,加了标签性能。kotlin的表达式都能够用标签(Label)来标记。标签=标识符+@,性能是记录地址,来跳转到相应地位。 1.5 类与对象 ...

July 20, 2020 · 3 min · jiezi

KotlinVueSpring-Data-JPAMySQL-增查改删

概述:Kotlin为后端开发语言,长久层是Spring Data JPA前后端拆散,进行简略增查改删(CRUD)前端应用VUE数据库应用MySQL Vue前端代码,不再反复。以下是Kotlin后盾代码 EmployeeController.ktpackage com.example.kotlinjpacrud.controllerimport com.example.kotlinjpacrud.entity.Employeeimport com.example.kotlinjpacrud.repositories.EmployeeRepositoryimport org.springframework.data.domain.Pageimport org.springframework.data.domain.Pageableimport org.springframework.data.domain.Sortimport org.springframework.data.web.PageableDefaultimport org.springframework.http.HttpStatusimport org.springframework.http.ResponseEntityimport org.springframework.web.bind.annotation.*import javax.validation.Valid@RestController@RequestMapping("/api/employee")class EmployeeController(private val employeeRepository: EmployeeRepository) { /** * 获取所有员工分页 * 以字段Id为降序 * 没有为3条记录 */ @GetMapping fun getAllEmployees(@PageableDefault(sort = ["id"], direction = Sort.Direction.DESC, size = 3) pageable: Pageable): Page<Employee> { return employeeRepository.findAll(pageable) } /** * 新增员工 */ @PostMapping fun createEmployee(@Valid @RequestBody employee: Employee): Employee { return employeeRepository.save(employee) } /** * 依据ID获取员工 */ @GetMapping("/{id}") fun getEmployeeById(@PathVariable(value = "id") employeeId: Long): ResponseEntity<Employee> { return employeeRepository.findById(employeeId) .map { employee -> ResponseEntity.ok(employee) } .orElse(ResponseEntity.notFound().build()) } /** * 批改员工 */ @PutMapping fun updateEmployeeById(@Valid @RequestBody newEmployee: Employee): ResponseEntity<Employee> { return employeeRepository.findById(newEmployee.id) .map { existingArticle -> val updatedArticle: Employee = existingArticle .copy(name = newEmployee.name, gender = newEmployee.gender, age = newEmployee.age, introduce = newEmployee.introduce) ResponseEntity.ok().body(employeeRepository.save(updatedArticle)) }.orElse(ResponseEntity.notFound().build()) } /** * 依据ID删除 */ @DeleteMapping("/{id}") fun deleteEmployeeById(@PathVariable(value = "id") employeeId: Long): ResponseEntity<Void> { return employeeRepository.findById(employeeId) .map { deleteEmployee -> employeeRepository.delete(deleteEmployee) ResponseEntity<Void>(HttpStatus.OK) }.orElse(ResponseEntity.notFound().build()) }}Employee.ktpackage com.example.kotlinjpacrud.entityimport com.example.kotlinjpacrud.enums.Genderimport javax.persistence.Entityimport javax.persistence.GeneratedValueimport javax.persistence.Id@Entitydata class Employee( @Id @GeneratedValue var id: Long =0, var name: String ="", var gender: Gender = Gender.MALE, var age: Int =0, var introduce: String ="")EmployeeRepository.tkpackage com.example.kotlinjpacrud.repositoriesimport com.example.kotlinjpacrud.entity.Employeeimport org.springframework.data.jpa.repository.JpaRepositoryimport org.springframework.stereotype.Repository@Repositoryinterface EmployeeRepository :JpaRepository<Employee,Long> {}

July 10, 2020 · 1 min · jiezi

Kotlin-Hello-World

1 KotlinKotlin是一种在JVM上运行的静态类型编程语言,被称为Android界的Wsift,由JetBrains设计。Kotline可以编译成Java字节码,也可以编译成JavaScript,方便在没有JVM的设备上运行。Google宣布在Google I/O 2017上宣布Kotlin成为Android官方语言。 笔者不是专攻Android的,是做服务端的,尽管目前大部分都是使用Java做后端,但是也有一些Kotlin做后端的资料,比如Kotlin结合Spring Boot的也有不少文章,因此笔者决定使用Kotlin进行后端开发。 那么,先从Hello world开始。 2 新建工程IDE用的是IDEA,新建工程并选择Kotlin:项目名:结构应该长这样(居然连个Main都没有。。。): 3 Main在src下新建一个Main.kt:代码在图中就不再贴一次了。当然可能有人会问Stirng [] args去哪里了,那就把它加上:IDEA提示从Kotlin1.3开始main参数不是必要的,因此把它去掉了。不过老实说Kotlin比起Java还真的简洁。 4 添加运行配置选择Add Configuration,接着选择Kotlin:在Main class:处输入默认包MainKt: 5 运行Shift+F10或者点击绿色小箭头运行即可。

June 26, 2020 · 1 min · jiezi

Kotlin学习笔记

1 概述这篇文章首先会介绍Kotlin的特点,接着介绍Kotlin与Java的语法比较。 2 Kotlin特点一门现代化的编程语言可开发跨平台应用,web,Socket,安卓,js,NativeApp等静态编程语言,性能基本与原声Java相当100%兼容Java(说是兼容但实际上有些坑,可以戳这里看看)简洁:跟Java相比真的是简洁很多,语法糖特别舒服安全:彻底解决写Java基本上都会遇到的著名的NullPointerException问题,结合编译器可以在编译截断发现几乎所有可能存在NPE问题的代码互操作性:基于JVM,可以直接拿现有的Java库用工具友好:和JetBrains的IDE结合简直舒服得不要不要的支持函数式编程:比如Lambda表达式支持协程:协程像是非常轻量级的县城,协程将复杂性放入库来简化异步编程,逻辑可以在协程中顺序表达,底层库负责解决异步性,很重要的一点是协程挂起不会阻塞其他线程。官方一个demo是开启10w个协程: 支持扩展函数:类似C#,能够扩展一个类的新功能而无需继承类或者使用装饰者这样的设计模式,Kotlin支持扩展函数和扩展属性泛型:当然Java也支持泛型,但是Kotlin比Java支持得更好不依赖XML下面进入Kotlin的语法部分。 3 基本语法无;结尾println()代替System.out.println();输出语句中使用$变量名代替Java中的+变量名,比如println("age:$age")而不是System.out.println("age:"+age)三引号(三个双引号连在一起)中的字符串不会进行转义4 变量与常量var声明变量val声明常量可以在var/val后面加上类型,比如val a:Int如上图提示val不能被赋值,提示改为var。val类似与Java中的final,虽然val引用自身不可变,但是指向的对象是可以改变的。 val只能进行唯一一次初始化,如果编译器能确保只有唯一一条初始化语句被执行,可以根据条件进行不同的初始化操作: val a:Intif (4>3){ a = 9}else{ a = 10}5 表达式和语句Java中所有的控制结构都是语句,在Kotlin中除了三大循环(while,for,do while)外,大多数控制结构都是表达式。比如if是表达式而不是语句。也就是说,if有值而不像Java里面一样没有值(语句)。例子: var a = if (3>2) 3 else 2fun main(){ var a = max(4,9)}fun max(a:Int,b:Int): Int = if(a>b) a else b6 枚举使用enum class而不是Java中的enum: fun main(){ val months = Months.May println(months.days)}enum class Months(val days:Int){ May(31), Jun(30)}7 whenwhen相当于Java中的switch: fun main(){ val months = Months.May when(months) { Months.May -> print("May") Months.Jun -> print("June") }}enum class Months(val days:Int){ May(31), Jun(30),;}使用->进行了简化。 ...

June 26, 2020 · 2 min · jiezi

浅谈Kotlin中的函数

本文首发于 vivo互联网技术 微信公众号  链接:https://mp.weixin.qq.com/s/UV23Uw_969oVhiOdo4ZKAw 作者:连凌能Kotlin,已经被Android官方宣布 kotlin first 的存在,去翻 Android 官方文档的时候,发现提供的示例代码已经变成了 Kotlin。Kotlin的务实作风,提供了很多特性帮助开发者减少冗余代码的编写,可以提高效率,也能减少异常。 本文简单谈下Kotlin中的函数,包括表达式函数体,命名参数,默认参数,顶层函数,扩展函数,局部函数,Lambda表达式,成员引用,with/apply函数等。从例子入手,从一般写法到使用特性进行简化,再到原理解析。 1.表达式函数体通过下面这个简单的例子看下函数声明相关的概念,函数声明的关键字是fun,嗯,比JS的function还简单。 Kotlin中参数类型是放在变量:后面,函数返回类型也是。 fun max(a: Int, b: Int) : Int { if (a > b) { return a } else { return b }}当然, Kotlin是有类型推导功能,如果可以根据函数表达式推导出类型,也可以不写返回类型。 但是上面的还是有点繁琐,还能再简单,在 Kotlin中if是表达式,也就是有返回值的,因此可以直接return,另外判断式中只有一行一句也可以省略掉大括号: fun max(a: Int, b: Int) { return if (a > b) a else b}还能在简单点吗?可以,if是表达式,那么就可以通过表达式函数体返回: fun max(a: Int, b: Int) = if(a > b) a else b最终只需要一行代码。 Example 再看下面这个例子,后面会基于这个例子进行修改。这个函数把集合以某种格式输出,而不是默认的toString()。 <T>是泛型,在这里形参集合中的元素都是T类型。返回String类型。fun <T> joinToString( ...

November 4, 2019 · 6 min · jiezi

Kotlin协程教程1启动

协程协程简单的来说,就是用户态的线程。 emmm,还是不明白对吧,那想象一个这样的场景,如果在一个单核的机器上有两个线程需要执行,因为一次只能执行一个线程里面的代码,那么就会出现线程切换的情况,一会需要执行一下线程A,一会需要执行一下线程B,线程切换会带来一些开销。 假设两个线程,交替执行,如下图所示 线程会因为Thread.sleep方法而进入阻塞状态(就是什么也不会执行),这样多浪费资源啊。 能不能将代码块打包成一个个小小的可执行片段,由一个统一的分配器去分配到线程上去执行呢,如果我的代码块里要求sleep一会,那么就去执行别的代码块,等会再来执行我呢。 协程就是这样一个东西,我们作为使用者不需要再去考虑创建一个新线程去执行一坨代码,也不需要关心线程怎么管理。我们需要关心的是,我要异步的执行一坨代码,待会我要拿到它的结果,我要异步的执行很多坨代码,待会我要按某种顺序,或者某种逻辑得到它们的结果。 总而言之,协程是用户态的线程,它是在用户态实现的一套机制,可以避免线程切换带来的开销,可以高效的利用线程的资源。 从代码上来讲,也可以更漂亮的写各种异步逻辑。 这里想再讲讲一个概念,阻塞与非阻塞是什么意思 阻塞与非阻塞简单来说,阻塞就是不执行了,非阻塞就是一直在执行。比如 Thread.wait() // 阻塞了// 这里执行不到了但是,如果 while (true) { // 一直在运行,没有阻塞 i++;}// 这里也执行不到了runBlocking:连接阻塞与非阻塞的世界runBlocking是启动新协程的一种方法。 runBlocking启动一个新的协程,并阻塞它的调用线程,直到里面的代码执行完毕。 举个例子 println("aaaaaaaaa ${Thread.currentThread().name}")runBlocking { for (i in 0..10) { println("$i ${Thread.currentThread().name}") delay(100) }}println("bbbbbbbbb ${Thread.currentThread().name}")上面代码的输出为: aaaaaaaaa main0 main1 main2 main3 main4 main5 main6 main7 main8 main9 main10 mainbbbbbbbbb mainemmm,这并没有什么稀奇,所有的代码都在主线程执行,按照顺序来,去掉runBlocking也是一样的嘛。 但是,runBlocking可以指定参数,就可以让runBlocking里面的代码在其他线程执行,但同样可以阻塞外部线程。 println("aaaaaaaaa ${Thread.currentThread().name}")runBlocking(Dispatchers.IO) { // 注意这里 for (i in 0..10) { println("$i ${Thread.currentThread().name}") delay(100) }}println("bbbbbbbbb ${Thread.currentThread().name}")上面的代码,给runBlocking添加了一个参数,Dispatchers.IO,这样里面的代码块就会执行到其他线程了。 ...

August 28, 2019 · 2 min · jiezi

ReentrantLock-实现原理笔记一

java.util.concurrent.locks.ReentrantLockexclusive : adj. (个人或集体) 专用的,专有的,独有的,独占的; 排外的; 不愿接收新成员(尤指较低社会阶层)的; 高档的; 豪华的; 高级的 reentrant : 可重入; 可重入的; 重入; 可再入的; 重进入 一切从 Thread 线程开始 独占线程 exclusiveOwnerThread 出场: package java.util.concurrent.locks;/** * A synchronizer that may be exclusively owned by a thread. This * class provides a basis for creating locks and related synchronizers * that may entail a notion of ownership. The * {@code AbstractOwnableSynchronizer} class itself does not manage or * use this information. However, subclasses and tools may use * appropriately maintained values to help control and monitor access * and provide diagnostics. * * @since 1.6 * @author Doug Lea */public abstract class AbstractOwnableSynchronizer implements java.io.Serializable { /** Use serial ID even though all fields transient. */ private static final long serialVersionUID = 3737899427754241961L; /** * Empty constructor for use by subclasses. */ protected AbstractOwnableSynchronizer() { } /** * The current owner of exclusive mode synchronization. */ private transient Thread exclusiveOwnerThread; /** * Sets the thread that currently owns exclusive access. * A {@code null} argument indicates that no thread owns access. * This method does not otherwise impose any synchronization or * {@code volatile} field accesses. * @param thread the owner thread */ protected final void setExclusiveOwnerThread(Thread thread) { exclusiveOwnerThread = thread; } /** * Returns the thread last set by {@code setExclusiveOwnerThread}, * or {@code null} if never set. This method does not otherwise * impose any synchronization or {@code volatile} field accesses. * @return the owner thread */ protected final Thread getExclusiveOwnerThread() { return exclusiveOwnerThread; }}这里的获取当前锁的独占线程的方法是 final 的: ...

July 12, 2019 · 15 min · jiezi

Flutter个人填坑指南详解

Flutter个人填坑指南详解第一步安装解压完flutter后,按照flutter的官方教程,首先需要在你的IDE或者编译器(vscode)里安装插件,分别是 flutter 和dart的插件(我使用的是AS,所以下文以AS为例) 第二步---配置环境变量由于在国内访问Flutter有时可能会受到限制,Flutter官方为中国开发者搭建了临时镜像,大家可以将如下环境变量加入到用户环境变量中 第三步进入Flutter的目录中,运行命令行脚本 第一个问题!!!!运行flutter doctor后,你会发现它提示你✗ Android license status unknown. 因此我们应该尝试运行flutter doctor --android-licenses 但是会报错,提示你应该去 sdk目录进行 sdkmanager --update,运行sdkmanager --update时又会出现找不到或无法加载主类的问题 解决方案经过不断的google,在GitHub flutter的i16025 issues中 有人提到 这是jdk版本的问题,原文(OpenJDK 10 was superseeded by OpenJDK 11, which doesn't implement java.se.ee at all. This means that the hack of adding --add-modules java.se.ee doesn't do anything anymore. It also means that OpenJDK 10 will be automatically removed from your system and replaced with OpenJDK 11 the next time you update, if your updates are configured properly. ...

July 10, 2019 · 1 min · jiezi

工作日志多租户模式下的数据备份和迁移

工作日志,多租户模式下的数据备份和迁移 记录和分享一篇工作中遇到的奇难杂症。目前做的项目是多租户模式。一套系统管理多个项目,用户登录不同的项目加载不同的数据。除了一些系统初始化的配置表外,各项目之间数据相互独立。前期选择了共享数据表的隔离方案,为后期的数据迁移挖了一个大坑。这里记录填坑的思路。可能不优雅,仅供参考。 多租户多租户是一种软件架构,在同一台(组)服务器上运行单个实例,能为多个租户提供服务。以实际例子说明,一套能源监控系统,可以为A产业园提供服务,也可以为B产业园提供服务。A的管理员登录能源监控系统只会看到A产业园相关的数据。同样的道理,B产业园也是一样。多住户模式最重要的就是数据之间的独立。其最大的局限性在于对租户定制化开发困难很大。适合通用的业务场景。 数据隔离方案独立数据库顾名思义,一个租户独享一个数据库,其隔离级别最强,数据安全性最高,数据的备份和恢复最方便。对数据独立性要求很高,数据的扩张性要求较多的租户可以考虑使用。或者钱给的多也可以考虑。毕竟该模式下的硬件成本较高。代码成本较低,Hibernate已经提供DATABASE的实现。 共享数据库、独立 Schema多个租户共有一个数据库,每个租户拥有属于自己的Schema(Schema表示数据库对象集合,它包含:表,视图,存储过程,索引等等对象)。其隔离级别较强,数据安全性较高,数据的备份和恢复较为麻烦。数据库出了问题会影响到所有租户。Hibernate也提供SCHEMA的实现。 共享数据库、共享 Schema、共享数据表多个租户共享一个数据库,一个Schema,一张数据表。各租户之间通过字段区分。其隔离级别最低,数据安全性最低,数据的备份和恢复最麻烦(让我哭一分钟????)。若一张表出现问题会影响到所有租户。其代码工作量也是最多,因为Hibernate(5.0.3版本)并没有支持DISCRIMINATOR模式,目前还只是计划支持。其模式最大的好处就是用最少的服务器支持最多的租户。 业务场景在我们的能源管理的系统中,多个租户就是多个项目。将需要数据独立的数据表通过ProjectID区分。而一些系统初始化的配置表则可以数据共享。怎么用尽可能少的代码来管理每个租户呢?这里提出我个人的思路。 多租户的实现第一步:用户登录时获取当前项目,并保存到上下文中。 第二步:通过EntityListeners注解监听,在实体被创建时将当前项目ID保存到数据库中。 第三步:通过自定义拦截器,拦截需要数据隔离的sql语句,重新拼接查询条件。 将当前项目保存到上下文中,不同的安全框架实现的方法也有所不同,实现的方式也多种多样,这里就不贴出代码。 通过EntityListeners注解可以对实体属性变化的跟踪,它提供了保存前,保存后,更新前,更新后,删除前,删除后等状态,就像是拦截器一样。这里我们可以用到PrePersist 在保存前将项目ID赋值 @MappedSuperclass@EntityListeners(ProjectIdListener::class)@Pokoclass TenantModel: AuditModel() { var projectId: String? = null}class ProjectIdListener { @PrePersist fun setProjectId(resultObj: Any) { try { val projectIdProperty = resultObj::class.java.superclass.getDeclaredField("projectId") if (projectIdProperty.type == String::class.java) { projectIdProperty.isAccessible = true projectIdProperty.set(resultObj, ContextUtils.getCurrentProjectId()) } else { } } catch (ex: Exception) { } }}自定义SQL拦截器,通过实现StatementInspector接口,实现inspect方法即可。不同的业务逻辑,实现的逻辑也不一样,这里就不贴代码了。 注意: 一)、以上是kotlin代码,IDEA支持Kotlin和Java代码的互转。 二)、需要数据隔离的实体,继承TenantModel类即可,没有继承的实体默认为数据共享。 三)、ContextUtils是自定义获取上下文的工具类。 数据备份业务分析到了文章的重点。数据的备份目的是数据迁移和数据的还原。友好的备份格式可以为数据迁移减少很多工作量。刚开始觉得这个需求很简单,MySQL的数据备份做过很多次,也很简单。但数据备份不仅仅是数据恢复,还有数据迁移的功能(A项目下的数据备份后,可以导入的B项目下)。这下就有意思了。我们理一理: 一)、数据备份是数据隔离的。A项目数据备份,只能备份A项目下的数据。 二)、备份的数据用于数据恢复。 三)、备份的数据用于数据迁移,之前存在的关联数据要重新绑定关联关系。 ...

July 10, 2019 · 3 min · jiezi

从一段代码谈起浅谈JavaIO接口

本文首发于泊浮目的专栏:https://segmentfault.com/blog...1.前言前阵子休息天日常在寻找项目里不好的代码,看到了这样的一段代码: private Result sshSameExec(Session session, String cmd) { if (log.isDebugEnabled()) { log.debug("shell command: {}", cmd); } UserInfo ui = getUserInfo(); session.setUserInfo(ui); int exitStatus = 0; StringBuilder builder = new StringBuilder(); ChannelExec channel; InputStream in; InputStream err; try { session.connect(connectTimeout); channel = (ChannelExec) session.openChannel("exec"); channel.setCommand(cmd); in = channel.getInputStream(); err = channel.getErrStream(); channel.connect(); } catch (Exception e) { throw new CloudRuntimeException(e); } try { long lastRead = Long.MAX_VALUE; byte[] tmp = new byte[1024]; while (true) { while (in.available() > 0 || err.available() > 0) { int i = 0; if (in.available() > 0) { i = in.read(tmp, 0, 1024); } else if (err.available() > 0) { i = err.read(tmp, 0, 1024); } if (i < 0) { break; } lastRead = System.currentTimeMillis(); builder.append(new String(tmp, 0, i)); } if (channel.isClosed()) { if (in.available() > 0) { continue; } exitStatus = channel.getExitStatus(); break; } if (System.currentTimeMillis() - lastRead > exeTimeout) { break; } } } catch (IOException e) { throw new CloudRuntimeException(e); } finally { channel.disconnect(); session.disconnect(); } if (0 != exitStatus) { return Result.createByError(ErrorData.builder() .errorCode(ResultCode.EXECUTE_SSH_FAIL.getCode()) .detail(builder.toString()) .title(ResultCode.EXECUTE_SSH_FAIL.toString()) .build()); } else { return Result.createBySuccess(builder.toString()); } }简单解释一下这段代码——即通过ssh到一台机器上,然后执行一些命令.对命令输出的东西,开了一个循环,每一次读一定的位置,然后以字节流的形式读回来. ...

July 1, 2019 · 4 min · jiezi

What-你还不知道Kotlin-Coroutine

今天我们来聊聊Kotlin Coroutine,如果你还没有了解过,那么我要提前恭喜你,因为你将掌握一个新技能,对你的代码方面的提升将是很好的助力。 What Coroutine简单的来说,Coroutine是一个并发的设计模式,你能通过它使用更简洁的代码来解决异步问题。 例如,在Android方面它主要能够帮助你解决以下两个问题: 在主线程中执行耗时任务导致的主线程阻塞,从而使App发生ANR。提供主线程安全,同时对来自于主线程的网络回调、磁盘操提供保障。这些问题,在接下来的文章中我都会给出解决的示例。 Callback说到异步问题,我们先来看下我们常规的异步处理方式。首先第一种是最基本的callback方式。 callback的好处是使用起来简单,但你在使用的过程中可能会遇到如下情形 GatheringVoiceSettingRepository.getInstance().getGeneralSettings(RequestLanguage::class.java) .observe(this, { language -> convertResult(language, { enable -> // todo something }) })这种在其中一个callback中回调另一个callback回调,甚至更多的callback都是可能存在。这些情况导致的问题是代码间的嵌套层级太深,导致逻辑嵌套复杂,后续的维护成本也要提高,这不是我们所要看到的。 那么有什么方法能够解决呢?当然有,其中的一种解决方法就是我接下来要说的第二种方式。 Rx系列对多嵌套回调,Rx系列在这方面处理的已经非常好了,例如RxJava。下面我们来看一下RxJava的解决案例 disposable = createCall().map { // return RequestType }.subscribeWith(object : SMDefaultDisposableObserver<RequestType>{ override fun onNext(t: RequestType) { // todo something } })RxJava丰富的操作符,再结合Observable与Subscribe能够很好的解决异步嵌套回调问题。但是它的使用成本就相对提高了,你要对它的操作符要非常了解,避免在使用过程中滥用或者过度使用,这样自然复杂度就提升了。 那么我们渴望的解决方案是能够更加简单、全面与健壮,而我们今天的主题Coroutine就能够达到这种效果。 Coroutine在Kotlin中的基本要点在Android里,我们都知道网络请求应该放到子线程中,相应的回调处理一般都是在主线程,即ui线程。正常的写法就不多说了,那么使用Coroutine又该是怎么样的呢?请看下面代码示例: private suspend fun get(url: String) = withContext(Dispatchers.IO) { // to do network request url } private suspend fun fetch() { // 在Main中调用 val result = get("https://rousetime.com") // 在IO中调用 showToast(result) // 在Main中调用 }如果fetch方法在主线程调用,那么你会发现使用Coroutine来处理异步回调就像是在处理同步回调一样,简洁明了、行云流水,同时再也没有嵌套的逻辑了。 ...

June 28, 2019 · 2 min · jiezi

YRoute开发随笔

YRoute是一个新开发的Android路由库,使用了arrow函数式库作为核心库,是之前对于函数范式学习和思考的集大成者。但目前还在前期开发阶段,仅实现了一些简单的功能做架构验证用。 OOP中的23种设计模式相信大家已经烂熟于心了, 它们已经被广泛应用于软件工业的各个领域. 它们当初被创造是因为当时旧的编程思想在软件规模逐渐庞大的情况下已经难以驾驭了. 然而随着软件工业这么多年的持续发展, 同样的问题又来到了OOP的面前, 现在的代码抽象度越来越高, OOP的很多技法已经开始有点捉襟见肘, 这也是为什么这几年抽象度更高的函数范式的概念被越来越多的提起函数式编程中单子、高阶类型等概念被经常提起, 但面向组合子编程的概念却少有提及, 它是一种与以前构建程序完全的不同的思维模式: 由下至上构建程序. YRoute是使用这种方式进行构建的, 希望通过这个库和这篇对于开发过程描述的文章, 对大家会有所启发前因FragmentManager是几年前个人开发的一个Fragment管理库,相比其他库有Rx方式启动、多堆栈切换、Fragment与Activity一致的动画处理等等。此库在多个实际项目中被使用,功能被不断完善,稳定性、灵活性也得到了项目的验证,所以现在基本是项目开发的默认基础库了。 但对于个人而言其实一直不满于这个库本身的架构技术。最开始构建的时候以功能实现为主,也以以前习惯的OOP思想进行构建(因为那个时候还没有被函数范式“荼毒”),导致了架构上的各种问题: 状态和逻辑混杂在一起由于需求的功能基于Fragment和Activity的很多基础方法和生命周期,导致需要强继承BaseFragmentManagerActivity 和BaseManagerFragmentBaseFragmentManagerActivity被设计为了“超级类”:功能强大,但包含了大量可以被分离的逻辑,导致逻辑代码混杂由于启动、切换等功能逻辑很复杂,需要很多的判断和异常处理,导致有些方法虽然很相似却依然无法被重构合并,方法的粒度大,难以被组合虽然中期在添加新功能的时候尝试进行逻辑分离(比如侧滑返回返回功能SwipeBackUtil 、Rx启动功能、抖动抑制ThrottleUtil ),但基于OOP设计本身的缺点,它们的分离没有统一的模式,也无法真正清晰的分离,实际功能代码还是需要依赖混杂到BaseFragmentManagerActivity 后期虽然也希望借鉴Redux等架构设计进行了几次重构,但那时对函数范式架构的理解还不够深,导致架构本身难以承受库本身复杂的功能,而且也没有达到最初希望的灵活性,甚至相比原始的架构更加难用了得益于对函数范式在实践中的更多理解, 才有了YRoute这个库的出现 YRoute之前要理解YRoute库, 首先需要介绍一下相关的几个数据结构 Lens这是一个用于类型转换的数据类型, 从它的定义上就可以看出 data class Lens<S, T>( get: (S) -> T, set: (S, T) -> S)它包含两个函数, 一个是从数据类型S中提取T的get函数, 二一个是将旧的S和T数据组合成为新的S的函数set 用法可以参照: data class Player(val health: Int)val playerLens: Lens<Player, Int> = Lens( get = { player -> player.health }, set = { player, value -> player.copy(health = value) })val player = Player(70)Reader在函数范式中我们会提取很多的单子, 而其中函数本身其实也是一种单子, 而函数(D) -> A所抽取的单子就是Reader: ...

June 25, 2019 · 2 min · jiezi

GitHub-最新-Android-热门开源项目公布

作者: LeanCloud weakish 分享 2019 年 5 月 GitHub 上比较流行的 9 个和 Android 开发相关的开源项目,包括对话框、日历、矢量绘图组件,内存泄露检测库,Kotlin 的 linter、mock 库、依赖注入框架等。 LeakCanarysquare 开源的内存泄露检测库。 使用起来极为便捷,只需在 build.gradle 中引入依赖: dependencies { debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-alpha-2'}LeakCanary 会自动检测 debug build 中的内存泄露,并显示提示。无需修改代码,也不会影响正式发布版本。 GitHub 仓库:square/leakcanary Material Dialogs美观、可扩展的 Material Design 风格对话框。 Material Dialogs 采用模块化架构,核心模块(core)包含了核心功能和基本功能,文本输入框、文件选择器、色彩选择器、时间日期选择器、弹出表单等功能作为扩展模块提供,可单独引入。支持 AndroidX 生命周期组件。 GitHub 仓库:afollestad/material-dialogs CalendarView基于 RecyclerView 的日历库。 这个库借鉴了 iOS 的 [JTAppleCalendar],提供了比较齐全的日历视图所需特性,支持定制样式。 GitHub 仓库:kizitonwose/CalendarView KyrieVectorDrawable 和 AnimatedVectorDrawable 的超集。 VectorDrawable 提供了像素密度无关性——在任意设备上随意缩放而不损失画质。AnimatedVectorDrawable 在其基础上添加了动画特性。然而,它们有三大缺陷: 无法暂停和继续。无法在运行时动态创建。相比 web 上的 SVG,它们仅仅支持少量特性。Kyrie 提供了 KyrieDrawable 类,支持 VectorDrawable 和 AnimatedVectorDrawable 的所有特性,同时改进了上述缺陷。 ...

June 20, 2019 · 2 min · jiezi

Android-Gradle系列进阶篇

上篇文章我们已经将Gradle基础运用介绍了一遍,可以这么说,只要你一直看了我这个Gradle系列,那么你的Gradle也将过关了,应对正常的工作开发已经不成问题了。 这篇文章我要向你介绍的是关于如何使用Gradle来更加优雅的管理多个module之间的依赖关系。 相信你一定有这样的经历:主项目依赖于多个子项目,或者项目间互相依赖。不同子项目间的依赖的第三方库版本又没有进行统一,升级一个版本所有依赖的项目都要进行修改;甚至minSdkVersion与targetSdkVersion也不相同。 今天我们就来解决这个问题,让Gradle版本管理更加优雅。 Google推荐之前的文章Android Gradle系列-运用篇中的dependencies使用的是最基本的引用方式。如果你有新建一个kotlin项目的经历,那么你将看到Google推荐的方案 buildscript { ext.kotlin_version = '1.1.51' repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.0.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" }}在rootProject的build.gradle中使用ext来定义版本号全局变量。这样我们就可以在module的build.gradle中直接引用这些定义的变量。引用方式如下: dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"}你可以将这些变量理解为java的静态变量。通过这种方式能够达到不同module中的配置统一,但局限性是,一但配置项过多,所有的配置都将写到rootProject项目的build.gradle中,导致build.gradle臃肿。这不符合我们的所提倡的模块开发,所以应该想办法将ext的配置单独分离出来。 这个时候我就要用到之前的文章Android Gradle系列-原理篇中所介绍的apply函数。之前的文章我们只使用了apply三种情况之一的plugin(应用一个插件,通过id或者class名),只使用在子项目的build.gradle中。 apply plugin: 'com.android.application'这次我们需要使用它的from,它主要是的作用是应用一个脚本文件。作用接下来我们需要做的是将ext配置单独放到一个gradle脚本文件中。 首先我们在rootProject目录下创建一个gradle脚本文件,我这里取名为version.gradle。 然后我们在version.gralde文件中使用ext来定义变量。例如之前的kotlin版本号就可以使用如下方式实现 ext.deps = [:] def versions = [:]versions.support = "26.1.0"versions.kotlin = "1.2.51"versions.gradle = '3.2.1' def support = [:]support.app_compat = "com.android.support:appcompat-v7:$versions.support"support.recyclerview = "com.android.support:recyclerview-v7:$versions.support"deps.support = support def kotlin = [:]kotlin.kotlin_stdlib = "org.jetbrains.kotlin:kotlin-stdlib-jre7:$versions.kotlin"kotlin.plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin"deps.kotlin = kotlin deps.gradle_plugin = "com.android.tools.build:gradle:$versions.gradle" ext.deps = deps def build_versions = [:]build_versions.target_sdk = 26build_versions.min_sdk = 16build_versions.build_tools = "28.0.3"ext.build_versions = build_versions def addRepos(RepositoryHandler handler) { handler.google() handler.jcenter() handler.maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }}ext.addRepos = this.&addRepos因为gradle使用的是groovy语言,所以以上都是groovy语法例如kotlin版本控制,上面代码的意思就是将有个kotlin相关的版本依赖放到deps的kotlin变量中,同时deps放到了ext中。其它的亦是如此。 ...

June 10, 2019 · 2 min · jiezi

Android图片加载优化

Android图片加载优化在Android开发中图片加载往往是导致OOM(Out of Memory)的主要原因,所以图片的压缩不得不作为Android开发中比用的一项技能点,以下将以简单的方式进行优化。 图片的大小如何被定义?其实图片大小的计算是很简单的,只需要用图片的width乘以图片的height然后再乘以每一个像素所占用的字节数,这个字节数需要根据图片解码模式来获得,Android中提供了6种方案,不过常用的只有三个,在下方已经列出。 A:透明度(Alpha) R:红色(Red) G:绿(Green) B:蓝(Blue) Bitmap.Config.ARGB_8888:由4个8位组成,即A=8,R=8,G=8,B=8,那么一个像素点占8+8+8+8=32位(4字节) Bitmap.Config.ARGB_4444:由4个4位组成,即A=4,R=4,G=4,B=4,那么一个像素点占4+4+4+4=16位 (2字节) Bitmap.Config.RGB_565:没有透明度,R=5,G=6,B=5,,那么一个像素点占5+6+5=16位(2字节) 基础知识补脑:8bit(位)=1byte(字节)1024byte=1KB1024kb=1MB1024mb=1GB 举例:1、480x800的图片,在色彩模式为ARGB_8888的情况下 480*800*4/1024/1024=1.46484375MB2、480x800的图片,在色彩模式为ARGB_4444的情况下 480*800*2/1024/1024=0.732421875MB通过以上的测试,发现导致过大的原因主要集中在两点上,一点是图片的宽和高零一点则是解码方式,通过等比例缩放图片以及切换解码方式可以有效的降低图片的内存占用。不过在以下的优化方案中还有一种优化就是内存复用技术。 优化方案缩放图片+质量压缩var bitmapOptions = BitmapFactory.Options()bitmapOptions.inJustDecodeBounds = true//设置仅加载图片的宽高信息不将图片加载的内存中BitmapFactory.decodeResource(resources, R.drawable.testimg, bitmapOptions)//获取图片的宽高bitmapOptions.inSampleSize = 4//设置缩放比例(1、缩放图片)bitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565//设置解码模式(2、质量压缩)bitmapOptions.inJustDecodeBounds = false//消除仅加载图片的宽高var bitmap = BitmapFactory.decodeResource(resources, R.drawable.testimg, bitmapOptions)//加载图片到内存(注意添加Option对象)this.imgMain.setImageBitmap(bitmap)//使用BitmapinSampleSize的作用就是可以把图片的宽高缩小inSampleSize倍,如下代码设置为4那么就是宽高的四分之一,所占内存缩小inSampleSize的平方。单纯的缩放4倍并不充分,只能说治标不治本,好比一个高清50MB的图片,你缩放4倍还是凉凉,所以说还是根据尺寸来计算需要缩放的倍数。 bitmapOptions.inSampleSize =Math.ceil(bitmapOptions.outWidth.toDouble() / targetWidth.toDouble()).toInt()//设置缩放比例(1、缩放图片)PS:以上的缩放并非精确的缩放 内存复用何为内存复用,通俗来说就是我已经创建了一个Bitmap对象了,那么我接着还想用一个bitmap对象,那么就可以复用上一个Bitmap对象,不过这样做有两个缺点,第一就是会将之前的图片覆盖掉,第二就是后边加载的图片必须小于或者等于之前的图片大小,否则就不行。 var bitmapOptions1 = BitmapFactory.Options()bitmapOptions1.inBitmap = bitmap//绑定一个已经加载的Bitmap对象var bitmap1 = BitmapFactory.decodeResource(resources, R.drawable.timg, bitmapOptions1)//加载新的Bitmap对象this.imgMain1.setImageBitmap(bitmap1)//使用Bitmap其他方案推荐使用WEBP格式代替PNG和JPEG图片格式。 写在最后以上所说的只是简单的图片压缩优化,并非特定专业的优化方式,不过应对一般的项目需求应该还是可以满足的。

June 3, 2019 · 1 min · jiezi

Gradle系列运用篇

上次我们说到gradle的原理,主要是偏理论上的知识点,直通车在这Android Gradle系列-原理篇。这次我们来点实战的,随便巩固下之前的知识点。 android在app module下的gradle.build中都有一个android闭包,主要配置都在这里设置。例如默认配置项:defaultConfig;签名相关:signingConfig;构建变体:buildTypes;产品风格:productFlavors;源集配置:sourceSets等。 defaultConfig对于defaultConfig其实它是也一个productFlavor,只不过这里是用来提供默认的设置项,如果之后的productFlavor没有特殊指定的配置都会使用defaultConfig中的默认配置。 public class DefaultConfig extends BaseFlavor { @Inject public DefaultConfig( @NonNull String name, @NonNull Project project, @NonNull ObjectFactory objectFactory, @NonNull DeprecationReporter deprecationReporter, @NonNull Logger logger) { super(name, project, objectFactory, deprecationReporter, logger); }} public abstract class BaseFlavor extends DefaultProductFlavor implements CoreProductFlavor { ...}可以看到defaultConfig的超级父类就是DefaultProductFlavor。而在DefaultProductFlavor中定义了许多我们经常见到的配置:VersionCode、VersionName、minSdkVersion、targetSdkVersion与applicationId等等。 有了上面的基础,那么在defaultConfig中我们要配置的变量就显而易见了。 defaultConfig { applicationId "com.idisfkj.androidapianalysis" minSdkVersion 16 targetSdkVersion 26 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" }signingConfigssigningConfig是用来配置keyStore,我们可以针对不同的版本配置不同的keyStore,例如 signingConfigs { config { //默认配置 storeFile file('key.store') storePassword 'android123' keyAlias 'android' keyPassword 'android123' } dev { //dev测试版配置 storeFile file('xxxx') storePassword 'xxx' keyAlias 'xxx' keyPassword 'xxx' } }有人可能会说这不安全的,密码都是明文,都暴露出去了。是的,如果这项目发布到远程,那么这些秘钥就泄露出去了。所以为了安全起见,我们可以对其进一些特殊处理。 ...

May 30, 2019 · 3 min · jiezi

初识Kotlin之集合

Kotlin的集合是让我为之心动的地方,丰富的高阶函数帮助我们高效开发。今天介绍Kotlin的基础集合用法、获取集合元素的函数、过滤元素的函数、元素排序的函数、元素统计的函数、集合元素映射的函数、集合的交差并补集的函数。还有一些工作中的经验。 初始化集合和Java集合不同的是,Kotlin的集合分可变和不可变两种集合。同时也支持两种集合相互切换。 List集合// 声明并初始化不可变List集合val list: List<Any> = listOf<Any>(1, "2", 3)// 声明并初始化可变MutableList集合val mutableList: MutableList<Any> = mutableListOf<Any>(4, "5", 6)mutableList.add("7")list.map { print("$it \t") }mutableList.map { print("$it \t") }Set集合// 声明并初始化不可变Set集合val set: Set<Any> = setOf<Any>(1, "2", 3, "3")// 声明并初始化可变MutableSet集合val mutableSet: MutableSet<Any> = mutableSetOf<Any>(4, "5", 6)mutableSet.add(6)set.map { print("$it \t") }mutableSet.map { print("$it \t") }Map集合// 声明并初始化不可变Map集合val map: Map<String, Any> = mapOf("k1" to "v1" , "k2" to 3)// 声明并初始化可变MutableMap集合val mutableMap: MutableMap<String, Any> = mutableMapOf("k1" to "v1" , "k1" to 3)map.map { println("key : ${it.key} \t value : ${it.value}") }mutableMap.map { println("key : ${it.key} \t value : ${it.value}") }集合高阶函数获取集合元素用Java语言开发时,我们通常用循环遍历集合的每个元素。有时候也会通过下标直接获取指定元素。此时原则上时需要我们先考虑集合元素的长度,以避免下标越界的异常问题。但往往我们会抱着侥幸的心态直接通过get(index)方法获取元素。一般情况下我们会在黑盒自测中发现越界问题(有部分朋友从不黑盒,直接白盒测试,并反问:测试的工作难道不就是发现问题?)。即便是在运行中出现越界问题,也可以甩锅给数据库。但不管怎么样,因为越界导致系统不稳定是不合理的。 ...

May 19, 2019 · 4 min · jiezi

初识Kotlin之函数

本章通过介绍Kotlin的基本函数,默认参数函数,参数不定长函数,尾递归函数,高阶函数,Lamdba表达式。来对Kotlin函数做进一步了解。将上一篇的Kotlin变量的知识得以运用。Kotlin变量 Kotlin函数简介Kotlin中是通过关键字fun声明函数。和变量一样,返回值类型放在名称后面,并用":"冒号分开。Kotlin函数默认修饰符public,且可以在文件顶层声明。其格式如下 fun 函数名(变量): 返回值类型 { }Kotlin常见函数基础函数fun getValue(v: Int): Int { return v}当函数不需要返回任何值时,可以将返回值类型定义成Unit,也可以不显式返回。 fun setValue(v: Int) { }参数默认值函数函数的参数可以有默认值,当函数调用者不给默认参数赋值时,函数体就使用参数的默认值。这样可以减少很多方法重载的代码量。 fun setValue(x: Int, y: Int = 10): Int { retunr x + y}setValue(10) -----> 20setValue(10, 20) -----> 30参数默认值函数固然好用。但是由于每个人的编程习惯和编程水平的不同。项目中出现下面的代码的概率还不低。通过程序打印的结果可以看出,输出的结果并不是我们预期的21.2,而且10。说明编译器是调用的是第一个函数。 fun main(args: Array<String>) { println(setValue(10)) -----> 10}fun setValue(x: Int) = xfun setValue(x: Int, y: Int = 10, z: Double = 1.2) = x + y + z还一个语法问题,子类继承父类的参数默认值函数后,是不允许重写的函数为其参数指定默认值。好在这种情况编译器会提示错误。 open class FatherClass { open fun setValue(x: Int, y: Int = 10, z: Double = 1.2) = x + y + z}class SunClass: FatherClass() {// An overriding function is not allowed to specify default values for its paramete override fun setValue(x: Int, y: Int, z: Double) = x + y + z}单表达式函数若函数体只是单个表达式时,可以省略花括号并用"=" 指定代码体。了解一下即可,至少遇到了不要惊讶。 ...

May 7, 2019 · 2 min · jiezi

Kotlin-Native实战开发

Kotlin Native实战开发17/100发布文章xiangzhihong8 注:本部分内容来源于《Kotlin入门与实战》,预计9月上市。 16.1 Kotlin Native16.1.1 Kotlin Native简介Kotlin Native是一种将Kotlin源码编译成不需要任何VM支持的目标平台二进制数据的技术,编译后的二进制数据可以直接运行在目标平台上,它主要包含一个基于LLVM的后端编译器的和一个Kotlin本地运行时库。设计Kotlin Native的目的是为了支持在非JVM环境下进行编程,如在嵌入式平台和iOS环境下,如此一来,Kotlin就可以运行在非JVM平台环境下。 LLVM是Low Level Virtual Machine的缩写,是一种比较底层的虚拟机技术,LLVM由C++编写而成,主要用来优化应用程序的编译时间(compile-time)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time)。LLVM可以有效的解决编译器重复编译代码的问题,并且LLVM制定了LLVM IR这种中间代码表示语言,LLVM IR充分考虑了各种应用场景,有效的提高了代码的编译效率。 在讲解Kotlin Native具体的知识之前,先来看一下计算机高级语言常见两种流派:编译型语言和解释型语言。 所谓编译型语言,是指使用专门的编译器、针对特定平台/操作系统将某种高级语言源代码一次性编译成该平台硬件能够执行的机器码,编译生成的可执行程序可以脱离开发环境,在特定的平台上独立运行。因为编译型语言是一次性编译成机器码的,所以可以脱离开发环境独立运行,而且通常运行效率较高。不过,正因为编译型语言只能被编译成特定平台上的机器码,所以生成的可执行程序通常无法移植到其他平台上运行。例如,现在比较流行的C、C++等高级编程语言都属于编译型语言。 而所谓解释型语言,是指使用专门的解释器对源程序进行逐行解释,并生成特定平台的机器码并立即执行的编程语言。解释型语言通常不需要进行整体的编译和链接处理,解释型语言会把编译型语言中的编译和解释过程混合在一起执行。虽然解释型语言运行效率较低且不能脱离释器独立运行,但解释型语言可以很方便的实现源程序的移植和运行。 16.1.2 Kotlin Native编译器目前,Kotlin Native主要提供了Mac、Linux和Windows三个主流平台的编译器,使用该编译器可以很轻松的编译出运行在树莓派、iOS、OS X、Windows以及Linux系统上的程序。Kotlin Native支持平台和版本如下表所示。 支持的系统平台 支持的版本Windows x86_64Linux x86_64、arm32、MIPS、MIPS小端MacOS x86_64iOS arm64Android arm32、arm64WebAssembly wasm32表16-1 Kotlin Native支持平台及其版本 编译Kotlin Native项目,首先需要到Github上下载Kotlin Native的编译器软件包,下载地址为:https://github.com/JetBrains/...,使用前下载对应的平台版本即可,下载后解压下载的Kotlin Native编译器软件包,其目录结构如图16-1所示。图16-1 Kotlin Native编译器目录结构图当然,也可以通过克隆Kotlin Native编译器的源码进行编译,编译需要先到Github上下载编译器源码,下载地址为:https://github.com/JetBrains/...。下载完成后,使用如下命令下载依赖关系,命令如下: ./gradlew dependencies:update然后,建立编译器和库的关联。 ./gradlew bundle如果需要构建整个项目可能需要很长的时间。然后,使用以下的命令即可编译项目。 ./gradlew dist distPlatformLibs到此,就可以得到Kotlin的Native编译器了,它通常位于项目的./dist/bin目录下,打开bin文件可以看到Native编译器的相关信息,它有7个可执行程序构成,如图15-2所示。通过对比发现,Native编译器的目录结构和Kotlin Native官方提供的编译器的内容是一样的。然后,就可以利用Native编译器编译应用程序了。例如: export PATH=./dist/bin:$PATHkotlinc hello.kt -o hello如果需要进行优化编译,可以使用-opt参数。 kotlinc hello.kt -o hello -opt如果需要对应用程序进行测试,可以使用类似于下面的命令。 ./gradlew backend.native:tests:run 图16-2 Kotlin的Native编译器目录结构在Kotlin Native官方提供的示例中,系统自带了针对不同平台的例子,并且这些例子都是可以直接编译运行的。由于Kotlin Native本身是一个gradle构建的项目,所以可以使用idea直接打开Kotlin Native目录下的samples文件,idea会自动识别该项目。 ...

May 3, 2019 · 4 min · jiezi

SpringBoot-2X-Kotlin系列之数据校验和异常处理

在开发项目时,我们经常需要在前后端都校验用户提交的数据,判断提交的数据是否符合我们的标准,包括字符串长度,是否为数字,或者是否为手机号码等;这样做的目的主要是为了减少SQL注入攻击的风险以及脏数据的插入。提到数据校验我们通常还会提到异常处理,因为为了安全起见,后端出现的异常我们通常不希望直接抛到客户端,而是经过我们的处理之后再返回给客户端,这样做主要是提升系统安全性,另外就是给予用户友好的提示。定义实体并加上校验注解class StudentForm() { @NotBank(message = '生日不能为空') var birthday: String = "" @NotBlank(message = "Id不能为空") var id:String = "" @NotBlank(message = "年龄不能为空") var age:String = "" @NotEmpty(message = "兴趣爱好不能为空") var Interests:List<String> = Collections.emptyList() @NotBlank(message = "学校不能为空") var school: String = "" override fun toString(): String { return ObjectMapper().writeValueAsString(this) }}这里首先使用的是基础校验注解,位于javax.validation.constraints下,常见注解有@NotNull、@NotEmpty、@Max、@Email、@NotBank、@Size、@Pattern,当然出了这些还有很多注解,这里就不在一一讲解,想了解更多的可以咨询查看jar包。 这里简单讲解一下注解的常见用法: @NotNull: 校验一个对象是否为Null@NotBank: 校验字符串是否为空串@NotEmpty: 校验List、Map、Set是否为空@Email: 校验是否为邮箱格式@Max @Min: 校验Number或String是否在指定范围内@Size: 通常需要配合@Max @Min一期使用@Pattern: 配合自定义正则表达式校验定义返回状态枚举enum class ResultEnums(var code:Int, var msg:String) { SUCCESS(200, "成功"), SYSTEM_ERROR(500, "系统繁忙,请稍后再试"),}自定义异常这里主要是参数校验,所以定义一个运行时异常,代码如下: ...

April 30, 2019 · 2 min · jiezi

初识Kotlin之变量

用Java开发了很多年,因为工作的需要学习Kotlin。初识Kotlin时是各种不习惯,觉得这个语言相对于Java而言并不够严谨。随着不断的深入,最终还是逃不过"真香定理"。我一直认为普通的技术使用者是没有太多的权利去评论一门技术语言的好坏。很多人只了解皮毛就开始对它进行过分的评论。所以大家不要被网上的一些信息所左右(像我这样单纯的程序员好像不多了)。如果你有Java基础,上手Kotlin是非常的快,而且还会让你爱不释手。 Hello World国际惯例,我们分别用Java和Kotlin实现打印hello world的功能。通过比较两者的区别,开启Kotlin大门。 先是熟悉的Java代码: 第一步:创建java文件,并定义一个HelloWorld类, 第二步:创建类的入口函数main方法,在main函数中执行输出语句。 package com.java.helloworld;public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World"); }}接一下是陌生的Kotlin代码: 第一步:创建kt文件, 第二步:创建入口函数main方法,在main函数中执行输出语句。 package com.kotlin.helloworldfun main(args: Array<String>) { println("Hello World")}看到这部分代码,你一定会奇怪 Kotlin不需要定义class类就可以执行main方法?是的,当然也可以做到先定义类,然后在类中创建main函数。 Kotlin是通过fun关键字定义一个函数? Kotlin的main函数为什么没有public static void 等关键字? Kotlin的变量名怎么在变量类型的前面? Kotlin的输出语句看起来好简单!!!没错,这些都是Kotlin的语法特点。先混个脸熟! Kotlin简介Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言。并且Google 已经宣布 Kotlin 成为 Android 官方开发语言。但这并不意味着Kotlin只能做Android开发。Java能做到的,Kotlin基本都可以做到。而且比Java更简单,更简洁,更实用。Kotlin还提供了大量的函数帮助我们快速开发,同时也挖了很多坑等着我们跳。 用Kotlin以后发现自己变笨了点!大家有没有这样的感觉呢? Kotlin变量在Java中,声明一个变量,必须要先规定变量的类型,然后才是变量的名称。与之相反,Kotlin是先定义变量名称,最后才是变量类型。Kotlin甚至可以省略变量类型,通过编译器智能推断出变量的类型。这也是变量类型写在后面的好处。 还是老规矩,我们分别用Java和Kotlin变量定义。通过比较两者的区别,进一步了解Kotlin。 列举了一些Java常见的基本数据类型和用法,定义变量的格式:[修饰符] + 变量类型 + 变量名称 public class Variable { public static void main(String[] args) { byte b = 0; short s = 0; int i = 0; long l = 0L; float f = 0.0f; double d = 0.0; boolean bl = true; char c = 'c'; }}习惯用Java的朋友在使用Kotlin中最大的不适应的地方就是变量。因为声明变量的代码是常有的,而且两者的语法格式恰恰相反。Kotlin通过关键字val和var定义变量,然后接变量名和变量类型。格式:val/var + 变量名 : 变量类型。 ...

April 27, 2019 · 2 min · jiezi

SpringBoot 2.X Kotlin系列之AOP统一打印日志

在开发项目中,我们经常会需要打印日志,这样方便开发人员了解接口调用情况及定位错误问题,很多时候对于Controller或者是Service的入参和出参需要打印日志,但是我们又不想重复的在每个方法里去使用logger打印,这个时候希望有一个管理者统一来打印,这时Spring AOP就派上用场了,利用切面的思想,我们在进入、出入Controller或Service时给它切一刀实现统一日志打印。SpringAOP不仅可以实现在不产生新类的情况下打印日志,还可以管理事务、缓存等。具体可以了解官方文档。https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-api基础概念在使用SpringAOP,这里还是先简单讲解一些基本的知识吧,如果说的不对请及时指正,这里主要是根据官方文档来总结的。本章内容主要涉及的知识点。Pointcut: 切入点,这里用于定义规则,进行方法的切入(形象的比喻就是一把刀)。JoinPoint: 连接点,用于连接定义的切面。Before: 在之前,在切入点方法执行之前。AfterReturning: 在切入点方法结束并返回时执行。这里除了SpringAOP相关的知识,还涉及到了线程相关的知识点,因为我们需要考虑多线程中它们各自需要保存自己的变量,所以就用到了ThreadLocal。依赖引入这里主要是用到aop和mongodb,在pom.xml文件中加入以下依赖即可:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId></dependency>相关实体类/** * 请求日志实体,用于保存请求日志 /@Documentclass WebLog { var id: String = "" var request: String? = null var response: String? = null var time: Long? = null var requestUrl: String? = null var requestIp: String? = null var startTime: Long? = null var endTime: Long? = null var method: String? = null override fun toString(): String { return ObjectMapper().writeValueAsString(this) }}/* * 业务对象,上一章讲JPA中有定义 /@Documentclass Student { @Id var id :String? = null var name :String? = null var age :Int? = 0 var gender :String? = null var sclass :String ?= null override fun toString(): String { return ObjectMapper().writeValueAsString(this) }}定义切面定义切入点/* * 定义一个切入,只要是为io.intodream..web下public修饰的方法都要切入 /@Pointcut(value = “execution(public * io.intodream..web..(..))")fun webLog() {}定义切入点的表达式还可以使用within、如:/** * 表示在io.intodream.web包下的方法都会被切入 /@Pointcut(value = “within(io.intodream.web..")定义一个连接点/** * 切面的连接点,并声明在该连接点进入之前需要做的一些事情 /@Before(value = “webLog()”)@Throws(Throwable::class)fun doBefore(joinPoint: JoinPoint) { val webLog = WebLog() webLog.startTime = System.currentTimeMillis() val attributes = RequestContextHolder.getRequestAttributes() as ServletRequestAttributes? val request = attributes!!.request val args = joinPoint.args val paramNames = (joinPoint.signature as CodeSignature).parameterNames val params = HashMap<String, Any>(args.size) for (i in args.indices) { if (args[i] !is BindingResult) { params[paramNames[i]] = args[i] } } webLog.id = UUID.randomUUID().toString() webLog.request = params.toString() webLog.requestUrl = request.requestURI.toString() webLog.requestIp = request.remoteAddr webLog.method = request.method webRequestLog.set(webLog) logger.info(“REQUEST={} {}; SOURCE IP={}; ARGS={}”, request.method, request.requestURL.toString(), request.remoteAddr, params)}方法结束后执行@AfterReturning(returning = “ret”, pointcut = “webLog()”)@Throws(Throwable::class)fun doAfterReturning(ret: Any) { val webLog = webRequestLog.get() webLog.response = ret.toString() webLog.endTime = System.currentTimeMillis() webLog.time = webLog.endTime!! - webLog.startTime!! logger.info(“RESPONSE={}; SPEND TIME={}MS”, ObjectMapper().writeValueAsString(ret), webLog.time) logger.info(“webLog:{}”, webLog) webLogRepository.save(webLog) webRequestLog.remove()}这里的主要思路是,在方法执行前,先记录详情的请求参数,请求方法,请求ip, 请求方式及进入时间,然后将对象放入到ThreadLocal中,在方法结束后并取到对应的返回对象且计算出请求耗时,然后将请求日志保存到mongodb中。完成的代码package io.intodream.kotlin07.aspectimport com.fasterxml.jackson.databind.ObjectMapperimport io.intodream.kotlin07.dao.WebLogRepositoryimport io.intodream.kotlin07.entity.WebLogimport org.aspectj.lang.JoinPointimport org.aspectj.lang.annotation.AfterReturningimport org.aspectj.lang.annotation.Aspectimport org.aspectj.lang.annotation.Beforeimport org.aspectj.lang.annotation.Pointcutimport org.aspectj.lang.reflect.CodeSignatureimport org.slf4j.Loggerimport org.slf4j.LoggerFactoryimport org.springframework.beans.factory.annotation.Autowiredimport org.springframework.core.annotation.Orderimport org.springframework.stereotype.Componentimport org.springframework.validation.BindingResultimport org.springframework.web.context.request.RequestContextHolderimport org.springframework.web.context.request.ServletRequestAttributesimport java.util./** * {描述} * * @author yangxianxi@gogpay.cn * @date 2019/4/10 19:06 * /@Aspect@Order(5)@Componentclass WebLogAspect { private val logger:Logger = LoggerFactory.getLogger(WebLogAspect::class.java) private val webRequestLog: ThreadLocal<WebLog> = ThreadLocal() @Autowired lateinit var webLogRepository: WebLogRepository /* * 定义一个切入,只要是为io.intodream..web下public修饰的方法都要切入 / @Pointcut(value = “execution(public * io.intodream..web..(..))”) fun webLog() {} /** * 切面的连接点,并声明在该连接点进入之前需要做的一些事情 / @Before(value = “webLog()”) @Throws(Throwable::class) fun doBefore(joinPoint: JoinPoint) { val webLog = WebLog() webLog.startTime = System.currentTimeMillis() val attributes = RequestContextHolder.getRequestAttributes() as ServletRequestAttributes? val request = attributes!!.request val args = joinPoint.args val paramNames = (joinPoint.signature as CodeSignature).parameterNames val params = HashMap<String, Any>(args.size) for (i in args.indices) { if (args[i] !is BindingResult) { params[paramNames[i]] = args[i] } } webLog.id = UUID.randomUUID().toString() webLog.request = params.toString() webLog.requestUrl = request.requestURI.toString() webLog.requestIp = request.remoteAddr webLog.method = request.method webRequestLog.set(webLog) logger.info(“REQUEST={} {}; SOURCE IP={}; ARGS={}”, request.method, request.requestURL.toString(), request.remoteAddr, params) } @AfterReturning(returning = “ret”, pointcut = “webLog()”) @Throws(Throwable::class) fun doAfterReturning(ret: Any) { val webLog = webRequestLog.get() webLog.response = ret.toString() webLog.endTime = System.currentTimeMillis() webLog.time = webLog.endTime!! - webLog.startTime!! logger.info(“RESPONSE={}; SPEND TIME={}MS”, ObjectMapper().writeValueAsString(ret), webLog.time) logger.info(“webLog:{}”, webLog) webLogRepository.save(webLog) webRequestLog.remove() }}这里定义的是Web层的切面,对于Service层我也可以定义一个切面,但是对于Service层的进入和返回的日志我们可以把级别稍等调低一点,这里改debug,具体实现如下:package io.intodream.kotlin07.aspectimport com.fasterxml.jackson.databind.ObjectMapperimport org.aspectj.lang.JoinPointimport org.aspectj.lang.annotation.AfterReturningimport org.aspectj.lang.annotation.Aspectimport org.aspectj.lang.annotation.Beforeimport org.aspectj.lang.annotation.Pointcutimport org.aspectj.lang.reflect.CodeSignatureimport org.slf4j.Loggerimport org.slf4j.LoggerFactoryimport org.springframework.core.annotation.Orderimport org.springframework.stereotype.Componentimport org.springframework.validation.BindingResult/* * service层所有public修饰的方法调用返回日志 * * @author yangxianxi@gogpay.cn * @date 2019/4/10 17:33 * /@Aspect@Order(2)@Componentclass ServiceLogAspect { private val logger: Logger = LoggerFactory.getLogger(ServiceLogAspect::class.java) /* * / @Pointcut(value = “execution(public * io.intodream..service..*(..))”) private fun serviceLog(){} @Before(value = “serviceLog()”) fun deBefore(joinPoint: JoinPoint) { val args = joinPoint.args val codeSignature = joinPoint.signature as CodeSignature val paramNames = codeSignature.parameterNames val params = HashMap<String, Any>(args.size).toMutableMap() for (i in args.indices) { if (args[i] !is BindingResult) { params[paramNames[i]] = args[i] } } logger.debug(“CALL={}; ARGS={}”, joinPoint.signature.name, params) } @AfterReturning(returning = “ret”, pointcut = “serviceLog()”) @Throws(Throwable::class) fun doAfterReturning(ret: Any) { logger.debug(“RESPONSE={}”, ObjectMapper().writeValueAsString(ret)) }}接口测试这里就不在贴出Service层和web的代码实现了,因为我是拷贝之前将JPA那一章的代码,唯一不同的就是加入了切面,切面的加入并不影响原来的业务流程。执行如下请求:我们会在控制台看到如下日志2019-04-14 19:32:27.208 INFO 4914 — [nio-9000-exec-1] i.i.kotlin07.aspect.WebLogAspect : REQUEST=POST http://localhost:9000/api/student/; SOURCE IP=0:0:0:0:0:0:0:1; ARGS={student={“id”:“5”,“name”:“Rose”,“age”:17,“gender”:“Girl”,“sclass”:“Second class”}}2019-04-14 19:32:27.415 INFO 4914 — [nio-9000-exec-1] org.mongodb.driver.connection : Opened connection [connectionId{localValue:2, serverValue:4}] to localhost:270172019-04-14 19:32:27.431 INFO 4914 — [nio-9000-exec-1] i.i.kotlin07.aspect.WebLogAspect : RESPONSE={“id”:“5”,“name”:“Rose”,“age”:17,“gender”:“Girl”,“sclass”:“Second class”}; SPEND TIME=239MS2019-04-14 19:32:27.431 INFO 4914 — [nio-9000-exec-1] i.i.kotlin07.aspect.WebLogAspect : webLog:{“id”:“e7b0ca1b-0a71-4fa0-9f5f-95a29d4d54a1”,“request”:"{student={"id":"5","name":"Rose","age":17,"gender":"Girl","sclass":"Second class"}}”,“response”:”{"id":"5","name":"Rose","age":17,"gender":"Girl","sclass":"Second class"}",“time”:239,“requestUrl”:"/api/student/",“requestIp”:“0:0:0:0:0:0:0:1”,“startTime”:1555241547191,“endTime”:1555241547430,“method”:“POST”}查看数据库会看到我们的请求日志已经写入了:这里有一个地方需要注意,在Service层的实现,具体如下:return studentRepository.findById(id).get()这里的findById会返回一个Optional<T>对象,如果没有查到数据,我们使用get获取数据会出现异常java.util.NoSuchElementException: No value present,可以改为返回对象可以为空只要在返回类型后面加一个?即可,同时调用Optional的ifPresent进行安全操作。 ...

April 16, 2019 · 3 min · jiezi

SpringBoot 2.X Kotlin系列之JavaMailSender发送邮件

在很多服务中我经常需要用到发送邮件功能,所幸的是SpringBoot可以快速使用的框架spring-boot-starter-mail,只要引入改框架我们可以快速的完成发送邮件功能。引入mailJar<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId></dependency>获取邮件发送服务器配置在国内用的最多的就是QQ邮件和网易163邮件,这里会简单讲解获取两家服务商的发送邮件配置。QQ邮箱等录QQ邮箱,点击设置然后选择账户在下方可以看到POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务,然后我们需要把smtp服务开启,开启成功后会得到一个秘钥。如图所示:开启成功需要在application.properties配置文件中加入相应的配置,以下信息部分需要替换为自己的信息,教程结束下面的账号就会被停用spring.mail.host=smtp.qq.comspring.mail.username=6928700@qq.com # 替换为自己的QQ邮箱号spring.mail.password=owqpkjmqiasnbigc # 替换为自己的秘钥或授权码spring.mail.port=465spring.mail.properties.mail.smtp.auth=truespring.mail.properties.mail.smtp.starttls.enable=truespring.mail.properties.mail.smtp.starttls.required=true# sender email.sender=6928700@qq.com # 替换为自己的QQ邮箱号163邮箱登录账户然后在设置找到POP3/SMTP/IMAP选项,然后开启smtp服务,具体操作如下图所示,然后修改对应的配置文件spring.mail.host=smtp.163.comspring.mail.username=xmsjgzs@163.com # 替换为自己的163邮箱号spring.mail.password=owqpkj163MC # 替换为自己的授权码spring.mail.port=465spring.mail.properties.mail.smtp.auth=truespring.mail.properties.mail.smtp.starttls.enable=truespring.mail.properties.mail.smtp.starttls.required=true# sender email.sender=xmsjgzs@163.com # 替换为自己的163邮箱号实现简单发送邮件这里发送邮件我们主要用到的是JavaMailSender对象,发送简单邮件主要是发送字符串内容,复杂的邮件我们可能会添加附件或者是发送HTML格式的邮件,我们先测试简单的发送,代码如下:override fun sendSimple(receiver: String, title: String, content: String) { logger.info(“发送简单邮件服务”) val message = mailSender.createMimeMessage() val helper = MimeMessageHelper(message, true) helper.setFrom(sender) helper.setTo(receiver) helper.setSubject(title) helper.setText(content) mailSender.send(message)}测试代码@RunWith(SpringJUnit4ClassRunner::class)@SpringBootTestclass MailServiceImplTest { @Autowired lateinit var mailService: MailService @Test fun sendSimple() { mailService.sendSimple(“xmsjgzs@163.com”, “Hello Kotlin Mail”, “SpringBoot Kotlin 专栏学习之JavaMailSender发送邮件”) }}检查邮件是否收到发送的内容发送模板邮件我们这里用的HTML模板引擎是thymeleaf,大家需要引入一下spring-boot-starter-thymeleaf<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>有个地方需要注意,SpringBoot项目默认静态资源都是放在resources/templates目录下,所以我们编写的HTML模板就需要放在该目录下,具体内容如下:<!DOCTYPE html><html lang=“en” xmlns=“http://www.w3.org/1999/xhtml" xmlns:th=“http://www.thymeleaf.org”><head> <meta charset=“UTF-8”> <title th:text="${title}">Title</title></head><body> <h1 th:text="${name}">Demo</h1> <h1 th:text="${phone}">xxx</h1></body></html>发送模板邮件主要实现代码override fun sendMail(receiver: String, title: String, o: Any, templateName: String) { logger.info(“开始发送邮件服务,To:{}”, receiver) val message = mailSender.createMimeMessage() val helper = MimeMessageHelper(message, true) helper.setFrom(sender) helper.setTo(receiver) helper.setSubject(title) val context = Context() context.setVariable(“title”, title) /* * 设置动态数据,这里不建议强转,具体业务需求传入具体的对象 / context.setVariables(o as MutableMap<String, Any>?) / * 读取取模板html代码并赋值 / val content = templateEngine.process(templateName, context) helper.setText(content, true) mailSender.send(message) logger.info(“邮件发送结束”)}测试代码@Testfun sendMail() { val model = HashMap<String, Any>() model[“name”] = “Tom” model[“phone”] = “69288888” mailService.sendMail(“xmsjgzs@163.com”, “Kotlin Template Mail”, model, “mail”)}查看邮件我们可以看到如下内容:邮件添加附件附件的添加也是非常容易的,我需要先把发送的附件放在resources/templates目录下,然后在MimeMessageHelper对象中设置相应的属性即可,如下所示:helper.addAttachment(“test.txt”, FileSystemResource(File(“test.txt”)))完整的代码package io.intodream.kotlin06.service.implimport io.intodream.kotlin06.service.MailServiceimport org.slf4j.Loggerimport org.slf4j.LoggerFactoryimport org.springframework.beans.factory.annotation.Autowiredimport org.springframework.beans.factory.annotation.Valueimport org.springframework.core.io.FileSystemResourceimport org.springframework.mail.javamail.JavaMailSenderimport org.springframework.mail.javamail.MimeMessageHelperimport org.springframework.stereotype.Serviceimport org.thymeleaf.TemplateEngineimport org.thymeleaf.context.Contextimport java.io.File/* * {描述} * * @author yangxianxi@gogpay.cn * @date 2019/4/8 19:19 * /@Serviceclass MailServiceImpl @Autowired constructor(private var mailSender: JavaMailSender, private var templateEngine: TemplateEngine) : MailService{ val logger : Logger = LoggerFactory.getLogger(MailServiceImpl::class.java) @Value(”${email.sender}") val sender: String = “6928700@qq.com” override fun sendSimple(receiver: String, title: String, content: String) { logger.info(“发送简单邮件服务”) val message = mailSender.createMimeMessage() val helper = MimeMessageHelper(message, true) helper.setFrom(sender) helper.setTo(receiver) helper.setSubject(title) helper.setText(content) mailSender.send(message) } override fun sendMail(receiver: String, title: String, o: Any, templateName: String) { logger.info(“开始发送邮件服务,To:{}”, receiver) val message = mailSender.createMimeMessage() val helper = MimeMessageHelper(message, true) helper.setFrom(sender) helper.setTo(receiver) helper.setSubject(title) val context = Context() context.setVariable(“title”, title) / * 设置动态数据,这里不建议强转,具体业务需求传入具体的对象 / context.setVariables(o as MutableMap<String, Any>?) / * 添加附件 / helper.addAttachment(“test.txt”, FileSystemResource(File(“test.txt”))) / * 读取取模板html代码并赋值 / val content = templateEngine.process(templateName, context) helper.setText(content, true) mailSender.send(message) logger.info(“邮件发送结束”) }}测试代码package io.intodream.kotlin06.service.implimport io.intodream.kotlin06.service.MailServiceimport org.junit.Testimport org.junit.runner.RunWithimport org.springframework.beans.factory.annotation.Autowiredimport org.springframework.boot.test.context.SpringBootTestimport org.springframework.test.context.junit4.SpringJUnit4ClassRunner/* * {描述} * * @author yangxianxi@gogpay.cn * @date 2019/4/9 18:38 */@RunWith(SpringJUnit4ClassRunner::class)@SpringBootTestclass MailServiceImplTest { @Autowired lateinit var mailService: MailService @Test fun sendSimple() { mailService.sendSimple(“xmsjgzs@163.com”, “Hello Kotlin Mail”, “SpringBoot Kotlin 专栏学习之JavaMailSender发送邮件”) } @Test fun sendMail() { val model = HashMap<String, Any>() model[“name”] = “Tom” model[“phone”] = “69288888” mailService.sendMail(“xmsjgzs@163.com”, “Kotlin Template Mail”, model, “mail”) }}关于Kotlin使用JavaMailSender发送邮件的介绍就到此结束了,如果大家觉得教程有用麻烦点一下赞,如果有错误的地方欢迎指出。 ...

April 16, 2019 · 2 min · jiezi

SpringBoot 2.X Kotlin 系列之Reactive Mongodb 与 JPA

一、本节目标前两章主要讲了SpringBoot Kotlin的基本操作,这一章我们将学习使用Kotlin访问MongoDB,并通过JPA完成(Create,Read,Update,Delete)简单操作。这里有一个问题什么不选用MySQL数据库呢?答案是 Spring Data Reactive Repositories 目前支持 Mongo、Cassandra、Redis、Couchbase。不支持 MySQL,那究竟为啥呢?那就说明下 JDBC 和 Spring Data 的关系。Spring Data Reactive Repositories 突出点是 Reactive,即非阻塞的。区别如下:基于 JDBC 实现的 Spring Data,比如 Spring Data JPA 是阻塞的。原理是基于阻塞 IO 模型 消耗每个调用数据库的线程(Connection)。事务只能在一个 java.sql.Connection 使用,即一个事务一个操作。二、构建项目及配置本章不在讲解如何构建项目了,大家可以参考第一章。这里我们主要引入了mongodb-reactive框架,在pom文件加入下列内容即可。<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId></dependency>如何jar包后我们需要配置一下MongoDB数据库,在application.properties文件中加入一下配置即可,密码和用户名需要替换自己的,不然会报错的。spring.data.mongodb.host=localhostspring.data.mongodb.port=27017spring.data.mongodb.password=student2018.Docker_spring.data.mongodb.database=studentspring.data.mongodb.username=student三、创建实体及具体实现3.1实体创建package io.intodream.kotlin03.entityimport org.springframework.data.annotation.Idimport org.springframework.data.mongodb.core.mapping.Document/** * @description * * @author Jwenk * @copyright intoDream.io 筑梦科技 * @email xmsjgzs@163.com * @date 2019-03-25,18:05 /@Documentclass Student { @Id var id :String? = null var name :String? = null var age :Int? = 0 var gender :String? = null var sClass :String ?= null override fun toString(): String { return ObjectMapper().writeValueAsString(this) }}3.2Repositorypackage io.intodream.kotlin03.daoimport io.intodream.kotlin03.entity.Studentimport org.springframework.data.mongodb.repository.ReactiveMongoRepository/* * @description * * @author Jwenk * @copyright intoDream.io 筑梦科技 * @email xmsjgzs@163.com * @date 2019-03-25,18:04 /interface StudentRepository : ReactiveMongoRepository<Student, String>{}3.3Servicepackage io.intodream.kotlin03.serviceimport io.intodream.kotlin03.entity.Studentimport reactor.core.publisher.Fluximport reactor.core.publisher.Mono/* * @description * * @author Jwenk * @copyright intoDream.io 筑梦科技 * @email xmsjgzs@163.com * @date 2019-03-25,18:04 /interface StudentService { /* * 通过学生编号获取学生信息 / fun find(id : String): Mono<Student> /* * 查找所有学生信息 / fun list(): Flux<Student> /* * 创建一个学生信息 / fun create(student: Student): Mono<Student> /* * 通过学生编号删除学生信息 / fun delete(id: String): Mono<Void>}// 接口实现类package io.intodream.kotlin03.service.implimport io.intodream.kotlin03.dao.StudentRepositoryimport io.intodream.kotlin03.entity.Studentimport io.intodream.kotlin03.service.StudentServiceimport org.springframework.beans.factory.annotation.Autowiredimport org.springframework.stereotype.Serviceimport reactor.core.publisher.Fluximport reactor.core.publisher.Mono/* * @description * * @author Jwenk * @copyright intoDream.io 筑梦科技 * @email xmsjgzs@163.com * @date 2019-03-25,18:23 /@Serviceclass StudentServiceImpl : StudentService{ @Autowired lateinit var studentRepository: StudentRepository override fun find(id: String): Mono<Student> { return studentRepository.findById(id) } override fun list(): Flux<Student> { return studentRepository.findAll() } override fun create(student: Student): Mono<Student> { return studentRepository.save(student) } override fun delete(id: String): Mono<Void> { return studentRepository.deleteById(id) }}3.4 Controller实现package io.intodream.kotlin03.webimport io.intodream.kotlin03.entity.Studentimport io.intodream.kotlin03.service.StudentServiceimport org.slf4j.LoggerFactoryimport org.springframework.beans.factory.annotation.Autowiredimport org.springframework.web.bind.annotation.import reactor.core.publisher.Fluximport reactor.core.publisher.Mono/ * @description * * @author Jwenk * @copyright intoDream.io 筑梦科技 * @email xmsjgzs@163.com * @date 2019-03-25,18:03 /@RestController@RequestMapping("/api/student")class StudentController { @Autowired lateinit var studentService: StudentService val logger = LoggerFactory.getLogger(this.javaClass) /* * 保存或新增学生信息 / @PostMapping("/") fun create(@RequestBody student: Student): Mono<Student> { logger.info("【保存学生信息】请求参数:{}", student) return studentService.create(student) } /* * 更新学生信息 / @PutMapping("/") fun update(@RequestBody student: Student): Mono<Student> { logger.info("【更新学生信息】请求参数:{}", student) return studentService.create(student) } /* * 查找所有学生信息 / @GetMapping("/list") fun listStudent(): Flux<Student> { return studentService.list() } /* * 通过学生编号查找学生信息 / @GetMapping("/id") fun student(@RequestParam id : String): Mono<Student> { logger.info(“查询学生编号:{}”, id) return studentService.find(id) } /* * 通过学生编号删除学生信息 */ @DeleteMapping("/") fun delete(@RequestParam id: String): Mono<Void> { logger.info(“删除学生编号:{}”, id) return studentService.delete(id) }}四、接口测试这里我们使用Postman来对接口进行测试,关于Postman这里接不用做过多的介绍了,不懂可以自行百度。控制台打印如下:2019-03-25 18:57:04.333 INFO 2705 — [ctor-http-nio-3] i.i.kotlin03.web.StudentController : 【保存学生信息】请求参数:{“id”:“1”,“name”:“Tom”,“age”:18,“gender”:“Boy”,“sclass”:“First class”}我们看一下数据库是否存储了其他接口测试情况如果大家觉得文章有用麻烦点一下赞,有问题的地方欢迎大家指出来。 ...

April 10, 2019 · 2 min · jiezi

SpringBoot Kotlin 系列之HTML与WebFlux

上一章我们提到过Mono 与 Flux,对于具体的介绍没说到,这一章我在这里简单介绍一下,既然提到Mono和Flux,那肯定得提到什么是响应式编程,什么是WebFlux。一、什么是响应式编程对于关于什么是响应编程,网上的说也很多,这里简单一句话介绍:响应式编程是基于异步和事件驱动的非阻塞程序,只是垂直通过在 JVM 内启动少量线程扩展,而不是水平通过集群扩展。二、Mono 与 FluxMono 和 Flux Reactor 是提供的两种响应式APIMono:实现发布者,并返回 0 或 1 个元素Flux:实现发布者,并返回 N 个元素三、什么是Spring WebfluxSpring Boot Webflux 就是基于 Reactor 实现的。Spring Boot 2.0 包括一个新的 spring-webflux 模块。该模块包含对响应式 HTTP 和 WebSocket 客户端的支持,以及对 REST,HTML 和 WebSocket 交互等程序的支持。一般来说,Spring MVC 用于同步处理,Spring Webflux 用于异步处理。Spring Boot Webflux 有两种编程模型实现,一种类似 Spring MVC 注解方式,另一种是使用其功能性端点方式。注解的会在第二篇文章讲到,下面快速入门用 Spring Webflux 功能性方式实现。在Spring官方有介绍,如图所示:四、Thymeleaf渲染HTML这里就不演示如何创建项目了,大家参考第一章,我们需要引入Thymeleaf框架,在pom文件中添加如下内容即可:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>引入Thymeleaf后我们需要做一些简单的配置,在application.properties文件中直接粘贴即可。主要是包括常用的编码、是否开启缓存等等。spring.thymeleaf.cache=truespring.thymeleaf.check-template=truespring.thymeleaf.check-template-location=truespring.thymeleaf.enabled=truespring.thymeleaf.encoding=UTF-8spring.thymeleaf.mode=HTML5spring.thymeleaf.prefix=classpath:/templates/spring.thymeleaf.servlet.content-type=text/htmlspring.thymeleaf.suffix=.html编写HTML,把文件放在resources/templates下<!DOCTYPE html><html lang=“zh-CN” xmlns:th=“http://www.w3.org/1999/xhtml"><head> <meta charset=“UTF-8”> <title>Title</title></head><body><h1>Hello <span th:text="${name}"></span></h1><h1>Now time <span th:text="${time}"></span></h1></body></html>编写Controllerpackage io.intodream.kotlin02.webimport org.springframework.stereotype.Controllerimport org.springframework.ui.Modelimport org.springframework.web.bind.annotation.GetMappingimport org.springframework.web.bind.annotation.RequestMappingimport reactor.core.publisher.Monoimport java.time.LocalDateTime/** * @description * * @author Jwenk * @copyright intoDream.io 筑梦科技 * @email xmsjgzs@163.com * @date 2019-03-24,18:24 */@RequestMapping("/webflux”)@Controllerclass IndexController { @GetMapping("/index") fun index(model : Model): Mono<String> { model.addAttribute(“name”, “Tom”) model.addAttribute(“time”, LocalDateTime.now()) return Mono.create{ monoSink -> monoSink.success(“index”)} }}启动项目,访问路径http://localhost:8080/webflux/index看到图片里面的内容则说明编写成功了,在Controller里面可以直接返回String,而不是Mono<String> ,但是 Mono 代表着我这个返回 View 也是回调的。 ...

April 10, 2019 · 1 min · jiezi

在函数范式中构建程序

什么是函数范式函数式编程(英语:functional programming) 以演算为理论基础的编程范型, 将电脑运算视为数学上的函数计算, 并且避免使用程序状态以及易变对象.比起命令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。电脑运算视为数学上的函数计算数学上的函数只要遵守计算准则, 输入和输出是确定的, 是可以被组合、可以被严格推理的$$f(x)=ax^2+bx+c$$复杂的函数是由简单的函数组合而成的, 函数范式也是由简单运算一层层组合为复杂执行过程的但电脑运算最大的障碍是电脑程序是会有副作用的因此函数范式的核心问题即是如何处理这些副作用副作用变量IO操作(文件读写、网络操作等)线程操作与系统的交互(GUI、硬件交互等)等强类型数学中函数是有严格定义域的, 即值的定义范围, 映射到程序中就是类型换句话说我们定义一个类型实际就是在定义一个值的范围(“范围"这个词可能不太准确,可能更类似“枚举”,比如“True”和“False”两个枚举的集合就是Boolean类型)电脑运算视为数学上的函数计算另一个过程即是定义域的转换, 如果数学函数需要严格的定义域, 程序中需要严格的类型限定也是很自然的事情fun plusOne(a: Int): Int函数名定义域值域plusOnea: Int: Int高阶类型、逆变协变、类型别名从数据结构开始构建程序程序 = 数据结构 + 算法OOP实际很多时候将数据结构和算法混杂到了一起, 函数范式则回归本源将两者分离:函数式程序 = 数据结构 + 算法程序编写开始之前, 首先定义数据结构函数式数据结构OOP提倡针对不同问题定义不同的数据结构, 这导致定义的数据结构通常不通用, 比如不同XML解析库会定义不同的专用数据类型函数范式则不同, 它们用很少一组关键数据结构(如list、set、map)来搭配专为这些数据结构深度优化过的操作。在这些关键数据结构和操作构成的一套运转机构上,按需要插入另外的数据结构和高阶函数来调整以适应具体问题。Example创造值域:sealed class ItemDatadata class ItemListData(…) : ItemData()data class ItemOptionData(…) : ItemData()List<ItemData>typealias Selecable<T> = Pair<T, Boolean>List<Selecable<ItemData>>不可变数据类型定义代数数据类型的一个基本限制是其中不能有变元, 这就是代数的的含义所以我们在函数范式中只能定义不可变数据比如连接两个list会产生一个新的list,对输入的两个list不做改变。从另一方面来解释这种限制:变量的存在往往是程序Bug的最终来源, 它的值依赖于运行时(线程切换状态、用户操作顺序、系统状态等等), 无法在测试期完全限制掉, 墨菲定律函数式数据结构中的数据共享当我们对一个已存在的列表xs在前面添加一个元素1的时候,返回一个新的列表,即Cons(1, xs),既然列表是不可变的,我们不需要去复制一份xs,可以直接复用它,这称为数据共享。共享不可变数据可以让函数实现更高的效率。我们可以返回不可变数据结构而不用担心后续代码修改它,不需要悲观地复制一份以避免对其修改或污染。所有关于更高效支持不同操作方式的纯函数式数据结构,其实都是找到一种聪明的方式来利用数据共享。正因如此,在大型程序里,函数式编程往往能取得比依赖副作用更好的性能。用组合代替继承OOP中算法和数据结构是混合在一起放在叫做类的东西里, 因此对算法(通常算法需要操作数据结构)的扩展需要通过继承的方式来实现, 这种处理方式有两个问题:多继承问题、难以组合难以额外扩展方法可以被复写意味着方法是可变的eg: 两种实现了不同功能的Activity继承扩展类无法被组合回溯上文, 我们说函数式程序 = 数据结构 + 算法, 数据结构被单独定义后, 算法就被独立出来了, 因此函数范式采用了更加灵活的方式组合这些算法类型类这些被分离出来的算法不同于OOP中的类, 所以函数范式中将之称之为类型类(typeclass)由于数据本身不可变, 所以不用像OOP一样需要严格private内聚一些内部方法以防止内部变量被非法操作后数据变得不符合预期因此函数范式中方法默认是public的Exampleinterface Eq<in F> { fun F.eqv(b: F): Boolean}interface Show<in A> { fun A.show(): String}interface ShowEq<in A> : Eq<A>, Show<A> { override fun A.show(): String = this.toString()}interface CharEqInstance : Eq<Char> { override fun Char.eqv(b: Char): Boolean = this == b}fun Char.Companion.eq(): Eq<Char> = object : CharEqInstance {}QuickCheck我们定义一个方法(函数)实际上就是在定义一个数学上的函数, 因此它应该是可以被严格验证的, 即指定定义域上值应该都能被映射到指定的值域测试在数学意义上就是做这个验证的, 输入所有定义域上的可能值验证是否正确映射到了值域传统的测试实际并没有覆盖全定义域, 因此可能导致某些额外情况. 函数范式中常用的测试框架QuickCheck即是尝试做全覆盖测试这也是为什么提倡对函数参数进行强类型化, 没有足够的类型限制实际就是扩展了函数的定义域, 这一般有两种情况:假定了输入数据的子域(比如参数是String, 却假定格式为1.1.2)包含了实际自己处理不了的数据(比如不能处理空数据, 却标明可以为空)甚至函数对象也是可以被生成的Exampleobject EqLaws { inline fun <F> laws(EQ: Eq<F>, noinline cf: (Int) -> F): List<Law> = listOf( Law(“Eq Laws: identity”) { EQ.identityEquality(cf) }, Law(“Eq Laws: commutativity”) { EQ.commutativeEquality(cf) } ) fun <F> Eq<F>.identityEquality(cf: (Int) -> F): Unit = forAll(Gen.int()) { int: Int -> val a = cf(int) a.eqv(a) == a.eqv(a) } fun <F> Eq<F>.commutativeEquality(cf: (Int) -> F): Unit = forAll(Gen.int()) { int: Int -> val a = cf(int) val b = cf(int) a.eqv(b) == b.eqv(a) }}副作用的分离副作用在程序中是确实存在的, 问题是如何将之分离出去, 函数范式的处理方式可以看做建造一个管道, 管道本身是代数的、确定的, 而其中流淌的就是副作用这些管道常见的有:IO、Single、Maybe等对于必须存在的变量的处理方法就是, 将之放到安全的地方, 一般而言即线程安全IOIO不同于我们通常意义上的input/output操作的意思, 它是Haskell中用来抽象外部作用的特殊数据结构, 它隐含了一个叫RealWorld的上下文用于和外部环境进行交互(即副作用), 所有的副作用必须包含在其中ExampleHaskell:putStrLn :: String -> IO ()getLine :: IO Stringmain = do name <- getLine putStrLn (“Hey, " ++ name)Kotlin:fun putStrLn(s: String): IO<Unit>fun getLine(): IO<String>IO.monad().binding { val s = readString().bind() putStrLn(s).bind()}Rx:fun putStrLn(s: String): Completablefun getLine(): Single<String>fun main() = getLine() .flatMapCompletable { s -> putStrLn(s) }main().blockingAwait() ...

April 8, 2019 · 2 min · jiezi

DslAdapter开发简介

DslAdapter开发简介DslAdapter是一个Android RecyclerView的Adapter构建器, DSL语法, 面向组合子设计. 专注类型安全, 所有代码采用Kotlin编写.为什么要开发DslAdapter实际上在DslAdapter开始开发的时点已经有很多RecyclerAdapter的扩展库存在了, 有的甚至从2015年开始已经持续开发到了现在. 从功能上来说, 这些库的各项功能都非常成熟了, 几乎所有的需求都有涉及; 而从思想上来说, 各种构建方式都有相应的库 以现在很常见的库举例:CymChad/BaseRecyclerViewAdapterHelper 1w6 star这是Github上检索出Star最多的RecyclerAdapter的库, 它支持添加Item事件、添加列表加载动画、添加头部、尾部、树形列表等等,甚至设置空布局FastAdapter 这个库是以前项目中也使用过的库, 功能也相当丰富, 相比上面的库更注重List的功能, 是一个从2015年开始开发一直持续维护的库KidAdapter kotlin编写,DSL语法的构建器,利用了kotlin的语法特性,构建上更简单,同时也实现了types功能。但功能上相比上面的库就简单很多了甚至我多年前也已经写过一个相关库AdapterRenderer,也实现了很多功能。可以说RecyclerAdapter领域是最难以有新突破的地方了,似乎能做的只是在现有基础上小修小改而已了。但现有的库真的已经完美到这种程度了吗?不,现有的库也有很多的缺点:非强类型,为了兼容多类型而直接忽略数据类型信息,失去了编译期类型检查,只能靠编写时自我约束繁琐的模板代码。有些库会需要继承基础Adapter或者基础Holder继承方法来实现功能,因此会写大量的XXAdapter、XXItem逻辑代码被迫分离。也是由于需要单独写XXAdapter、XXItem,而这些小类抽象度低,该界面的逻辑被迫分离到了这些小类中,即使是业务联系很大的逻辑功能过于繁杂,抽象度低。功能上虽然丰富,但实际上包括了很多并不是RecyclerView应该关注的功能,导致变成了一个全功能的Adapter,Adapter的逻辑结构极其复杂,难以维护也难以扩展类中变量和算法混杂,需要时常注意状态的同步问题正因如此,为了解决以上这些问题,让我们编写的Adapter更简单、更安全,从而有了开发一个新库的想法一个新的Adapter库应该是什么样的想要构建一个Adapter的库,首先我们要想想我们这个新库应该是干什么的,这就需要回到RecyclerView这个库中Adapter被定义为什么。RecyclerAdapter被定义为数据适配器,即将数据和视图进行绑定:数据 –映射–> 视图List而RecyclerView之所以很强大是因为它已经不仅仅是用于显示List,它会需要显示复杂的视图结构,比如树状图、可展开列表等等数据 –映射–> 复杂结构 –渲染–> 视图List我们的Adapter库需要完成的工作简单来说就是:将数据变换为复杂的抽象结构(比如树状),再将复杂的抽象结构渲染为视图List(因为RecyclerView最终只支持平整单列表)开始构建定义基本组合子实际我们需要实现的是一个变换问题,无论最终我们需要的抽象结构是简单的List还是复杂的树状图本质上都只是做这么一个数据的变换,因此这个变换函数就是我们的基础组合子,我们可以通过基础组合子的相互组合实现复杂功能这个基本组合子就是DslAdapter库中的BaseRenderer类:interface Renderer<Data, VD : ViewData<Data>> { fun getData(content: Data): VD fun getItemId(data: VD, index: Int): Long = RecyclerView.NO_ID fun getItemViewType(data: VD, position: Int): Int fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder fun bind(data: VD, index: Int, holder: RecyclerView.ViewHolder) fun recycle(holder: RecyclerView.ViewHolder)}它包含作为RecyclerAdapter基础组合子需要的几个基本方法。数据放在哪儿函数范式中副作用是要严格分离的,而变量就是一种副作用,如果允许变量在Renderer中不受管制的存在会使Renderer组合子本身失去可组合性,同时数据也变得很不可靠(线程不安全、Renderer并不保证只在一个地方使用一次)而可以看到Renderer的基础方法中定义的都是纯函数,并且不包含允许副作用存在的IO等类型(这里的IO不同于Java中的input/output,而是指Haskell中的IO类型类),因此数据被设计为与Renderer严格分离(数据与算法的严格分离),ViewData即是这个被分离的数据interface ViewData<out OriD> : ViewDataOf<OriD> { val count: Int val originData: OriD}Adapter设计根据前一章的描述,我们构建的Renderer就是一个映射函数,因此Adapter也只需要使用这个映射函数将数据进行渲染即可class RendererAdapter<T, VD : ViewData<T>>( val initData: T, val renderer: BaseRenderer<T, VD>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { private val dataLock = Any() private var adapterViewData: VD = renderer.getData(initData) … override fun getItemCount(): Int = adapterViewData.count override fun getItemViewType(position: Int): Int = renderer.getItemViewType(adapterViewData, position) override fun getItemId(position: Int): Long = renderer.getItemId(adapterViewData, position) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = renderer.onCreateViewHolder(parent, viewType) override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) = renderer.bind(adapterViewData, position, holder) override fun onFailedToRecycleView(holder: RecyclerView.ViewHolder): Boolean { renderer.recycle(holder) return super.onFailedToRecycleView(holder) } override fun onViewRecycled(holder: RecyclerView.ViewHolder) { renderer.recycle(holder) }}可以看到Adapter实际只是将Renderer中的各个函数实际应用于原生RecyclerView.Adapter的各个方法中即可,极其简单另外,ViewData只保存于Adapter中一份,它其实就包含整个Adapter的所有状态,换句话说只要保存它,可以完全恢复RecyclerView的数据状态;另外ViewData是被锁保护起来的,保证数据的线程安全性定义基础Renderer组合子Renderer被我们定义为基础组合子,那我们需要哪些Renderer呢:EmptyRenderer: 空Renderer, count为0LayoutRenderer: 与View绑定的末端Renderer, 可自定义数量ConstantItemRenderer: 将常量绑定到View的末端Renderer, 可适配任意数据源, 可自定义数量MapperRenderer: 转换目标Renderer的数据源类型, 一般通过mapT()来使用它ListRenderer: 将目标Renderer转换为适配列表数据源SealedItemRenderer: 根据数据源具体数据选择不同的Renderer渲染, 比如对于Int?类型,可以在为null的时候用EmptyRenderer渲染; 不为null的时候使用LayoutRenderer渲染ComposeRenderer: 组合多个不同RendererDataBindingRenderer : Android Databinding支持的Renderer复杂的结构基本都可以通过组合他们来实现:val adapter = RendererAdapter.multipleBuild() .add(layout<Unit>(R.layout.list_header)) .add(none<List<Option<ItemModel>>>(), optionRenderer( noneItemRenderer = LayoutRenderer.dataBindingItem<Unit, ItemLayoutBinding>( count = 5, layout = R.layout.item_layout, bindBinding = { ItemLayoutBinding.bind(it) }, binder = { bind, item, _ -> bind.content = “this is empty item” }, recycleFun = { it.model = null; it.content = null; it.click = null }), itemRenderer = LayoutRenderer.dataBindingItem<Option<ItemModel>, ItemLayoutBinding>( count = 5, layout = R.layout.item_layout, bindBinding = { ItemLayoutBinding.bind(it) }, binder = { bind, item, _ -> bind.content = “this is some item” }, recycleFun = { it.model = null; it.content = null; it.click = null }) .forList() )) .build()以上Adapter可图示为:|–LayoutRenderer header||–SealedItemRenderer| |–none -> LayoutRenderer placeholder count 5| || |–some -> ListRenderer| |–DataBindingRenderer 1| |–DataBindingRenderer 2| |–…技术细节特征类型上面说到Renderer被定义为:Renderer<T, VD : ViewData<T>>, 其中ViewData因为和Renderer是强绑定的,所以往往一种ViewData和一种Renderer是一一对应的,在类型上ViewData和Renderer是相等的用法上来说可以这样来看:类型Renderer<T, VD : LayoutViewData<T>>可以被等价看待为LayoutRenderer<T>, 相同的道理, 由于Updater和ViewData也是一一对应的关系, 因此类型Updater<T, VD : LayoutViewData<T>>可以被等价看待为LayoutUpdater<T>因此类型LayoutViewData<T>可以看做LayoutXX系列所有类的一个特征类型:通过这个类型可以唯一地识别出其对应的原始类型在高阶类型的Java类型系统实现中也使用了类似的手法:可以注意到VIewData的原始定义中继承了ViewDataOf类型,这个类型原始定义是这样的:class ForViewData private constructor() { companion object }typealias ViewDataOf<T> = Kind<ForViewData, T>@Suppress(“UNCHECKED_CAST”, “NOTHING_TO_INLINE”)inline fun <T> ViewDataOf<T>.fix(): ViewData<T> = this as ViewData<T>其中ForViewData就是我们定义的特征类型,在fix()函数中我们通过识别Kind<ForViewData, T>中的ForViewData部分即可识别为其唯一对应的ViewData<T>,从而安全地进行类型转换注意,特征类型只是用于类型系统识别用,代码中我们实际并不会使用它的实例,因此可以看到上面定义的ForViewData类型是私有构造函数,无法被实例化另外,实际只要是具有唯一性任意类型都可以作为特征类型,可以利用已有的类型(比如LayoutViewData)、也可以单独定义(比如ForViewData)利用特征类型我们可以更灵活使用类型(见Kotlin与高阶类型),也可以简化冗余的类型信息比如之前版本的DslAdapter中,Adapter的泛型信息为:<T, VD : ViewData<T>, BR : BaseRenderer<T, VD>>包含了T、VD和BR三个类型信息,但根据我们上面的分析,VD和BR两个类型实际是等价的,他们包含了相同的类型特征,因此我们可以将其简化为<T, VD : ViewData<T>,两个泛型即可保留所有我们需要的类型信息对于复杂的Adapter这种简化对于减少编译系统压力来说的是更加明显的:比如原来的类型有6000多字符:ComposeRenderer<HConsK<ForIdT, Pair<ED, SearchConditions>, HConsK<ForIdT, Pair<ED, SearchConditions>, HNilK<ForIdT>>>, HConsK<ForComposeItem, Pair<Pair<ED, SearchConditions>, SealedItemRenderer<Pair<ED, SearchConditions>, HConsK<Kind<ForSealedItem, Pair<ED, SearchConditions>>, Pair<List<Pair<ED, CategoryInfo>>, ListRenderer<List<Pair<ED, CategoryInfo>>, Pair<ED, CategoryInfo>, MapperViewData<Pair<ED, CategoryInfo>, HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, ComposeViewData<HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, HConsK<ForComposeItemData, Pair<List<Pair<ED, NormalCategoryInfo>>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>, HConsK<ForComposeItemData, Pair<CategoryInfo, SealedItemRenderer<CategoryInfo, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<Unit, EmptyRenderer<Unit>>, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<CategoryInfo, DataBindingRenderer<CategoryInfo, CategoryInfo>>, HNilK<Kind<ForSealedItem, CategoryInfo>>>>>>, HNilK<ForComposeItemData>>>>>, MapperRenderer<Pair<ED, CategoryInfo>, HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, ComposeViewData<HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, HConsK<ForComposeItemData, Pair<List<Pair<ED, NormalCategoryInfo>>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>, HConsK<ForComposeItemData, Pair<CategoryInfo, SealedItemRenderer<CategoryInfo, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<Unit, EmptyRenderer<Unit>>, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<CategoryInfo, DataBindingRenderer<CategoryInfo, CategoryInfo>>, HNilK<Kind<ForSealedItem, CategoryInfo>>>>>>, HNilK<ForComposeItemData>>>>, ComposeRenderer<HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, HConsK<ForComposeItem, Pair<List<Pair<ED, NormalCategoryInfo>>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>, HConsK<ForComposeItem, Pair<CategoryInfo, SealedItemRenderer<CategoryInfo, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<Unit, EmptyRenderer<Unit>>, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<CategoryInfo, DataBindingRenderer<CategoryInfo, CategoryInfo>>, HNilK<Kind<ForSealedItem, CategoryInfo>>>>>>, HNilK<ForComposeItem>>>, HConsK<ForComposeItemData, Pair<List<Pair<ED, NormalCategoryInfo>>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>, HConsK<ForComposeItemData, Pair<CategoryInfo, SealedItemRenderer<CategoryInfo, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<Unit, EmptyRenderer<Unit>>, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<CategoryInfo, DataBindingRenderer<CategoryInfo, CategoryInfo>>, HNilK<Kind<ForSealedItem, CategoryInfo>>>>>>, HNilK<ForComposeItemData>>>>>>>, HConsK<Kind<ForSealedItem, Pair<ED, SearchConditions>>, Pair<List<Pair<ED, NormalCategoryInfo>>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>, HNilK<Kind<ForSealedItem, Pair<ED, SearchConditions>>>>>>>, HConsK<ForComposeItem, Pair<Pair<ED, SearchConditions>, MapperRenderer<Pair<ED, SearchConditions>, List<Pair<ED, NormalCategoryInfo>>, ListViewData<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>>, HNilK<ForComposeItem>>>, HConsK<ForComposeItemData, Pair<Pair<ED, SearchConditions>, SealedItemRenderer<Pair<ED, SearchConditions>, HConsK<Kind<ForSealedItem, Pair<ED, SearchConditions>>, Pair<List<Pair<ED, CategoryInfo>>, ListRenderer<List<Pair<ED, CategoryInfo>>, Pair<ED, CategoryInfo>, MapperViewData<Pair<ED, CategoryInfo>, HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, ComposeViewData<HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, HConsK<ForComposeItemData, Pair<List<Pair<ED, NormalCategoryInfo>>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>, HConsK<ForComposeItemData, Pair<CategoryInfo, SealedItemRenderer<CategoryInfo, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<Unit, EmptyRenderer<Unit>>, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<CategoryInfo, DataBindingRenderer<CategoryInfo, CategoryInfo>>, HNilK<Kind<ForSealedItem, CategoryInfo>>>>>>, HNilK<ForComposeItemData>>>>>, MapperRenderer<Pair<ED, CategoryInfo>, HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, ComposeViewData<HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, HConsK<ForComposeItemData, Pair<List<Pair<ED, NormalCategoryInfo>>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>, HConsK<ForComposeItemData, Pair<CategoryInfo, SealedItemRenderer<CategoryInfo, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<Unit, EmptyRenderer<Unit>>, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<CategoryInfo, DataBindingRenderer<CategoryInfo, CategoryInfo>>, HNilK<Kind<ForSealedItem, CategoryInfo>>>>>>, HNilK<ForComposeItemData>>>>, ComposeRenderer<HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, HConsK<ForComposeItem, Pair<List<Pair<ED, NormalCategoryInfo>>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>, HConsK<ForComposeItem, Pair<CategoryInfo, SealedItemRenderer<CategoryInfo, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<Unit, EmptyRenderer<Unit>>, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<CategoryInfo, DataBindingRenderer<CategoryInfo, CategoryInfo>>, HNilK<Kind<ForSealedItem, CategoryInfo>>>>>>, HNilK<ForComposeItem>>>, HConsK<ForComposeItemData, Pair<List<Pair<ED, NormalCategoryInfo>>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>, HConsK<ForComposeItemData, Pair<CategoryInfo, SealedItemRenderer<CategoryInfo, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<Unit, EmptyRenderer<Unit>>, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<CategoryInfo, DataBindingRenderer<CategoryInfo, CategoryInfo>>, HNilK<Kind<ForSealedItem, CategoryInfo>>>>>>, HNilK<ForComposeItemData>>>>>>>, HConsK<Kind<ForSealedItem, Pair<ED, SearchConditions>>, Pair<List<Pair<ED, NormalCategoryInfo>>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>, HNilK<Kind<ForSealedItem, Pair<ED, SearchConditions>>>>>>>, HConsK<ForComposeItemData, Pair<Pair<ED, SearchConditions>, MapperRenderer<Pair<ED, SearchConditions>, List<Pair<ED, NormalCategoryInfo>>, ListViewData<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD>, ListRenderer<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD, BR>>>, HNilK<ForComposeItemData>>>>简化后只有1900多个字符:ComposeRenderer<HConsK<ForIdT, Pair<ED, SearchConditions>, HConsK<ForIdT, Pair<ED, SearchConditions>, HNilK<ForIdT>>>, HConsK<ForComposeItemData, Pair<Pair<ED, SearchConditions>, SealedViewData<Pair<ED, SearchConditions>, HConsK<Kind<ForSealedItem, Pair<ED, SearchConditions>>, Pair<List<Pair<ED, CategoryInfo>>, ListViewData<List<Pair<ED, CategoryInfo>>, Pair<ED, CategoryInfo>, MapperViewData<Pair<ED, CategoryInfo>, HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, ComposeViewData<HConsK<ForIdT, List<Pair<ED, NormalCategoryInfo>>, HConsK<ForIdT, CategoryInfo, HNilK<ForIdT>>>, HConsK<ForComposeItemData, Pair<List<Pair<ED, NormalCategoryInfo>>, ListViewData<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD>>, HConsK<ForComposeItemData, Pair<CategoryInfo, SealedViewData<CategoryInfo, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<Unit, EmptyViewData<Unit>>, HConsK<Kind<ForSealedItem, CategoryInfo>, Pair<CategoryInfo, DataBindingViewData<CategoryInfo, CategoryInfo>>, HNilK<Kind<ForSealedItem, CategoryInfo>>>>>>, HNilK<ForComposeItemData>>>>>>>, HConsK<Kind<ForSealedItem, Pair<ED, SearchConditions>>, Pair<List<Pair<ED, NormalCategoryInfo>>, ListViewData<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD>>, HNilK<Kind<ForSealedItem, Pair<ED, SearchConditions>>>>>>>, HConsK<ForComposeItemData, Pair<Pair<ED, SearchConditions>, MapperViewData<Pair<ED, SearchConditions>, List<Pair<ED, NormalCategoryInfo>>, ListViewData<List<Pair<ED, NormalCategoryInfo>>, Pair<ED, NormalCategoryInfo>, VD>>>, HNilK<ForComposeItemData>>>>递归类型函数式列表函数范式中有List类型,但与OOP中的列表的结构是完全不同的:List a = Nil | Cons a (List a)或者用kotlin来描述为:sealed class List<A>object Nil : List<Nothing>data class Cons<T>(val head: T, val tail: List<T>) : List<T>它可以看做是一个自包含的数据结构:如果没有数据就是Nil;如果有数据则包含一个数据以及下一个List<T>,直到取到Nil结束即递归数据结构异构列表强类型化最困难的在于ComposeRenderer的强类型化,ComposeRenderer可以实现的是组合不同的Renderer:|– item1 (eg: itemRenderer)|– item2 (eg: stringRenderer)val composeRenderer = ComposeRenderer.startBuild .add(itemRenderer) .add(stringRenderer) .build()原始的实现方法可以通过一个List来保存:List<BaseRenderer>但这样就丢失了每个元素的类型特征信息,比如我们无法知道第二个元素是stringRenderer还是itemRenderer,这也是现有所有库都存在的问题,常常我们只能使用强制类型转换的方式来得到我们希望的类型,但没有类型系统的检查这种转换是不安全的,只能在运行期被检查出来无论是OOP的列表还是上面介绍的函数式列表都无法满足这个需求,他们在add()的时候都把类型特征丢弃了而异构列表可以将这些保留下来:sealed class HListobject HNil : HList()data class HCons<out H, out T : HList>(val head: H, val tail: T) : HList()可以看到它的数据结构和原始的函数式列表的结构很相似,都是递归数据结构但它在泛型中增加了out T : HList,这是一个引用了自己类型的泛型声明,即:HList = HCons<T1, HList>HList = HCons<T1, HCons<T2, HList>>HList = HCons<T1, HCons<T2, HCons<T3, HList>>>HList = HCons<T1, HCons<T2, HCons<T3, HCons<T4, HList>>>>…它的类型可以在不断的代换中形成类型列表,这种即是递归类型使用上:// 原函数fun test2(s: String, i: Int): List<Any?> = listOf(s, i)// 异构列表fun test2(s: String, i: Int): HCons<Int, HCons<String, HNil>> = HCons(i, HCons(s, HNil))同样是构建列表, 异构列表包含了更丰富的类型信息:容器的size为2容器中第一个元素为String, 第二个为Int相比传统列表,异构列表的优势:完整保存所有元素的类型信息自带容器的size信息完整保存每个元素的位置信息这是基本的异构列表,DslAdapter为了做类型限定而自定义了高阶异构列表,可以参考源码HListK.kt递归类型递归类型是指的包含有自己的类型声明:fun <DL : HListK<ForIdT, DL>> test() = …这种泛型可以在不断代换中形成上面描述的类型列表:HList = HCons<T1, HList>HList = HCons<T1, HCons<T2, HList>>HList = HCons<T1, HCons<T2, HCons<T3, HList>>>HList = HCons<T1, HCons<T2, HCons<T3, HCons<T4, HList>>>>…在描述数量不确定的类型时很有用辅助类型在使用DslAdapter中可能会注意到有时会有一个奇特的参数type:fun <T, D, VD : ViewData<D>> BaseRenderer<D, VD>.mapT(type: TypeCheck<T>, mapper: (T) -> D, demapper: (oldData: T, newMapData: D) -> T) : MapperRenderer<T, D, VD> = …这个参数的实际值并不会在函数中被使用到,而跳转到TypeCheck的定义中:class TypeCheck<T>private val typeFake = TypeCheck<Nothing>()@Suppress(“UNCHECKED_CAST”)fun <T> type(): TypeCheck<T> = typeFake as TypeCheck<T>TypeCheck只是一个没有任何功能的空类型,返回的值也是固定的同一个值,那这个参数究竟是用来干什么的?要解释这个问题我们需要看一下mapT这个函数的调用:LayoutRenderer<String>(MOCK_LAYOUT_RES, 3) .mapT(type = type<TestModel>(), mapper = { it.msg }, demapper = { oldData, newMapData -> oldData.copy(msg = newMapData) })上面这段代码的作用是将接收String数据类型的Renderer转换为接受TestModel数据类型如果我们不使用type这个参数的话这段代码会变成什么样呢:LayoutRenderer<String>(MOCK_LAYOUT_RES, 3) .map<TestModel, String, LayoutViewData<String>>( mapper = { it.msg }, demapper = { oldData, newMapData -> oldData.copy(msg = newMapData) })可以看到由于函数的第一个泛型T类型系统也无法推测出来为TestModel,因此需要我们显式地把T的类型给写出来,但kotlin和Java的语法中无法只写泛型声明中的一项,所以我们不得不把其他可以被推测出来的泛型也显式的声明出来,不仅代码繁杂而且在类型复杂的时候几乎是不可完成的工作参数type中的泛型<T>就可以用于辅助编译器进行类型推测,我们只需要手动声明编译器难以自动推测的部分泛型,而其他可以被推测的泛型还是交由编译器自动完成,即可简化代码而这里的type = type<TestModel>()即是辅助类型,它的实例本身没有任何作用,它只是为了辅助编译器进行类型检查最后DslAdapter致力于完整的静态类型,使用了各种类型编程的技法,这是因为足够的类型信息不仅能帮编译器进行类型检查、早期排除问题,而且能够帮助我们在编码的时候编写足够准确的代码准确的代码意味着我们即不需要处理不会发生的额外情况、也不会少处理了可能的情况。(早期版本的DslAdapter的更新模块就是被设计为自动检查更新,导致需要处理大量额外情况,极其不安全)同时DslAdapter本身不是一个希望做到全能的库,它的目标是将Adapter的工作足够简化,并只专注于Adapter工作,其他功能就交给专注其他功能的库Do One Thing and Do It Well.但这并不意味着DslAdapter是一个抛弃了功能性的库,相反,它是一个极其灵活的库。它的核心被设计得非常简单,只有BaseRenderer和RendererAdapter, 这两个类也相当简单,并且由于全局只有一个变量被保存在RendererAdapter中,保证了数据的线程安全性。而数据中都是不可变属性,使内部数据也可以被完全暴露出来,方便了功能的扩展实际上DSL更新器 dsladapter-updater模块以及DSL position获取器 dsladapter-position模块都是以扩展方式存在的,后续还会根据需求继续扩展其他模块本文只是浅尝则止地介绍了一点DslAdapter的开发技巧,欢迎Star和提出issue ...

April 3, 2019 · 5 min · jiezi

对比Java泛型中的extends/super和Kotlin的out/in

欢迎关注我的博客:songjhh’s blog在 Java 泛型中,有一个叫做通配符上下界 bounded wildcard 的概念。<? extends T>:指的是上界通配符 (Upper Bounded Wildcards)<? super T>:指的是下界通配符 (Lower Bounded Wildcards)相对应在 Kotlin 泛型中,有 out 和 in 两个关键字下面我将会以工位分配的例子解释它可以用来解决什么问题,并且对比 Java 来说,Kotlin 作了什么改进。解决的问题这里有4个实体,分别是 Employee (员工基类),Manager (经理), DevManager (开发经理),WorkStation 工位。它们的关系如下:@Datapublic class Employee { private String name; public Employee(String name) { this.name = name; }}@Datapublic class Manager extends Employee { private Integer level; public Manager(String name) { super(name); }}@Datapublic class DevManager extends Manager { private String language; public DevManager(String name) { super(name); }}其中一个工位可以坐一个员工, 这里用泛型抽象出员工来:@Datapublic class WorkStation<T> { private T employee; public WorkStation(T employee) { this.employee = employee; }}按照逻辑,一个经理的工位,当然也是一个员工的工位,但事实真的如此吗?// 创建一个经理工位WorkStation<Manager> managerWorkStation = new WorkStation<>(new Manager(“John”));// 将经理工位赋给员工工位WorkStation<Employ> employWorkStation = managerWorkStation; // error但这里会报 incompatible types: WorkStation<Manager> cannot be converted to WorkStation<Employee>,意思是两个类型不能相互转化。虽然 Manager 继承于 Employee ,但是两个类型的工位并没有继承关系,所以不能直接将经理工位的引用传给员工工位。造成这个现象的原因,是因为Java 的参数类型是不型变的 invariant,而通配符上下界正是为了绕过这个问题。ps: 型变在计算机编程中,特别是面向对象编程,是重要的基石,可以在测试阶段帮助程序员发现很多的错误,这里不展开讨论。有界限的通配符(Bounded Wildcards)为了帮助理解和记忆,在讲通配符上下界之前,这里先讲一讲PECS原则。PECS stands for producer-extends, consumer-super From: Effective Java Third Edition - Item 31这里引用的是 Effective Java Third Edition 关于如何利用 bounded wildcards 来提升 API 灵活性章节一个助记词。简单来说,生产者适合用 <? extends T>,而消费者适合用 <? super T>,这里生产者指的是能用来读取的对象,消费者指的是用来写入的对象,下面将会详细解释这两个概念。上界通配符(extends)还是接着上面的例子,员工的工位为了获得经理工位的引用,这里使用上界通配符 <? extends T>// 创建一个经理工位WorkStation<Manager> managerWorkStation = new WorkStation<>(new Manager(“John”));// 将经理工位的引用赋给一个继承于员工对象的工位WorkStation<? extends Employee> exWorkStation = managerWorkStation;可以看到使用了上界通配符,我们将经理工位和员工工位关联起来了,使得 Java 泛型的灵活性大大增加。但是上面介绍了 PECS原则 , 它指出上界通配符只适合用于生产者中,下面我带大家来看看这句话如何理解:WorkStation<Manager> managerWorkStation = new WorkStation<>(new Manager(“John”));WorkStation<? extends Employee> exWorkStation = managerWorkStation;// 只可以获取它和它的基类Object a = exWorkStation.getEmployee();Employee b = exWorkStation.getEmployee();DevManager d = exWorkStation.getEmployee(); // error// 不可以存储exWorkStation.setEmployee(new Employee(“Sam”)); // error, incompatible types: Manager cannot be coverted to capture#1 of ? extends EmployeeexWorkStation.setEmployee(new DevManager(“James”)); // error, incompatible types: DevManager cannot be coverted to capture#1 of ? extends Employee上面的例子可以看到,使用了上界通配符只能用 get() 方法取出工位占位的类型和其基类,但是不能再用 set() 方法存对象到工位中,所以说上界通配符只适合用于生产者中。原因也很好理解,因为编译器只知道工位坐的人是 Employee 对象或它的派生类,但不知道具体是哪个对象(编译器用 capture#1 标记占位,指这里捕获 Employee 和它的子类),所以不能够判断存入的对象是不是这个工位能够匹配的:坐在 exWorkStation 的人一定是一个员工,所以可以取出 EmployeeexWorkStation 可能是 Manager 的工位,所以这里存取 TestManager 是没问题的。但问题在于它也可能是 DevManager 的工位,那么 TestManager 就不能坐在这个工位里了,编辑器无法判断,所以上界通配符不能用 set() 方法简而言之,上界通配符 Upper Bounded Wildcards 使得参数类型是协变的covariant。下界通配符和上界通配符恰恰相反,下界通配符 <? super T> 适合存储对象的场景。WorkStation<? super Manager> supWorkStation = new WorkStation<>(new Manager(“James”));// 可以存储它和它的子类supWorkStation.setEmployee(new DevManager(“Sam”));supWorkStation.setEmployee(new Manager(“Sam”));supWorkStation.setEmployee(new Employee(“Sam”)); // error// 只可以获取所有类的基类 - ObjectObject o = supWorkStation.getEmployee();Employee e = supWorkStation.getEmployee(); // errorManager e = supWorkStation.getEmployee(); // errorDevManager e = supWorkStation.getEmployee(); // error// 只能安全强转成它和它的基类Employee employee = (Employee) o;Manager manager = (Manager) o;WorkStation<? super Manager> w = new WorkStation<>(new Manager(“Sam”));// ClassCastException: Manager cannot be cast to DevManagerDevManager devManager = (DevManager) w.getEmployee();上面的例子可以看到,使用下界通配符可以用 set() 方法储存 Manager 和其子类,但只能用 get() 方法获得所有类的基类 Object 对象。使用强转的话,只能强转成 Manager 和它的基类,如果强转成 Manager 的子类的话,有可能会报 ClassCastException 运行时异常。因为存入方便,取出数据比较麻烦,所以说下界通配符适合使用在消费者中。究其原因,可以简单理解为,下界通配符标记了该工位至少是 Manager 的工位,所以这里无论是坐 DevManager 还是 TestManager 都没有问题。这个就叫做逆变性(contravariance)。在Kotlin的世界里是怎么样的?是 Java 世界是用通配符上下界来觉得泛型不型变的,那在 Kotlin 是怎么样的呢?val managerWorkStation: WorkStation<Manager> = WorkStation(Manager(“John”))val station: WorkStation<Employee> = managerWorkStation // error, type mismatch由此看到在 Kotlin 里对泛型也是有限制的。相对于 Java 提供的 <? extends T> 和 <? super T>,Kotlin 相对应提供了 out 和 in 关键字。在 Kotlin 中 out 相当于 <? extends T>,in 相当于 <? super T>,这里看看用法。out 关键字:val managerWorkStation: WorkStation<Manager> = WorkStation(Manager(“John”))val outStation: WorkStation<out Employee> = managerWorkStation// 只可以获取它和它的基类val a: Any = outStation.employeeval b: Employee = outStation.employeeval c: Employee = managerWorkStation.employeeval d: DevManager = managerWorkStation.employee // error, type mismatch// 不可以存储outStation.employee = DevManager(“Sam”) // Setter for ’employee’ is removed by type projectionin关键字:val inStation: WorkStation<in Manager> = WorkStation()// 可以存储它和它的子类inStation.employee = Manager(“James”)inStation.employee = DevManager(“James”)inStation.employee = Employee(“James”) // error, type mismatch// 只可以获得Anyval any: Any? = inStation.employee// 只能安全强转成它和它的基类val employee: Employee = any as Employeeval manager:Manager = any as Manager由以上两个例子可以看到,Kotlin 和 Java 非常相似,只是相关的关键字有所不同而已。但毕竟 Kotlin 是号称要解决 Java 的,那么会不会哪里有所不同呢?Kotlin 和 Java 的异同使用处型变在 Java 中,上下界通配符只能用在参数、属性、变量或者返回值中,不能在泛型声明处使用,所以才叫做使用处型变。以上的 Kotlin 例子也用的是使用处型变,被称为类型投影。所以 Java 和 Kotlin 都提供使用处型变。声明处型变但不同的是,Kotlin 还提供 Java 所不具备的声明处型变。顾名思义,Kotlin 提供的 out 和 in 两个型变关键字还可以用于泛型声明的时候。public interface Collection<out E> : Iterable<E> { …}// 错误,这里只能用val,不能用varclass Source<out T>(var t: T) { …}在声明处设置 out 后,使得了在 Kotlin 中,Collection<Number> 安全的作为 Collection<Int> 的父类使用,但 E 被标记为 out 后,E 只能被输出而不能写入。interface Comparable<in T> { operator fun compareTo(other: T): Int}fun demo(x: Comparable<Number>) { x.compareTo(1.0) // 1.0 拥有类型 Double,它是 Number 的子类型 // 因此,我们可以将 x 赋给类型为 Comparable <Double> 的变量 val y: Comparable<Double> = x}Comparable 在声明处设置 in 后,x 就可以和 Number 或它的子类进行比较了。总结以上就是 Java 和 Kotlin 关于泛型型变的内容,其中 Kotlin 对比 Java,多加了声明处型变的方式。JavaJava示例代码Kotlin示例代码使用处型变void example(List<? extends Number> list)fun example(list: List<out Number>)使用处逆变void example(List<? super Integer>)fun example(list: List<in Int>)声明处型变-interface Collection<out E> : Iterable<E>声明处逆变-interface Comparable<in T>为了帮助记忆,上文引用了PECS原则:producer-extends, consumer-super。最后这里再引用Effective Java - 31 | Use bounded wildcards to increase API flexibilty里面对通配符的几个意见:If an input parameter is both a producer and a consumer, then wildcard types will do you no good.如果输入参数同时是生产者和消费者, 那么通配符对你来说不是一个好的选择。Do not use bounded wildcard types as return types, if the user of a class has to think about wildcard types, there is probably something wrong with its API.不要用界限通配符作为你的返回类型,如果类的用户必须考虑通配符类型,类的 API 或许就会出错。If a type parameter appears only once in a method declaration, replace it with a wildcard.如果类型参数只在方法声明中出现一次,就可以用通配符取代它。谢谢阅读 ...

March 17, 2019 · 4 min · jiezi

Kotlin整合Vertx开发Web应用

今天我们尝试Kotlin整合Vertx,并决定建立一个非常简单的Web应用程序,使用Kotlin和Vertx作为编程语言进行编码构建。生成项目打开控制台窗口执行以下代码进行生成一个maven项目mvn archetype:generate -DgroupId=com.edurt.kvi -DartifactId=kotlin-vertx-integration -DarchetypeArtifactId=maven-archetype-quickstart -Dversion=1.0.0 -DinteractiveMode=false修改pom.xml增加java和kotlin的支持<project xmlns=“http://maven.apache.org/POM/4.0.0" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.edurt.kvi</groupId> <artifactId>kotlin-vertx-integration</artifactId> <packaging>jar</packaging> <version>1.0.0</version> <name>kotlin-vertx-integration</name> <description>Kotlin Vertx Integration is a open source kotlin vertx integration example.</description> <!– properties –> <properties> <!– dependency –> <dependency.kotlin.version>1.2.71</dependency.kotlin.version> <dependency.vertx.ersion>3.4.1</dependency.vertx.ersion> <!– plugin –> <plugin.maven.compiler.version>3.3</plugin.maven.compiler.version> <plugin.maven.javadoc.version>2.10.4</plugin.maven.javadoc.version> <plugin.maven.kotlin.version>1.2.71</plugin.maven.kotlin.version> <!– environment –> <environment.compile.java.version>1.8</environment.compile.java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <jvmTarget>1.8</jvmTarget> </properties> <!– dependencys –> <dependencies> <!– kotlin –> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib-jdk8</artifactId> <version>${dependency.kotlin.version}</version> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-reflect</artifactId> <version>${dependency.kotlin.version}</version> </dependency> <!– vertx –> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-core</artifactId> <version>${dependency.vertx.ersion}</version> </dependency> <dependency> <groupId>io.vertx</groupId> <artifactId>vertx-web</artifactId> <version>${dependency.vertx.ersion}</version> </dependency> </dependencies> <!– prerequisites –> <prerequisites> <maven>3.5.0</maven> </prerequisites> <!– build –> <build> <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory> <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory> <plugins> <plugin> <artifactId>kotlin-maven-plugin</artifactId> <groupId>org.jetbrains.kotlin</groupId> <configuration> <args> <arg>-Xjsr305=strict</arg> </args> <compilerPlugins> <plugin>spring</plugin> <plugin>jpa</plugin> <plugin>all-open</plugin> </compilerPlugins> <pluginOptions> <option>all-open:annotation=javax.persistence.Entity</option> </pluginOptions> </configuration> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-allopen</artifactId> <version>${plugin.maven.kotlin.version}</version> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-noarg</artifactId> <version>${plugin.maven.kotlin.version}</version> </dependency> </dependencies> <executions> <execution> <id>kapt</id> <goals> <goal>kapt</goal> </goals> <configuration> <sourceDirs> <sourceDir>src/main/kotlin</sourceDir> </sourceDirs> <annotationProcessorPaths> <annotationProcessorPath> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <version>${project.parent.version}</version> </annotationProcessorPath> </annotationProcessorPaths> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>${plugin.maven.compiler.version}</version> <configuration> <source>${environment.compile.java.version}</source> <target>${environment.compile.java.version}</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> <version>${plugin.maven.javadoc.version}</version> <configuration> <aggregate>true</aggregate> <!– custom tags –> <tags> <tag> <name>Description</name> <placement>test</placement> <head>description</head> </tag> </tags> <!– close jdoclint check document –> <additionalparam>-Xdoclint:none</additionalparam> </configuration> </plugin> </plugins> </build></project>添加Vertx实例创建CoreVerticle类文件package com.edurt.kvi.coreimport io.vertx.core.AbstractVerticleimport io.vertx.core.Futureimport io.vertx.core.Handlerimport io.vertx.ext.web.Routerimport io.vertx.ext.web.RoutingContextclass CoreVerticle : AbstractVerticle() { override fun start(startFuture: Future<Void>?) { val router = createRouter() val port = config().getInteger(“http.port”, 8080) vertx.createHttpServer() .requestHandler { router.accept(it) } .listen(port) { result -> if (result.succeeded()) { startFuture?.complete() } else { startFuture?.fail(result.cause()) } } } private fun createRouter() = Router.router(vertx).apply { get(”/”).handler(handlerRoot) } /** * create router instance / val handlerRoot = Handler<RoutingContext> { req -> req.response().end(“Hello Kotlin Vertx Integration!”) }}设置启动类package com.edurt.kviimport com.edurt.kvi.core.CoreVerticleimport io.vertx.core.Vertxclass KotlinVertxIntegrationfun main(args: Array<String>) { val vertx = Vertx.vertx() vertx.deployVerticle(CoreVerticle::class.java.name)}以上操作在vertx.deployVerticle阶段执行了部署Verticle的操作,即部署CoreVerticle。启动应用后浏览器访问http://localhost:8080出现以下页面增加页面渲染功能修改pom.xml文件增加页面依赖<dependency.slf4j.version>1.7.25</dependency.slf4j.version><dependency> <groupId>io.vertx</groupId> <artifactId>vertx-web-templ-thymeleaf</artifactId> <version>${dependency.vertx.ersion}</version></dependency><dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${dependency.slf4j.version}</version></dependency>增加页面渲染文件package com.edurt.kvi.routerimport io.vertx.ext.web.Routerimport io.vertx.ext.web.RoutingContextimport io.vertx.ext.web.templ.ThymeleafTemplateEngineimport org.thymeleaf.templatemode.TemplateModeclass HomeViewRouterfun index(r: Router) { val engine = ThymeleafTemplateEngine.create().setMode(TemplateMode.HTML) r.get("/index.html”).handler { c -> render(c, engine, “templates/index.html”) }}fun render(c: RoutingContext, engine: ThymeleafTemplateEngine, templ: String) { engine.render(c, templ) { res -> if (res.succeeded()) { c.response().end(res.result()) } else { c.fail(res.cause()) } }}在templates/index.html目录下创建页面文件<!DOCTYPE html SYSTEM “http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-4.dtd"><html xmlns=“http://www.w3.org/1999/xhtml"><head> <title>Kotlin Vertx Integration</title> <meta http-equiv=“Content-Type” content=“text/html; charset=UTF-8”/></head><body><p>Welcome To Kotlin Vertx Integration!</p></body></html>修改CoreVerticle增加页面跳转package com.edurt.kvi.coreimport com.edurt.kvi.router.indeximport io.vertx.core.AbstractVerticleimport io.vertx.core.Futureimport io.vertx.core.Handlerimport io.vertx.core.Vertximport io.vertx.core.http.HttpServerResponseimport io.vertx.ext.web.Routerimport io.vertx.ext.web.RoutingContextclass CoreVerticle : AbstractVerticle() { override fun start() { val router = createRouter(vertx) // go to index page index(router) vertx.createHttpServer().requestHandler { handler -> router.accept(handler) }.listen(8080)// val port = config().getInteger(“http.port”, 8080)// vertx.createHttpServer()// .requestHandler { router.accept(it) }// .listen(port) { result ->// if (result.succeeded()) {// startFuture?.complete()// } else {// startFuture?.fail(result.cause())// }// } } private fun createRouter() = Router.router(vertx).apply { get(”/”).handler(handlerRoot) } /* * create router instance */ val handlerRoot = Handler<RoutingContext> { req -> req.response().end(“Hello Kotlin Vertx Integration!”) } fun createRouter(v: Vertx): Router { var router = Router.router(v) router.route("/").handler { c -> c.response().end(“Hello Kotlin Vertx Integration!”) } router.route("/index").handler { c -> c.response().html().end(“Hello Kotlin Vertx Integration Page!”) } return router } fun HttpServerResponse.html(): HttpServerResponse { return this.putHeader(“content-type”, “text/html”) }}启动应用后浏览器访问http://localhost:8080/index.html出现以下页面 ...

February 26, 2019 · 3 min · jiezi

SpringBoot整合Kotlin构建Web服务

今天我们尝试Spring Boot整合Kotlin,并决定建立一个非常简单的Spring Boot微服务,使用Kotlin作为编程语言进行编码构建。创建一个简单的Spring Boot应用程序。我会在这里使用maven构建项目:<?xml version=“1.0” encoding=“UTF-8”?><project xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xmlns=“http://maven.apache.org/POM/4.0.0" xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.edurt.ski</groupId> <artifactId>springboot-kotlin-integration</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <name>springboot kotlin integration</name> <description>SpringBoot Kotlin Integration is a open source springboot, kotlin integration example.</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> <!– lookup parent from repository –> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <!– plugin config –> <plugin.maven.kotlin.version>1.2.71</plugin.maven.kotlin.version> </properties> <dependencies> <!– spring boot –> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!– kotlin –> <dependency> <groupId>com.fasterxml.jackson.module</groupId> <artifactId>jackson-module-kotlin</artifactId> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib-jdk8</artifactId> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-reflect</artifactId> </dependency> </dependencies> <build> <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory> <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <artifactId>kotlin-maven-plugin</artifactId> <groupId>org.jetbrains.kotlin</groupId> <configuration> <args> <arg>-Xjsr305=strict</arg> </args> <compilerPlugins> <plugin>spring</plugin> <plugin>jpa</plugin> <plugin>all-open</plugin> </compilerPlugins> <pluginOptions> <option>all-open:annotation=javax.persistence.Entity</option> </pluginOptions> </configuration> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-allopen</artifactId> <version>${plugin.maven.kotlin.version}</version> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-noarg</artifactId> <version>${plugin.maven.kotlin.version}</version> </dependency> </dependencies> <executions> <execution> <id>kapt</id> <goals> <goal>kapt</goal> </goals> <configuration> <sourceDirs> <sourceDir>src/main/kotlin</sourceDir> </sourceDirs> <annotationProcessorPaths> <annotationProcessorPath> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <version>${project.parent.version}</version> </annotationProcessorPath> </annotationProcessorPaths> </configuration> </execution> </executions> </plugin> </plugins> </build></project>添加所有必需的依赖项:kotlin-stdlib-jdk8 kotlin jdk8的lib包kotlin-reflect kotlin反射包一个简单的应用类:package com.edurt.skiimport org.springframework.boot.autoconfigure.SpringBootApplicationimport org.springframework.boot.runApplication@SpringBootApplicationclass SpringBootKotlinIntegrationfun main(args: Array<String>) { runApplication<SpringBootKotlinIntegration>(args)}添加Rest API接口功能创建一个HelloController Rest API接口,我们只提供一个简单的get请求获取hello,kotlin输出信息:package com.edurt.ski.controllerimport org.springframework.web.bind.annotation.GetMappingimport org.springframework.web.bind.annotation.RestController@RestControllerclass HelloController { @GetMapping(value = “hello”) fun hello(): String { return “hello,kotlin” }}修改SpringBootKotlinIntegration文件增加以下设置扫描路径@ComponentScan(value = [ “com.edurt.ski”, “com.edurt.ski.controller”])添加页面功能修改pom.xml文件增加以下页面依赖<!– mustache –><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mustache</artifactId></dependency>在src/main/resources路径下创建templates文件夹在templates文件夹下创建一个名为hello.mustache的页面文件<h1>Hello, Kotlin</h1>创建页面转换器HelloViewpackage com.edurt.ski.viewimport org.springframework.stereotype.Controllerimport org.springframework.ui.Modelimport org.springframework.web.bind.annotation.GetMapping@Controllerclass HelloView { @GetMapping(value = “hello_view”) fun helloView(model: Model): String { return “hello” }}浏览器访问http://localhost:8080/hello_view即可看到页面内容添加数据持久化功能修改pom.xml文件增加以下依赖(由于测试功能我们使用h2内存数据库)<!– data jpa and db –><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope></dependency>创建User实体package com.edurt.ski.modelimport javax.persistence.Entityimport javax.persistence.GeneratedValueimport javax.persistence.Id@Entity//class UserModel(// @Id// @GeneratedValue// private var id: Long? = 0,// private var name: String//)class UserModel { @Id @GeneratedValue var id: Long? = 0 get() = field set var name: String? = null get() = field set}创建UserSupport dao数据库操作工具类package com.edurt.ski.supportimport com.edurt.ski.model.UserModelimport org.springframework.data.repository.PagingAndSortingRepositoryinterface UserSupport : PagingAndSortingRepository<UserModel, Long> {}创建UserService服务类package com.edurt.ski.serviceimport com.edurt.ski.model.UserModelinterface UserService { /* * save model to db / fun save(model: UserModel): UserModel}创建UserServiceImpl实现类package com.edurt.ski.serviceimport com.edurt.ski.model.UserModelimport com.edurt.ski.support.UserSupportimport org.springframework.stereotype.Service@Service(value = “userService”)class UserServiceImpl(private val userSupport: UserSupport) : UserService { override fun save(model: UserModel): UserModel { return this.userSupport.save(model) }}创建用户UserController进行持久化数据package com.edurt.ski.controllerimport com.edurt.ski.model.UserModelimport com.edurt.ski.service.UserServiceimport org.springframework.web.bind.annotation.PathVariableimport org.springframework.web.bind.annotation.PostMappingimport org.springframework.web.bind.annotation.RequestMappingimport org.springframework.web.bind.annotation.RestController@RestController@RequestMapping(value = “user”)class UserController(private val userService: UserService) { @PostMapping(value = “save/{name}”) fun save(@PathVariable name: String): UserModel { val userModel = UserModel()// userModel.id = 1 userModel.name = name return this.userService.save(userModel) }}使用控制台窗口执行以下命令保存数据curl -X POST http://localhost:8080/user/save/qianmoQ收到返回结果{“id”:1,“name”:“qianmoQ”}表示数据保存成功增加数据读取渲染功能修改UserService增加以下代码/* * get all model */fun getAll(page: Pageable): Page<UserModel>修改UserServiceImpl增加以下代码override fun getAll(page: Pageable): Page<UserModel> { return this.userSupport.findAll(page)}修改UserController增加以下代码@GetMapping(value = “list”)fun get(): Page<UserModel> = this.userService.getAll(PageRequest(0, 10))创建UserView文件渲染User数据package com.edurt.ski.viewimport com.edurt.ski.service.UserServiceimport org.springframework.data.domain.PageRequestimport org.springframework.stereotype.Controllerimport org.springframework.ui.Modelimport org.springframework.ui.setimport org.springframework.web.bind.annotation.GetMapping@Controllerclass UserView(private val userService: UserService) { @GetMapping(value = “user_view”) fun helloView(model: Model): String { model[“users”] = this.userService.getAll(PageRequest(0, 10)) return “user” }}创建user.mustache文件渲染数据(自行解析返回数据即可){{users}}浏览器访问http://localhost:8080/user_view即可看到页面内容增加单元功能修改pom.xml文件增加以下依赖<!– test –><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>junit</groupId> <artifactId>junit</artifactId> </exclusion> <exclusion> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> </exclusion> </exclusions></dependency><dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <scope>test</scope></dependency>创建UserServiceTest文件进行测试UserService功能package com.edurt.skiimport com.edurt.ski.service.UserServiceimport org.junit.jupiter.api.AfterAllimport org.junit.jupiter.api.Testimport org.springframework.beans.factory.annotation.Autowiredimport org.springframework.boot.test.context.SpringBootTestimport org.springframework.data.domain.PageRequest@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)class UserServiceTest(@Autowired private val userService: UserService) { @Test fun get all() { println(">> Assert blog page title, content and status code”) val entity = this.userService.getAll(PageRequest(0, 1)) print(entity.totalPages) }}源码地址:GitHub ...

February 20, 2019 · 3 min · jiezi

SAP云平台对Kubernetes的支持

截至本文发稿(2019-2-10, 农历大年初六)时为止,访问SAP云平台的官方网站:https://cloudplatform.sap.com…能看到下面的网页:SAP云平台上的Kubernetes环境,Coming Soon(即将推出)Build powerful container-native applications and deploy them on a cloud or infrastructure of your choice without worrying about the creation and management of the underlying container clusters.我来翻译一下:SAP云平台的Kubernetes编程环境能让编程人员开发功能强大的容器原生应用并顺利部署在云上,而不用担心底层容器集群的创建和管理。为什么SAP云平台要选择支持Kubernetes?Containers are rapidly becoming a popular development paradigm for cloud-native applications in the industry – with Kubernetes as the dominant container orchestrations technology. The SAP Cloud Platform Kubernetes environment reduces pain points associated with the creation and management of Kubernetes clusters by offering an easy to use, secure and open standards-based Cluster-as-a-Service running on top of a variety of underlying infrastructure choices, deployment options of cloud vs. on-premise, etc. SAP is making this innovation accessible to the entire developer community via an open-source project called Gardener.SAP给出的答案是:基于Kubernetes的容器编排技术近年来已经成为行业里最流行的云原生应用开发范式之一。SAP云平台的Kubernetes编程环境提供了一种简易,安全,基于业界标准的方式提供了对Kubernetes集群的创建和管理,这种方式基于Cluster-as-a-Service(集群即服务)的方式运作,能运行在多种底层云技术设施上,解决了开发人员需要花大量时间学习Kubernetes底层细节的痛点。这种Cluster-as-a-Service的服务,SAP通过开源项目Gardener提供访问:https://gardener.cloud/更多Gardener的使用方式和截图,请参考我的文章:站在巨人肩膀上的牛顿:Kubernetes和SAP Kymahttps://www.jianshu.com/p/c85…使用SAP云平台的Kubernetes编程环境能享受哪些收益Customers can rely upon the SAP Cloud Platform to create, manage, secure and maintain the container clusters for them.客户可以直接使用SAP云平台创建和管理容器集群Leverage the Gardener open-source project to easily access a variety of community innovations.借助Gardener这个开源项目享受到开源社区的种种创新Use container-native development to build and operate complex, stateful cloud applications that require greater freedom over underlying infrastructure.通常情况下开发复杂的云原生应用和有状态的云应用,需要对底层云的基础设施具有很高的自由度。而SAP云平台通过Gardener提供的Cluster-as-a-Service, 给使用者提供了这种自由度。要获取更多Jerry的原创文章,请关注公众号"汪子熙": ...

February 14, 2019 · 1 min · jiezi

Kotlin/Native尝试

Kotlin/Native尝试在官网看到Kotlin/Native已经达到1.0 Beta版于是就去尝试了一下,结果发现坑还是挺多的。首先Kotlin/JVM很多库是用不了的,这个已经猜到了。官网说已经预先导入了 POSIX、 gzip、 OpenGL、 Metal、 Foundation 等很多其他的库,然后我就尝试了下基本的文件读写。和C还是有一点的差别的。如下:fun hello(): String = “Hello, Kotlin/Native!“fun letter() = “abcdefghigklmnopqrstuvwxyz"fun main(args: Array<String>) { val file = fopen(“data.txt”, “w”) fprintf(file, “%s”, hello()) fprintf(file, “%s”, “\n”) fprintf(file, “%s”, letter()) fclose(file) println(“write finished”) val filer = fopen(“data.txt”, “r”) val buf = ByteArray(255)// fscanf(filer, “%s”, buf.pin().addressOf(0)) fgets(buf.pin().addressOf(0), 255, filer) fclose(filer) print(buf.stringFromUtf8()) buf.pin().unpin() println(“read finished”) system(“pause”)}运行结果如下> Task :runProgramwrite finishedHello, Kotlin/Native!read finishedPress any key to continue . . . C:\BuildAgent\work\4d622a065c544371\runtime\src\main\cpp\Memory.cpp:1150: runtime assert: Memory leaks found> Task :runProgram FAILED令人郁闷的是提示C:\BuildAgent\work\4d622a065c544371\runtime\src\main\cpp\Memory.cpp:1150: runtime assert: Memory leaks found,虽然调用了buf.pin().unpin(),但依旧提示内存泄漏,也没有异常退出啊。如果是改成如下方式就不会提示错误了:fun hello(): String = “Hello, Kotlin/Native!“fun letter() = “abcdefghigklmnopqrstuvwxyz"fun main(args: Array<String>) { val file = fopen(“data.txt”, “w”) fprintf(file, “%s”, hello()) fprintf(file, “%s”, “\n”) fprintf(file, “%s”, letter()) fclose(file) println(“write finished”) val filer = fopen(“data.txt”, “r”) val buf = ByteArray(255)// fscanf(filer, “%s”, buf.pin().addressOf(0))// fgets(buf.pin().addressOf(0), 255, filer)// fclose(filer)// print(buf.stringFromUtf8())// buf.pin().unpin() buf.usePinned { fgets(it.addressOf(0), 255, filer) fclose(filer) print(buf.stringFromUtf8()) } println(“read finished”) system(“pause”)}结果如下:> Task :runProgramwrite finishedHello, Kotlin/Native!read finishedPress any key to continue . . . BUILD SUCCESSFUL in 9s另外吐槽下,这么几行代码就要9s,是不是太慢了。随后又试了下开启pthread线程,但是pthread_create函数的第一个参数th: kotlinx.cinterop.CValuesRef<platform.posix.pthread_tVar>,CValuesRef类型的变量怎么获得一直无解,难道只能通过继承获得?然后我在写文章的时候又发现只要这样写就可以了???fun main(args: Array<String>) { pthread_create(null, null, test(), null)}typealias func = kotlinx.cinterop.CPointer<kotlinx.cinterop.CFunction<(kotlinx.cinterop.COpaquePointer?) -> kotlinx.cinterop.COpaquePointer?>>?fun test(): func { return staticCFunction<kotlinx.cinterop.COpaquePointer?, kotlinx.cinterop.COpaquePointer?> { println(“run test”) it }}结果如下:> Task :runProgramrun testBUILD SUCCESSFUL in 8s> Task :runProgramwrite finishedHello, Kotlin/Native!read finishedPress any key to continue . . . C:\BuildAgent\work\4d622a065c544371\runtime\src\main\cpp\Memory.cpp:1150: runtime assert: Memory leaks found ...

February 5, 2019 · 1 min · jiezi

Google Translate的API调用

Google Translate的API调用由于经常用到谷歌翻译,而每次切换到网页又觉得耗费时间,所以决定自己写一个小工具来用,于是就去查询了一番谷歌翻译的API,但是看到都说是API已经开始收费了,但还是有人通过网页爬出了网页翻译的API但是利用起来比较繁琐。之后又找到了一个简单的API,如下:fun translate(text: String, source: String = “auto”, target: String = “zh-CN”): Pair<String, String> { val textChecked = if (text.isBlank()) “null” else URLEncoder.encode(text, “utf8”) val userAgent = “Mozilla/5.0” val url = “https://translate.googleapis.com/translate_a/single?client=gtx&sl=$source&tl=$target&dt=t&q=$textChecked" val connection = URL(url).openConnection().apply { setRequestProperty(“User-Agent”, userAgent) } val raw = connection.getInputStream().use { it.readBytes() }.toString(Charset.forName(“utf8”)) val p1 = raw.indexOf(”","") val p2 = raw.indexOf("",", p1 + 1) val result = raw.substring(4, p1) val query = raw.substring(p1 + 3, p2) return Pair(result, query)}测试函数如下:fun main() { println(translate(“别让这么应景的天空放晴啊”)) println(translate(“空気を読んだ空晴れないでよ”)) println(translate(“别降下这么看场合的雨啊”)) println(translate(“空気を読んだ雨降らないでよ”)) println(translate(“He sits no sure that sits too high”)) println(translate(“高处不胜寒”, target = “en-US”))}调用结果如下:(别让这么应景的天空放晴啊, 别让这么应景的天空放晴啊)(我看空中的天空请不要晴天, 空気を読んだ空晴れないでよ)(别降下这么看场合的雨啊, 别降下这么看场合的雨啊)(看风雨时不要下雨, 空気を読んだ雨降らないでよ)(他不确定是不是太高了, He sits no sure that sits too high)(High altitude, 高处不胜寒)translate函数参数分别是:需要翻译的字符串,原始语言(默认为auto,即自动检测),目标语言(默认zh-CN,即简体中文)。translate函数返回结果为:翻译后的字符串,需要翻译的字符串(原始字符串)。 ...

February 4, 2019 · 1 min · jiezi

Kotlin上手(一)

Kotlin上手(一)系列笔记为学习极客时间张涛讲解Kotlin的笔记。本篇笔记主要学习了从Java过渡到Kotlin的几个注意点1.最基本语法:1.1Kotlin的变量:Kotlin的文件是以.kt结尾,Kotlin的代码不需要;结尾var表示变量,val表示不可变的变量(不是常量),Kotlin的变量名在前面,变量名写在后面,中间冒号隔开,如果变量类型编译器可以推断出来,那么可以不用写明类型,同时Kotlin是具有空安全的,通过?和!!可以实现这种空安全的转换。例如:val name1:String = null //错误,String类型不为空,不能赋值nullval name1:String? = null //正确:String?类型可以为空(String与String?是两种不同类型)val name2:String = name1 //错误:name2是String类型不可以为空,而name1可能为空,不能赋值val name2:String = name1!! //正确:后面的!!指明开发者已经确保了name1不可能是null,可以赋值(如果运行时出现null,抛出相关的异常)1.2kotlin的函数:fun关键字指明Kotlin的函数变量,后面跟函数名,参数列表同变量写法一样,先写变量名后写变量类型,返回值最后写。fun functionName(str:String,num:Int):String{ if(num==0){ println(str) } return str}2.Kotlin与Java的互相调用:2.1.Java直接调用Kt函数kotlin的函数可以直接写在文件里面,不用写在类里面,但是编译kt文件之后,实际上最终还是编译成public static修饰//Util.ktfun pln(str:String){ println("$str")}//Main.javapublic static void main(args[] String){ UtilKt.echo("$args[0]");}其中$是Kotlin语法的转义符号之一,可以直接在字符串中插入变量名或者一段代码(用{}括起来),如果要在字符串中使用$符号,则可以println("${’$’}name") //output:$name2.2.匿名类对象:匿名内部类主要是针对那些不能创建实例的抽象类和接口而来的,在Kotlin中使用object关键字创建匿名类对象:首先看下Java和Kotlin的匿名类对象写法上的区别://在Java中创建和使用匿名内部类public interface AInterface { void sayMsg(String msg); void doMsg(String msg);}AInterface aInterface = new AInterface() { @Override public void sayMsg(String msg) { System.out.println(“sayMsg”+msg); } @Override public void doMsg(String msg) { System.out.println(“doMsg”+msg); }};aInterface.sayMsg(“B”);aInterface.doMsg(“B”);//在Kotlin中创建和使用匿名内部类interface KtInterface{ fun sayBye(msg:String?) fun doBye(msg:String?)}val bye = object: KtInterface{ override fun doBye(msg: String?) { println(“doBye$msg”) } override fun sayBye(msg: String?) { println(“sayBye$msg”) }}bye.sayBye(" bye")bye.doBye(" Bye")如果要在Java中调用Kotlin中的匿名类对象,则如下:类名加.INSTANCE而Kotlin的单例写法之一,也正是直接用object关键字实现。//在Test.kt文件中object Test{ fun sayMessage(msg:String?){ println(msg) }}//在MainTest.java文件中public static void main(String[] args){ Test.INSTANCE.sayMessage(“Hello”);}如果不通过INSTANCE来能不能调用到sayMessage方法?直接通过Test调用Test.sayMessage(“Hello”)可以通过@JVMStatic注解实现,该注解最终会把对应方法在编译成public static修饰。2.3.传递Class对象:在Java中传递一个Class对象的方法是直接ClassName.class而Kotlin如果要传递一个Java的class对象则是ClassName::class.java因为Kotlin的Class对象与Java的Class对象并不相同,Kt有着自己的Class类型:KClass,如果使用的是KClass那么直接ClassName:kotlin示例代码如下:fun printClass(clazz:Class<MainTest>){ println(“Class Name:"+clazz.simpleName);}fun printClass(clazz:KClass<Test>){ println(clazz.simpleName)}//调用printClass(MainTest::class.java)printClass(Test::class)2.4.关键字上的冲突:如果在Java代码中定义的变量或常量名使用到了Kotlin的关键字,需要借助一对单引号’‘解决这个冲突,如下://关键字冲突public static String object = “object”;//Kotlin中调用println(MainTest.object);3.新手易踩坑3.1.Kotlin没有封装类Kotlin中对于基本数据类型是没有封装类这一说的,这一点不同于Java的int->Interger,float->Float……首先看如下代码://用Java定义了一个接口,两个同名方法,参数类型一个是基本数据类型,另一个是它的封装类public interface NoBoxInterface { void printNum(int num); void printNum(Integer num);}//用Kotlin去实现它:class NoBoxImpl:NoBoxInterface{ override fun printNum(num: Int) {}}上面的代码中,我们只会要也只能实现一个方法,因为Kotlin中也不存在Integer这个类型。扩展一下:碰巧是在搜Kotlin有没有封箱这一说的时候看到的,来看下面这个例子:简单一点说,装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。这是在Java1.5中引入的特性,目的是为了节省内存和提高虚拟机对整型数据的处理能力。eg://自动装箱Integer total = 99;//自定拆箱int totalprim = total;val num1:Int = 127val num2:Int? = num1val num3:Int? = num1fun main(args:Array<String>){ println(num2== num3) println(num2===num3) //kotlin中===比较的是地址,换言之,比较的是它俩是不是同一个对象}//output:true true当num1>127的时候//output:true false可能会有点疑惑为什么举这个例子,跟装箱有什么关系,为什么大于与小于等于127输出的结果不一样?打开Idea的Tool->Kotlin->Show Kotlin ByteCode可以查看对应的字节码文件,然后decomplie可以反编译成java代码Int反编译之后对应到int,Int?反编译之后对应到Integer。在用Integer装箱的时候,我们用到了它的valueOf方法Integer i = Integer.valueOf(8);去看下valueOf就能知道为和如此了:public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i);}//low=-128 high=127正是这里,当整型值在-128到127之间的时候,Integer是不会去创建新对象的,而是从IntegerCache中读取,这个类是Integer的内部静态类,存放了-128到127之间的全部Integer对象。回到最初的例子,正是因为上面这一点,当数值在这个限定范围里面的时候,num3和num2指向的都是同一个Integer对象,当超过这个范围,就会去创建新的Integer对象,使得===的结果为false。3.2.Kotlin类型空值敏感这一点在笔记开头已经讲过了//java代码String format(String str) { return str.isEmpty() ? null : str;}//在kotlin中调用fun function(str: String) { val fmt1 = format(str) val fmt2: String = format(str) val fmt3: String? = format(str)}fmt2这一句会抛出NullPointException,而fmt3不会。3.3.Kotlin没有静态变量与静态方法网上很多博客说直接用object关键字修饰的类,它的方法就是static方法;或者companion object{}修饰的就是静态成员,个人认为这种理解是错误的。来看下面一段代码:class StaticTest { var num0 = 1 fun method(){ println("$num0”) } companion object { //注意:@JvmField var num1 = 1 //注意:@JvmStatic fun staticMethod(){ println("$num1") } }}在上面的代码中,我先注释了两个个注解,然后又恢复它们,通过查看编译好的字节码文件://没加注解private static I num1public final staticMethod()V//加了注解public static I num1public final static staticMethod()V可以看出,对于位于object或者companion object{}修饰的域里面的变量而言 ,kotlin的确把它们看做了static域,但是方法却不一样,只有在加了@JVMStatic注解之后(注意:这个注解只能在有object修饰的域里面才能使用),staticMethod才被编译成了static属性的方法。那么加与不加有什么区别?毕竟你都可以直接通过ClassName.MethodName来调用它,我个人觉得这更像是一种前面说到过的,Kotlin单例的写法。如果要给一个变量或者一个方法附上“Static”属性(打引号是指明在Kotlin中没有Static这一说,只是能够达到与Java的Static关键字相同的效果),可以通过以下方式来实现:下面我援引Kotlin doc来说明如何在Kotlin中达到Static效果3.3.1.Static字段@JvmField 注解;lateinit 修饰;const 修饰.@JVMFieldclass Key(val value: Int) { companion object { @JvmField val COMPARATOR: Comparator<Key> = compareBy<Key> { it.value } }}// JavaKey.COMPARATOR.compare(key1, key2);// public static final field in Key classlateinitobject Singleton { lateinit var provider: Provider}// JavaSingleton.provider = new Provider();// public static non-final field in Singleton classconst// file example.ktobject Obj { const val CONST = 1}class C { companion object { const val VERSION = 9 }}const val MAX = 239int c = Obj.CONST;int d = ExampleKt.MAX;int v = C.VERSION;3.3.2.Static方法@JVMStatic 注解class C { companion object { @JvmStatic fun foo() {} fun bar() {} }}//在Java中调用C.foo(); // works fineC.bar(); // error: not a static methodC.Companion.foo(); // instance method remainsC.Companion.bar(); // the only way it works注意Companion,如果是直接object修饰类,则是INSTANCE,如下:object Obj { @JvmStatic fun foo() {} fun bar() {}}Obj.foo(); // works fineObj.bar(); // errorObj.INSTANCE.bar(); // works, a call through the singleton instanceObj.INSTANCE.foo(); // works too ...

January 24, 2019 · 2 min · jiezi

异构列表(DslAdapter开发日志)

异构列表(DslAdapter开发日志)函数范式, 或者说Haskell的终极追求是尽量将错误"扼杀"在编译期, 使用了大量的手法和技术: 使用大量不可变扼杀异步的不可预计, 以及静态类型和高阶类型 说到静态类型大家应该都不会陌生, 它是程序正确性的强大保证, 这也是本人为什么一直不太喜欢Python, js等动态类型语言的原因静态类型: 编译时即知道每一个变量的类型,因此,若存在类型错误编译是无法通过的。 动态类型: 编译时不知道每一个变量的类型,因此,若存在类型错误会在运行时发生错误。类型检查, 即在编译期通过对类型进行检查的方式过滤程序的错误, 这是我们在使用Java和Kotlin等语言时常用的技术, 但这种技术是有限的, 它并不能通用于所有情况, 因此我们常常反而会回到动态类型, 采用动态类型的方式处理某些问题 本文聚焦于常见的列表容器在某些情况下如何用静态类型的手法进行开发进行讨论编译期错误检查对于函数(方法)的输入错误有两种方式:编译期检查, 比如List<String>中不能保存Integer类型的数据运行期检查, 比如对于列表的下标是否正确, 我们可以在运行的时候检查运行期检查是必须要运行到相应的代码时才会进行相应的检查(无论是实际程序还是测试代码), 这是不安全并且效率低下的, 所以能在编译期检查的问题都尽量在编译期排除掉 编译期的检查中除了语法问题之外最重要的就是类型检查, 但这要求我们提供足够的类型信息DslAdapter实现中遇到的问题DslAdapter是个人开发的一个针对Android RecyclerView的一个扩展库, 专注于静态类型和Dsl的手法, 希望创造一个基于组合子的灵活易用同时又非常安全的Adapter 在早期版本中已经实现了通过Dsl进行混合Adapter的创建:val adapter = RendererAdapter.multipleBuild() .add(layout<Unit>(R.layout.list_header)) .add(none<List<Option<ItemModel>>>(), optionRenderer( noneItemRenderer = LayoutRenderer.dataBindingItem<Unit, ItemLayoutBinding>( count = 5, layout = R.layout.item_layout, bindBinding = { ItemLayoutBinding.bind(it) }, binder = { bind, item, _ -> bind.content = “this is empty item” }, recycleFun = { it.model = null; it.content = null; it.click = null }), itemRenderer = LayoutRenderer.dataBindingItem<Option<ItemModel>, ItemLayoutBinding>( count = 5, layout = R.layout.item_layout, bindBinding = { ItemLayoutBinding.bind(it) }, binder = { bind, item, _ -> bind.content = “this is some item” }, recycleFun = { it.model = null; it.content = null; it.click = null }) .forList() )) .add(provideData(index).let { HListK.singleId(it).putF(it) }, ComposeRenderer.startBuild .add(LayoutRenderer<ItemModel>(layout = R.layout.simple_item, stableIdForItem = { item, index -> item.id }, binder = { view, itemModel, index -> view.findViewById<TextView>(R.id.simple_text_view).text = itemModel.title }, recycleFun = { view -> view.findViewById<TextView>(R.id.simple_text_view).text = "" }) .forList({ i, index -> index })) .add(databindingOf<ItemModel>(R.layout.item_layout) .onRecycle(CLEAR_ALL) .itemId(BR.model) .itemId(BR.content, { m -> m.content + “xxxx” }) .stableIdForItem { it.id } .forList()) .build()) .add(DateFormat.getInstance().format(Date()), databindingOf<String>(R.layout.list_footer) .itemId(BR.text) .forItem()) .build()以上代码实现了一个混合Adapter的创建:|–LayoutRenderer header||–SealedItemRenderer| |–none -> LayoutRenderer placeholder count 5| | | |–some -> ListRenderer| |–DataBindingRenderer 1| |–DataBindingRenderer 2| |–… ||–ComposeRenderer| |–ListRenderer| | |–LayoutRenderer simple item1| | |–LayoutRenderer simple item2| | |–…| || |–ListRenderer| |–DataBindingRenderer item with content1| |–DataBindingRenderer item with content2| |–…||–DataBindingRenderer footer即: Build Dsl –> Adapter, 最后生成了一个混合的val adapter而在使用的时候希望能通过这个val adapter对结构中某些部分进行部分更新 比如上面构造的结构中, 我们希望只在ComposeRenderer中第二个ListRendererinsert 一个元素进去, 并合理调用Adapter的notifyItemRangeInserted(position, count)方法, 并且希望这个操作可以通过Dsl的方式实现, 比如:adapter.updateNow { // 定位ComposeRenderer getLast2().up { // 定位第二个ListRenderer getLast1().up { insert(2, listOf(ItemModel(189, “Subs Title1”, “subs Content1”))) } }}以上Dsl必然是希望有一定的限定的, 比如不能在只有两个元素的Adapter中getLast3(), 也不能在非列表中执行insert() 而这些限制需要被从val adapter推出, 即adapter –> Update Dsl, 这意味着adapter中需要保存其结构的所有信息, 由于我们需要在编译期对结构信息进行提取, 也意味着应该在类型信息中保存所有的结构信息 对于通常的Renderer没有太大的问题, 但对于部分组合其他Renderer的Renderer, (比如ComposeRenderer, 它的作用是按顺序将任意的Renderer组合在一起), 通常的实现方式是将他们统统还原为共通父类(BaseRenderer), 然后看做同样的东西进行操作, 但这个还原操作也同时将各自独特的类型信息给丢失了, 那应该怎么办才能即保证组合的多样性, 同时又不会丢失各自的类型信息?换一种方式描述问题推广到其他领域, 这个问题实际挺常见的, 比如:我们现在有一个用于绘制的基类RenderableBase, 而有两个实现, 一个是绘制圆形的Circle和绘制矩形的Rectangle:graph TBA[RenderableBase]A1[Circle]A2[Rectangle]A –> A1A –> A2我们有一个共通的用于绘制的类Canvas, 保存有所有需要绘制的RenderableBase, 一般情况下我们会通过一个List<RenderableBase>容器的方式保存它们, 将它们还原为通用的父类 但这种方式的问题是这种容器的类型信息中已经丢失了每个元素各自的特征信息, 我们没法在编译期知道或者限定子元素的类型(比如我们并不知道其中有多少个Circle, 也不能限定第一个元素必须为Rectangle) 那是否有办法即保证容器的多样性, 同时又不会丢失各自的类型信息?再换一种方式描述问题对于一个函数(方法), 比如:fun test(s: String): List<String>它其实可以看做声明了两个部分的函数:值函数: 描述了元素s到列表list的态射类型函数: 描述了从类型String到类型List<String>的态射即包括s -> list和String -> List<String> 一般而言这两者是同步的, 或者说类型信息中包括了足够的值相关的信息(值的类型), 但请注意以下函数:fun test2(s: String, i: Int): List<Any?> = listOf(s, i)它声明了(s, i) -> list和(String, Int) -> List<Any?>, 它没有将足够的类型信息保存下来:List中只包括String和Int两种元素List的Size为2List中第一个元素是String, 第二个元素是Int那是否有办法将以上这些信息也合理的保存到容器的类型中呢?一种解决方案异构列表以上的问题注意原因是在于List容器本身, 它本身就是一个保存相同元素的容器, 而我们需要是一个可以保存不同元素的容器 Haskell中有一种这种类型的容器: Heterogeneous List(异构列表), 就实现上来说很简单:Tip: arrow中的实现sealed class HListdata class HCons<out H, out T : HList>(val head: H, val tail: T) : HList()object HNil : HList()我们来看看使用它来构造上一节我们所说的函数应该如何构造:// 原函数fun test2(s: String, i: Int): List<Any?> = listOf(s, i)// 异构列表fun test2(s: String, i: Int): HCons<Int, HCons<String, HNil>> = HCons(i, HCons(s, HNil))同样是构建列表, 异构列表包含了更丰富的类型信息:容器的size为2容器中第一个元素为String, 第二个为Int相比传统列表异构列表的优势完整保存所有元素的类型信息自带容器的size信息完整保存每个元素的位置信息比如, 我们可以限定只能传入一个保存两个元素的列表, 其中第一个元素是String, 第二个是Int:fun test(l: HCons<Int, HCons<String, HNil>>)同时我们也可以确定第几个元素是什么类型:val l: HCons<Int, HCons<String, HNil>> = …l.get0() // 此元素一定是Int类型的由于Size信息被固定了, 传统必须在运行期才能检查的下标是否越界的问题也可以在编译期被检查出来:val l: HCons<Int, HCons<String, HNil>> = …l.get3() // 编译错误, 因为只有两个元素 相比传统列表的难点由于Size信息和元素类型信息是绑定的, 抛弃Size信息的同时就会抛弃元素类型的限制注意类型信息中的元素信息和实际保存的元素顺序是相反的, 因为异构列表是一个FILO(先进后出)的列表由于Size信息是限定的, 针对不同Size的列表的处理需要分开编写对于第一点, 以上面的RenderableBase为例, 比如我们有一个函数可以处理任意Size的异构列表:fun <L : HList> test(l: L)我们反而无法限定每个元素都应该是继承自RenderableBase的, 这意味着HCons<Int, HCons<String, HNil>>这种列表也可以传进来, 这在某些情况下是很麻烦的异构列表中附加高阶类型的处理Tip: 关于高阶类型的内容可以参考这篇文章高阶类型带来了什么继承是OOP的一大难点, 它的缺点在程序抽象度越来越高的过程的越来越凸显. 函数范式中是以组合代替继承, 使得程序有着更强的灵活性由于采用函数范式, 我们不再讨论异构列表如何限定父类, 而是改为讨论异构列表如何限定高阶类型对HList稍作修改即可附加高阶类型的支持:Tip: DslAdapter中的详细实现: HListKsealed class HListK<F, A: HListK<F, A>>class HNilK<F> : HListK<F, HNilK<F>>()data class HConsK<F, E, L: HListK<F, L>>(val head: Kind<F, E>, val tail: L) : HListK<F, HConsK<F, E, L>>()以Option(可选类型)为例:arrow中的详细实现: Optionsealed class Option<out A> : arrow.Kind<ForOption, A>object None : Option<Nothing>()data class Some<out T>(val t: T) : Option<T>()通过修改后的HListK我们可以限定每个元素都是Option, 但并不限定Option内容的类型:// [Option<Int>, Option<String>]val l: HConsK<ForOption, String, HConsK<ForOption, Int, HNilK<ForOption>>> = HConsK(Some(“string”), HConsK(199, HNilK()))修改后的列表即可做到即保留每个元素的类型信息又可以对元素类型进行部分限定它即等价于原生的HList, 同时又有更丰富的功能比如:// 1. 定义一个单位类型data class Id<T>(val a: T) : arrow.Kind<ForId, A>// 类型HListK<ForId, L>即等同于原始的HListfun <L : HListK<ForId, L>> test()// 2. 定义一个特殊类型data class FakeType<T, K : T>(val a: K) : arrow.Kind2<ForFakeType, T, K>// 即可限定列表中每个元素必须继承自RenderableBasefun <L : HListK<Kind<ForFakeType, RenderableBase>, L>> test(l: L) = …fun test2() { val t = FakeType<RenderableBase, Circle>(Circle()) val l = HListK.single(t) test(l)}回到DslAdapter的实现上文中提到的异构列表已经足够我们用来解决文章开头的DslAdapter实现问题了 异构问题解决起来就非常顺理成章了, 以ComposeRenderer为例, 我们使用将子Renderer装入ComposeItem容器的方式限定传入的容器每个元素必须是BaseRenderer的实现, 同时ComposeItem通过泛型的方式尽最大可能保留Renderer的类型信息:data class ComposeItem<T, VD : ViewData<T>, UP : Updatable<T, VD>, BR : BaseRenderer<T, VD, UP>>( val renderer: BR) : Kind<ForComposeItem, Pair<T, BR>>其中可以注意到类型声明中的Kind<ForComposeItem, Pair<T, BR>>, arrow默认的三元高阶类型为Kind<Kind<ForComposeItem, T>, BR>, 这并不符合我们在这里对高阶类型的期望: 我们这里只想限制ForComposeItem, 而T我们希望和BR绑定在一起限定, 所以使用了积类型 Pair将T和BR两个类型绑定到了一起. 换句话说, Pair在这里只起到一个组合类型T和BR的类型粘合剂的作用, 实际并不会被使用到 ComposeItem保存的是在build之后不会改变的数据(比如Renderer), 而使用中会改变的数据以ViewData的形式保存在ComposeItemData:data class ComposeItemData<T, VD : ViewData<T>, UP : Updatable<T, VD>, BR : BaseRenderer<T, VD, UP>>( val viewData: VD, val item: ComposeItem<T, VD, UP, BR>) : Kind<ForComposeItemData, Pair<T, BR>>这里同样使用了Pair作为类型粘结剂的技巧 对于一个ComposeRenderer而言应该保存以下信息:可以渲染的数据类型子Renderer的所有类型信息当前Renderer的ViewData信息以及子Renderer的ViewData信息其中2. 子Renderer的所有类型信息由IL : HListK<ForComposeItem, IL>泛型信息保存3. 当前Renderer的ViewData信息以及子Renderer的ViewData信息由VDL : HListK<ForComposeItemData, VDL>泛型信息保存而1. 可以渲染的数据类型由DL : HListK<ForIdT, DL>(ForIdT等同于上文提到的单位类型Id)于是我们可以得到ComposeRenderer的类型声明:class ComposeRenderer<DL : HListK<ForIdT, DL>, IL : HListK<ForComposeItem, IL>, VDL : HListK<ForComposeItemData, VDL>>子Renderer的所有类型信息(Size, 下标等等)被完整保留, 也就意味着从类型信息我们可以还原出每个子Renderer的完整类型信息一个栗子:构造两个子Renderer:// LayoutRendererval stringRenderer = LayoutRenderer<String>(layout = R.layout.simple_item, count = 3, binder = { view, title, index -> view.findViewById<TextView>(R.id.simple_text_view).text = title + index }, recycleFun = { view -> view.findViewById<TextView>(R.id.simple_text_view).text = "" }) // DataBindingRendererval itemRenderer = databindingOf<ItemModel>(R.layout.item_layout) .onRecycle(CLEAR_ALL) .itemId(BR.model) .itemId(BR.content, { m -> m.content + “xxxx” }) .stableIdForItem { it.id } .forItem()使用ComposeRenderer组合两个Renderer:val composeRenderer = ComposeRenderer.startBuild .add(itemRenderer) .add(stringRenderer) .build()你可以猜出这里composeRenderer的类型是什么吗?答案是:ComposeRenderer< HConsK<ForIdT, String, HConsK<ForIdT, ItemModel, HNilK<ForIdT>>>, HConsK<ForComposeItem, Pair<String, LayoutRenderer<String>>, HConsK<ForComposeItem, Pair<ItemModel, DataBindingRenderer<ItemModel, ItemModel>>, HNilK<ForComposeItem>>>, HConsK<ForComposeItemData, Pair<String, LayoutRenderer<String>>, HConsK<ForComposeItemData, Pair<ItemModel, DataBindingRenderer<ItemModel, ItemModel>>, HNilK<ForComposeItemData>>>>其中完整保留了所有我们需要的类型信息, 因此我们可以通过composeRenderer还原出原来的数据结构:composeRenderer.updater .updateBy { getLast1().up { update(“New String”) } }这里的update(“New String”)方法知道当前定位的是一个stringRenderer, 所以可以使用String更新数据, 如果传入ItemModel就会出错虽然泛型信息非常多而长, 但实际大部分可以通过编译系统自动推测出来, 而对于某些无法被推测的部分也可以通过一些小技巧来简化, 你可以猜到用了什么技巧吗?结语以前我们常常更聚焦于面向过程编程, 但对函数范式或者说Haskell的学习, 类型编程其实也是一个很有趣并且很有用的思考方向 没错, 类型是有相应的计算规则的, 甚至有的编程语言会将类型作为一等对象, 可以进行相互计算(积类型, 和类型, 类型的幂等) 虽然Java或者Kotlin的类型系统并没有如此的强大, 但只要改变一下思想, 通过一些技巧还是可以实现很多像魔法一样的事情(比如另一篇文章中对高阶类型的实现)将Haskell的对类型系统编程应用到Kotlin上有很多有趣的技巧, DslAdapter只是在实用领域上一点小小的探索, 而fpinkotlin则是在实验领域的另外一些探索成果(尤其是第四部分 15.流式处理与增量I/O), 希望之后能有机会分享更多的一些技巧和经验, 也欢迎感兴趣的朋友一同探讨 ...

December 24, 2018 · 4 min · jiezi