优雅的使用Kotlin

优雅地封装和使用 ViewBinding,该替代 Kotlin synthetic 和 ButterKnife 了 答应大伙的备战金三银四,大厂面试真题来啦!这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。《960全网最全Android开发笔记》《379页Android开发面试宝典》包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。 阅读详情

优雅的使用Kotlin

这篇文章并不会从零开始来教你怎么使用Kotlin,Kotlin对于笔者来说也是一个新概念,由于大势所趋(Google所有的官方Demo都开始用Kotlin,各大第三方Library也开始适配Kotlin),所以不会Koltin寸步难行呀~,经过一段时间的学习和练习,对Kotlin有了一点浅薄的理解,在此记录下来,希望能给大家带来帮助。

开始进入主题,我打算从下面几个方面来给大家总结一些使用技巧和个人的理解(不足之处还请大家多多指出)

  • 类(类、接口、数据类)
  • 函数(扩展函数)
  • DSL、扩展库和Anko
  • 协程

类

如何创建一个Kotlin类

我们先看看我们熟悉的Java如何创建一个普通的类:

class Player{
    private String name;
    public Player(String name){
        this.name = name;
    }
    
    public void play(){
        system.out.println(name+" start play");
    }
}

下面是Kotlin声明的类

class Player(val name :String){
    fun play(){
        println("$name start paly")
    }
}

看上去好像差别不是很大嘛,其实对于学过Java的人来说,Kotlin还是比较相近的,只需要了解一些新特性,上手还是很快的。

在上面的例子中,Kotlin的构造函数直接声明在类的后面,并且变量的声明也可以在函数的参数中声明,最后字符串的输出,直接使用${变量名}的形式输出

将上面的Kotlin代码反编译成Java代码如下:

public final class Player {
   @NotNull
   private final String name;

   public final void play() {
      String var1 = this.name + " start paly";
      System.out.println(var1);
   }

   @NotNull
   public final String getName() {
      return this.name;
   }

   public Player(@NotNull String name) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
      this.name = name;
   }
}

基本上一样,接下来我们就开始看看Kotlin怎样常见一个类,和我们Java的类有什么具体的区别。

构造函数

相信大家对Java的构造函数已经很熟悉了:

  • 多个构造函数,构造函数之间是重载的
  • 可以不声明构造函数,编译器会自动生成一个无参的构造函数

Kotlin中,一个类中可以有一个主构造函数和多个次构造函数。主构造函数就是声明在类头的构造函数,次构造函数必须委托主构造函数,文字太枯燥,举几个例子就一目了然了。

//这是没有省略所有关键字的类声明,当然如果没有可见性的修饰符(默认是public),可以省略看见性修饰符和constructor
class Player public constructor(val name :String){
    
    //如果想在构造函数中定一些变量或者创建对象,可以直接在类体中定义,也可以在init{...}代码块中去定义
    val id = "111" 
    
    init{
        println("$id 是在初始化的代码块中")
    }
   
    fun play(){
        println("$name start paly")
    }
    
    // 调用player("222").play()输出下面字符串:
    // 111 是在初始化的代码块中
    // 222 start paly
}


// 主构造函数和次构造函数并存
//初始化块中的代码实际上会成为主构造函数的一部分。委托给主构造函数会作为次构造函数的第一条语句,因此所有初始化块中的代码都会在次构造函数体之前执行。即使该类没有主构造函数,这种委托仍会隐式发生,并且仍会执行初始化块
class Player(val name: String) {
    val id = "111"

    init {
        println("$id 是在初始化的代码块中")
    }

    constructor(gender: String, name: String) : this("123") {
        println("我是 $gender 性")
    }

    fun play() {
        println("$name start paly")
    }

}


Kotlin构造函数小结:

  1. 可以有一个主构造函数和多个次构造函数
  2. 可以只有主构造函数和次构造函数
  3. 主、次构造函数同事存在的时候,次构造函数必须直接或者间接的委托到住构造函数
  4. 没有声明主构构造函数,会自动生成一个无参数的主构造函数,这点与java一样。
  5. 在主构造函数中你可以设置默认的值。

数据类

个人觉得Kotlin的数据类使用起来真的很方便,省去我们很多工作量(妈妈从此再也不担心我写数据类了~~)。

先用Java写一个数据类,方便我们后面做对比

public class User {
    private String id;
    private String name;
    private int gender;
    private String avatar;
    private int age;

    public User(String id) {
        this.id = id;
    }
    

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getGender() {
        return gender;
    }

    public void setGender(int gender) {
        this.gender = gender;
    }

    public String getAvatar() {
        return avatar;
    }

    public void setAvatar(String avatar) {
        this.avatar = avatar;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

可以看到声明一个数据类将近需要60多行的代码,还是在没有重写toString(),equals(),hashCode()等方法的前提下。

我们再来看看Kotlin如果实现一个对象类

data class User(val id: String, var name: String = "", var gender: Int = 0, var avatar: String = "", var age: Int = 0)

Kotlin只需要一行代码就搞定,是不是很优雅,优雅的背后是编译器为我们做了许多的事情,编译器会自动的从主构造函数中根据所有声明的属性生成以下的函数

  • equals()/hasCode()
  • toString()
  • copy()函数

实用小窍门:
当我们使用定义好的类时,如果有些属性是不必要的,可以设置默认值,那么我们就可以按照以下的方式调用(拿上面的User做例子):

//Test.kt

class Test{
    
    fun main(args: Array<String>) {
        //姿势一,只赋值id属性
        User("111")
        //姿势二,打乱顺序,再也不用担心在参数居多的情况下,搞不清参数的含义了
         User(name = "liyachao", id = "222")
    }
}

在日常的工作中基本上熟悉上面两个知识点,就能很好的工作了,关于类还有

  • 枚举类 (和Java使用基本一样)
  • 密闭类 (sealed class)枚举类的扩展,每个枚举常量只存在一个实例,而密闭类的一个子类可以有多个实例,暂时还没有用到。
  • 嵌套类,与Java的静态内部类一样
  • 内部类,和Java一样,内部类持有一个外部类的对象引用
  • 匿名内部类。

函数

Kotlin在函数使用上增加很多的语法糖,我们可以方便、愉快的编写代码,也使得代码看起来非常简洁易懂,总是就是更加优雅

扩展函数

Kotlin允许在不改变已有类的情况下,为某个类添加新的函数,这个特性叫做扩展函数。有了这个特性,那么我们岂不是可以给任意的类添加我们想要的函数,再也不用添加XXXUtils或者XXXTool的类了,Kotlin使用了什么黑科技吗?让我们看看

假如我们要给Context添加一个toast的功能,可以这样写

fun Context.toast(str: String) = Toast.makeText(this, str, Toast.LENGTH_LONG).show()

// 当我们需要toast的时候,我们可以这样调用

context.toast("test")

是不是很神奇,我们通过Android Studio反编译看下.java文件是怎样的

//声明 toast方法的类
public final class TestKt {
   public static final void toast(@NotNull Context $receiver, @NotNull String str) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      Intrinsics.checkParameterIsNotNull(str, "str");
      Toast.makeText($receiver, (CharSequence)str, 1).show();
   }
}
// 调用toast的地方
TestKt.toast(context, "test");

从上面代码我们不难明白,其实Kotlin的扩展函数也是给我们生成一个工具类,在掉用的地方帮我们换成这个类的调用,原来如此的简单。

说到扩展函数,就不得不提的几个基础库的扩展函数,也叫做作用域函数,它们是:T.run、T.let、T.also、T.apply 以及run、width两个函数。对于这几个函数的使用刚开始的时候我还是很懵逼的,只是大概了解用法,以及带来的链式调用的便捷,但是它们怎么什么情况下使用,我还真是搞不明白,通常情况下都是想起拿个用哪个。但是这篇文章很好的解释了各个函数的用法和情况。

run

这是最简单的作用域,看下源代码,Kotlin是怎样定义这个方法的:

/**
 * Calls the specified function [block] and returns its result.
 * 其实就是提供了一个作用域的方法,返回函数block的结果
 */
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

//举个例子,可以这样使用

fun test(){
    var mood = "I am sad"
    
    run {
        val mod = "I am happy"
        Toast.makeText($receiver, (CharSequence)str, 1)
    }.show()
}

在这个例子中看起来好像并没有什么卵用,但是其实还是有一些好处的(虽然我在实际开发中没有用到过)

  • 分隔开的作用域。
  • 可以根据作用域返回的结果进行接下来的操作

普通函数和扩展函数(with VS T.run)

还是先看下这两个函数的源码

/**
 * Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

/**
 * Calls the specified function [block] with `this` value as its receiver and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

从源码上看基本上是没有区别的,都是接受一个函数作为参数,然后返回这个函数的结果。它们之间唯一不同的在于with是一个普通函数,而T.run是一个扩展函数,那么问题来了,它们各自使用的优点是什么呢?举个例子

// 对于user的处理

fun test(){
    with(user){
        id = "222"
        name = "test"
    }
    
    user.run{
        id = "222"
        name = "test"
    }
    
    // 好像并没有什么区别,想想一下,如果user可能为null呢,该如何去实现
    
    with(user){
        this?.id = "222"
        this?.name = "test"
    }
    
    user?.run{
        id = "222"
        name = "test"
    }
}

第二种场景下,显然T.run扩展函数更好,因为我们可以在使用它之前对可空性进行检查。

this和it区别(T.run VS T.let)

如果我们对比T.run和T.let两个函数也是非常相似的,上源码

/**
 * Calls the specified function [block] with `this` value as its argument and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

/**
 * Calls the specified function [block] with `this` value as its receiver and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

从源码中可以看出来,它们唯一的区别是接受的参数不一样。

  • T.run仅仅是被当做了block:T.()扩展函数的调用块。因此在其作用域内,T可以被this指代。在编码过程中,大多数情况下this是可以被省略的。
  • T.let将自己本身传递到函数block:(T)。因此这个类似于传递一个lambda表达式作为参数。它可以在函数作用域内部是用哪个it来指代,所以把这个称为传递it参数。

举个例子

user?.run{
    println("my name is $name)
}

user?.let{
    println("my name is $it.name")
}

从上面的例子来看,似乎T.run比T.let更加优越,但是T.let有一些微妙的优势(其实我觉得没什么卵用,都差不多,就看你喜欢用哪个)

  1. T.let函数提供了一种更清晰的区分方式去使用给定的变量函数/成员与外部类函数/成员。
  2. 例如当this作为函数的参数传递时,this不能被省略,并且it写起来比this更简洁,更清晰。
  3. T.let允许更好地命名已转换的已使用变量,即可以将it转换为其他有含义名称,而 T.run则不能,内部只能用this指代或者省略。

return this和return 函数结果(T.let VS T.also)

照例,我们先看下它们的源码

/**
 * Calls the specified function [block] with `this` value as its argument and returns `this` value.
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

/**
 * Calls the specified function [block] with `this` value as its argument and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

从上面源码可以看出来,它们唯一的不同就是T.let返回一个不同类型的值也就是函数执行的结果,而T.alse返回T类型本身

举个例子

val original = "abc"
// Evolve the value and send to the next chain
original.let {
    println("The original String is $it") // "abc"
    it.reversed() // evolve it as parameter to send to next let
}.let {
    println("The reverse String is $it") // "cba"
    it.length  // can be evolve to other type
}.let {
    println("The length of the String is $it") // 3
}
// Wrong
// Same value is sent in the chain (printed answer is wrong)
original.also {
    println("The original String is $it") // "abc"
    it.reversed() // even if we evolve it, it is useless
}.also {
    println("The reverse String is ${it}") // "abc"
    it.length  // even if we evolve it, it is useless
}.also {
    println("The length of the String is ${it}") // "abc"
}
// Corrected for also (i.e. manipulate as original string
// Same value is sent in the chain 
original.also {
    println("The original String is $it") // "abc"
}.also {
    println("The reverse String is ${it.reversed()}") // "cba"
}.also {
    println("The length of the String is ${it.length}") // 3
}

T.also似乎看上去没有意义,因为我们可以很轻易的将它们组合成为一个模块。仔细想想,T.also还是有一些好处的

  1. 它可以对一个对象提供非常清晰的区分过程,将其区分成更小的功能区域
  2. 在使用之前,可以进行强大的自我操作,创建一个链式调用的操作。

当把这个链式调用连接起来,就非常牛逼了。

// Normal approach
fun makeDir(path: String): File  {
    val result = File(path)
    result.mkdirs()
    return result
}
// Improved approach
fun makeDir(path: String) = path.let{ File(it) }.also{ it.mkdirs() }

一行代码搞定,是不是很优雅

回顾所有的属性值

通过回顾这三个属性特征,我们可以非常清楚函数的行为。说明一下T.apply函数:

  1. 扩展函数
  2. 传递this作为参数
  3. 返回this(自己本身)

举个例子

// Normal approach
fun createInstance(args: Bundle) : MyFragment {
    val fragment = MyFragment()
    fragment.arguments = args
    return fragment
}
// Improved approach
fun createInstance(args: Bundle) 
              = MyFragment().apply { arguments = args }

或者我们也可以让无链对象创建链式调用

// Normal approach
fun createIntent(intentData: String, intentAction: String): Intent {
    val intent = Intent()
    intent.action = intentAction
    intent.data=Uri.parse(intentData)
    return intent
}
// Improved approach, chaining
fun createIntent(intentData: String, intentAction: String) =
        Intent().apply { action = intentAction }
                .apply { data = Uri.parse(intentData) }

说了这么多我们该怎么选用呢,通过一张图来说明

大概分析了一遍基础库中的最常用的扩展函数,熟练使用这些函数,能够让我们的代码看上去更加简练和易懂,最主要的还是要多用。

尾随闭包

大家可能对这个概念有点陌生,甚至没有听过这个词(我第一次听,也是从火火老师那边听的),其实很简单,上面我举了很多例子都是用到了尾随闭包,例如

fun test(){
    User("111").let{//这里就是一个尾随闭包
        
    }
}

尾随闭包是kotlin的一个语法,当函数的最后一个参数为函数的时候(或者接口,接口的话只能有一个函数),可以直接省略括号,直接函数名后跟着大括号即可,举例说明

fun test(){
    view.setOnClickListener{//因为onClick(v:View),只有一个参数,这里就直接省略了,可以直接用it来代替
        
    }
    //如果函数有多个参数
    view.setOnTouchListener{
        v,event -> //可以这样来写
    }
    
    //如果不用尾随闭包,那么写法基本和Java一样
    view..setOnClickListener(object :View.OnClickListener{
            override fun onClick(v: View?) {
                
            }
        })
}

是不是这样写,优雅了很多,也更加易读了。

inline(内联)、infix(中缀)、高阶函数

inline

Kotlin天生支持函数式编程,高阶函数和lambda是其一大特色.
使用高阶函数会带来一些运行时间效率的损失:每个函数都是一个对象,别切都会捕获一个闭包。即在那些函数体内会被访问的变量。内部分配(对于函数对象和类)和虚拟调用会引入运行时间开销。

使用inline修饰的函数,可以从编译器角度将函数的函数体复制到调用出实现内联

infix

中缀表达式是一种通用的算术或者逻辑公式表示方法,操作符以中缀形式处于操作数中间。中缀表达式允许我们使用一个单词或者字母来当做运算符用(实质上还是函数调用),忽略调用的点和圆括号

Kotlin的中缀表达式,需要满足以下条件

  1. 使用infix修饰
  2. 只有一个参数
  3. 参数不得接受可变数量的参数且不能有默认值
infix fun Int.add(i:Int):Int = this + i

infix fun Int.加(i:Int):Int = this + i

fun main(args: Array<String>) {

    println(5 add 10)
    println(5 加 10)
}

由此可见,中缀表达式能让代码看起来更加接近自然语言。

DSL

DSL(domain specific language),即领域专用语言:专门解决某一特定问题的计算机语言,比如大家耳熟能详的SQL和正则表达式

那么Kotlin DSL 是什么呢?使用Kotlin语言呢开发的,解决特定领域问题,具备独特代码结构的API

Kitlin DSL的具体表现都有哪些呢?上面提到过的:扩展函数、中缀函数、lambda以及要讲的Anko Layout

DSL-扩展函数

想想一下这样的场景:我们需要给每一个点击事件添加埋点、需要判断是否连击(一般的按钮是不允许连击的),点击的时候需要进行动画。这种事件很像面向切面编程(AOP),在没有接触Kotlin的时候,大部分解决方案是用AspectJ或者Lancet来在编译时织入代码,进行hook函数。在Kotlin中我们使用扩展函数就可以实现这样的功能,我做了一个例子:

object TriggerTime {
    var triggerLastTime: Long = 0
    const val DELAY_TIME = 300
}

inline fun <T : View> T.clickWithAnimation(onClickListener: View.OnClickListener) = setOnClickListener {
    if (clickEnable) {
        it.animate().scaleX(0.8f).scaleY(0.8f).setDuration(80).withEndAction {
            it.animate().scaleX(1f).scaleY(1f).setDuration(80).start()
        }.start()
        onClickListener.onClick(it)
    }
}

inline fun <T : View> T.clickWithEvent(hashMap: HashMap<String, String>, noinline block: (T) -> Unit) {
    // hashmap 进行打点
    setOnClickListener {
        if (clickEnable)
            block.invoke(it as T)
    }
}

var <T : View> T.clickEnable: Boolean
    get() {
        var flag = false
        val currentClickTime = System.currentTimeMillis()
        if (currentClickTime - TriggerTime.triggerLastTime >= DELAY_TIME) {
            flag = true
        }
        TriggerTime.triggerLastTime = currentClickTime
        return flag
    }
    set(value) {

    }
    
//使用方法

fun test(view:View){
    view.clickWithEvent(hashMapOf("a" to "b")){
        //点击事件
    }
    
    view.clickWithAnimation{
        //点击事件
    }
}

上面的代码很简单,这里就不多做说明了,可以很方便的实现这一类的点击事件。

告别findViewById

使用Kotlin Android Extensions就可以访问布局XML的View,就像它们在布局中定义的属性一样,可以使用id的名称。Kotlin Android Extensions是Kotlin的一个插件。

将Kotlin Android Extensions集成到module中
只需要在module中的build.gradle上部添加apply plugin: 'kotlin-android-extensions'即可,十分方便,接下来举个例子,就知道怎样使用了

// 先定义一个普通的布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <View
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/test"/>

</androidx.constraintlayout.widget.ConstraintLayout>
// 下面是activity
// 这句很重要,只有添加这句,才能直接使用id
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : FragmentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 直接使用xml 中的id 就可以直接使用view,很神奇吧
        test.visibility = View.VISIBLE
    }
}

是不是很优雅,再也不用写一大堆变量,然后findViewById,就可以直接使用,不仅要知其然,还要知其所以然。让我们看下反编译后的java代码

public final class MainActivity extends FragmentActivity {
   private HashMap _$_findViewCache;

   protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      this.setContentView(2131296284);
      this.getWindow().setSoftInputMode(48);
      View var10000 = this._$_findCachedViewById(id.test);
      Intrinsics.checkExpressionValueIsNotNull(var10000, "test");
      var10000.setVisibility(0);
   }

   public View _$_findCachedViewById(int var1) {
      if (this._$_findViewCache == null) {
         this._$_findViewCache = new HashMap();
      }

      View var2 = (View)this._$_findViewCache.get(var1);
      if (var2 == null) {
         var2 = this.findViewById(var1);
         this._$_findViewCache.put(var1, var2);
      }

      return var2;
   }

   public void _$_clearFindViewByIdCache() {
      if (this._$_findViewCache != null) {
         this._$_findViewCache.clear();
      }

   }
}

上面代码 不用我讲解,大家也一定明白其中原理了。

DSL-Anko布局

Anko是什么鬼?看下官方的解释

Anko is a Kotlin library which makes Android application development faster and easier. It makes your code clean and easy to read, and lets you forget about rough edges of the Android SDK for Java.

大概意思就是:是一款Kotlin的库,可以使Android开发过程更快和更简单,可以使代码更加简洁和易读,并且能让你不去关注复杂的Android SDK。

Anko大体包含四个方面:

  • Commons:intents、dialogs、logging…
  • Layouts:动态的Android布局
  • SQLite
  • Coroutines(协程):轻量级的线程

基本的操作流程可以去官网去看下。

怎样预览Anko Layout

首先需要下载插件

然后打开预览

可以将XML转换为Anko Layout(转过去后一大堆红,最好还是不要用)

怎么使用Anko layout举个例子

// 方法一
class TestUI : AnkoComponent<Activity> {
    override fun createView(ui: AnkoContext<Activity>): View = ui.apply {
        frameLayout {
            lparams(matchParent, matchParent)
            textView {
                text = "Hello Anko!!"
            }.lparams(wrapContent, wrapContent) {
                gravity = Gravity.CENTER
            }
        }
    }.view
}

// 方法二
object TestUI1 {
    fun createView(context: Context): View = context.UI {
        frameLayout {
            lparams(matchParent, matchParent)
            textView {
                text = "Hello Anko!!"
            }.lparams(wrapContent, wrapContent) {
                gravity = Gravity.CENTER
            }
        }
    }.view
}


// 使用

fun test(){
    //直接在activity的onCreate中使用,代替setContentView(view)
     TestUI().setContentView(activity)
     //得到一个view对象
    val view2 = TestUI1.create(context)
}

使用第一种方式的好处是可以预览Anko Layout,预览AnkoLayout,但是也不是实时的,需要你build一下,才能预览,个人感觉现在用处不大,看后面会不会优化一下,可以做到实时更新。

AnkoLayout 优缺点

当一个新的东西引进时,我们要思考,引进进的技术能给我们带来什么?可以让我们的开发变得更简单?优化app的性能?不能只为用新技术而用新技术。

ankoLayout的优点:
可以不通过xml来反射拿到view,直接在代码中new出来,相对于效率更高,我做过实验,一个简单的布局:xml方式需要15-20ms,AnkoLayout需要7-12ms,大概能减少30%的执行时间,如果是复杂的,效果很更明显,对于我们的布局优化是一个很不错的方式。

ankoLayout缺点:
需要学习,现在anko库支持的不是很好,不能实时可视化布局,比较没法

其他

双冒号(::)

刚开始用到这个符号是取class,在kotlin中是这样取一个类的class,而不是我们常用的XXX.class了

System.out.print(Test::class.java)

刚开始的时候是懵逼的,根本不知道::是什么意思,只是简单理解,要获取一个类的class就使用这样的语法。随着对Kotlin的理解加深,也对::有了初步的理解。

Kotlin是支持函数作为参数(高阶函数),而::就是把一个方法当做一个参数,而且可以省略参数,举个例子

fun main() {
    foo2("xxx", this::bar2)     //right
    foo2("xxx", this::bar1)     //wrong, compile fail
}

fun bar1() {
    print("bar1")
}

fun foo2(content: String, body: (String) -> Unit) {
    body(content)
}

fun bar2(string: String) {
    print(string)
}

Kotlin委托

在委托模式中,当有两个对象参与参与同一个请求时,接受请求的对象将请求委托给另一个对象来处理。委托模式已经证明是实现继承的一个很好的代替方式。Kotlin中委托分为类委托和属性委托,Kotlin官方库也封装一些常用的委托。

类委托

其实我们平常经常使用委托模式,例如view.setOnClickListener(this/*或者listener*/),这行代码是不是看上去异常的眼熟,对这就是给view设置点击监听事件,而this和listener就是一种委托模式,来咱们看下kotlin的委托模式

interface A {
    fun print()
}

val a = object : A {
    override fun print() {
        println("this a")
    }
}

fun main(args: Array<String>) {
    var aa = object :A by a{}

    aa.print()// print: this a
}

做个小结:

  1. 委托的关键词是by
  2. 委托可以通过重写方法控制委托的范围

属性委托

个人在思考中,感觉属性委比类委托使用场景要多,类委托还没想到比较好的使用场景。

系统为我们提供了一些常用的属性委托,包括

  1. lazy 延迟属性,只在访问的时候初始化属性值
  2. observable,属性的值变更时,会有回调
  3. notNull,如果使用该属性的时候为null,则会抛出IllegalStateException异常
  4. 把多个属性存储在一个map中,而不是每个窜在单独的字段中
延迟属性 lazy

延迟属性只提供了只读属性,并且提供了三种模式来处理线程安全:

  • LazyThreadSafetyMode.SYNCHRONIZED 线程同步
  • LazyThreadSafetyMode.PUBLICATION 多线程使用,无需同步
  • LazyThreadSafetyMode.NONE 单线程场景

看个例子

fun test(){
    val a:String by lazy{
        println("get value")
        "a"
    }
}

看下源码

public fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
        when (mode) {
            LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
            LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
            LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
        }

//最简单的是UnsafeLazyImpl,单线程模式
internal class UnsafeLazyImpl<out T>(initializer: () -> T) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer  //拿到初始化的lambda方法
    private var _value: Any? = UNINITIALIZED_VALUE //定义了一个非法值

    override val value: T
        get() {         //这行应该和上面一行连起来看,就是重写该属性的get()方法
            if (_value === UNINITIALIZED_VALUE) {
                _value = initializer!!() //初始化
                initializer = null
            }
            @Suppress("UNCHECKED_CAST")
            return _value as T //返回初始化后的值
        }

    override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE // 这个是Lazy的方法,用于记录是否初始化结束。

    override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."  //重写toString方法

    private fun writeReplace(): Any = InitializedLazyImpl(value) //未找到方法作用
}

可观察属性 Observable

Delegates一共提供了三个委托方法notNull、observable、vetoable

直接看例子

/**
在值发生变化的时候,会回调下面的方法
**/
var name: String by Delegates.observable("init") { prop, old, new ->
    println("old : $old")
    println("new : $new")
}


/**
vetoable委托对象可以对数值的变化进行控制。与observable唯一的不同是,参数B的返回值变为了Boolean,如果返回值为true则接受变化,如果为false则不接受变化。
**/
var age: Int by Delegates.vetoable(18) { prop, old, new ->
    println("old : $old")
    println("new : $new")
    val accept=new>16
    println("accept : $accept")
    accept //只接受>16的变更
}

自定义属性委托

有两种方式实现委托属性

  • 实现接口ReadOnlyProperty或者ReadWriteProperty,从字面就可以看出来,一个是只读的委托属性,一个是读写的委托属性,lazy就是只读的委托属性
  • 字符重定义 operator fun getValue和operator fun setValue

看个例子

// 第一种方式
var a: String by object : ReadWriteProperty<Any?, String> {
        var _value: String = "" //属性委托一般都需要一个间接对象进行数据读取与赋值
        
        override fun getValue(thisRef: Any?, property: KProperty<*>): String {
         
            print("in getValue")
            print("$thisRef")
            print("$property")
            return _value
        }
        override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {

            println("in setValue")
            _value = value
        }
    }


// 第二种方式
class ExtrasDelegate<out T>(private val extraName: String, private val defaultValue: T) {

    private var extra: T? = null

    operator fun getValue(thisRef: AppCompatActivity, property: KProperty<*>): T {
        extra = getExtra(extra, extraName, thisRef)
        return extra ?: defaultValue
    }
}


写了多年的Java,直到看到Kotlin,原来代码可以如此优雅! 写了多年的Java,直到看到Kotlin,原来代码可以如此优雅! 如果你是像我一样是一名 优秀 的Java开发者 _ ,而且已经想用kotlin来实现你的程序,那么,抱歉!不要用Java的语法思维来写Kotlin,不要让kotlin的优雅埋没。如果你没有Java开发经验,下面的内容也对你会有帮助。。。 1.尽可能的少用 !! 个人感觉对于Null的检查是K... 阅读详情

相关推荐

解决vLLM安装卡在vllm-nccl-cu12依赖项的实战指南

本文提供了解决vLLM安装过程中卡在vllm-nccl-cu12依赖项的详细指南。通过分析CUDA版本兼容性问题,介绍了三种实用解决方案:安装指定版本vLLM、手动安装nccl-cu12依赖以及升级CUDA到12.x版本,并特别针对离线环境提供了安装建议,帮助开发者顺利完成vLLM的安装部署。

weixin_29098367的博客 513

Kotlin 中的Class 简单使用

Kotlin 中的Class 文章目录Kotlin 中的Class特点abstract classSubclass(子类)正确继承错误示范注意Sample(例子)Run and output(运行和输出)关键字with多个参数的构造SampleRun and output(运行和输出) 特点 默认情况下,在Kotlin中,类是final类,不能子类化(被继承),只允许继承abstract class 或者被关键字open标记的class abstract class abstract class Dwell

K_Hello的博客 2838

Oracle 11g R2 RAC 到单实例 Data Guard 搭建(RMAN备份方式)

Oracle RAC集群到单机的dataguard搭建过程

驰兔的博客 1721

kotlin的基本用法详解

基本用法 1.常量和变量 使用val关键字声明一个常量(只读,不可修改),使用var关键字声明一个变量,下面是具体用法: 1. fun test() { 2. //使用val关键字声明一个常量(只读),声明常量时必须初始化 3. val a: Int = 1 //显式指定常量的类型 4. val b = 2 //自动推断类型 5.

q9104422999的博客 3949

Tesseract in Java - Intellij IDEA对Tess4J的配置和使用

最近要弄OCR,搜到这个,官网全英文,很烦,看看有没有中文的博客介绍,擦,一个比一个奇葩,还是自己按照官方的来吧,外网慢的很,传到csdn给大家下载。中文训练库下载地址: http://download.youkuaiyun.com/detail/d_dmelon/9916532Tess4J3.4下载地址: http://download.youkuaiyun.com/detail/d_dmelon/9916519安装

Lewis的博客 1万+

增距镜或中继镜选型

增距镜光路图

camerall 5242

Kotlin基础用法总结

Kotlin 团队为 Android 开发提供了一套超越标准语言功能的工具: Kotlin Android 扩展是一个编译器扩展, 可以让你摆脱代码中的 findViewById() 调用,并将其替换为合成的编译器生成的属性。 Anko 是一个提供围绕Android API 的 Kotlin 友好的包装器的库 ,以及一个可以用 Kotlin 代码替换布局 .xml 文件的 DSL。 定...

millerkevin 657

BAQ压缩原理

BAQ——Block Adaptive Quantization,块自适应量化,一种数据压缩算法。

2867

写出优雅的Kotlin代码:聊聊我认为的 “Kotlinic“

Kotlinic”一词属于捏造的,参考的是著名的"Pythonic",后者可以译为“很Python”,意思是写的代码一看就很有Python味。照这个意思,"Kotlinic"就是“很Kotlin”,很有Kotlin味。Kotlin程序员们不少是从Java转过来的,包括我;大部分时候,大家也都把它当大号的Java语法糖在用。但Kotlin总归是一门新语言,而且,在我眼里还是门挺优雅的语言。所以,或许我们可以把Kotlin写得更Kotlin些。我想的聊聊。本文希望。...

m0_59162559的博客 344

【具身智能】感知 × 决策 × 执行:一个智能体的认知闭环怎么搭?

构建一个真正能在现实世界中运作的具身智能体,并不是简单地拼接“视觉 + 控制器 + 动作模块”就能完成的,它必须是一个**认知闭环结构**:能感知环境、理解状态、制定策略、执行动作,并从结果中学习反馈。 本讲将深入剖析具身智能的三大核心支柱:**感知系统、决策系统、执行系统**,并解析它们如何协同构建出一个“能观察、能思考、能行动”的智能体。

努力分享一些人工智能、计算机视觉、影像等相关的知识干货! 3259

# 【WorkBuddy专栏33】工作空间,最大的WorkBuddy技巧——任务隔离、记忆分区与多项目管理实战

工作空间,最大的WorkBuddy技巧——任务隔离、记忆分区与多项目管理实战

AI人人懂 2101

Kotlin:三分钟提高你的kotlin代码优雅度

Kotlin:三分钟提高你的kotlin代码优雅度前言单表达式函数使⽤作⽤域函数集合构造函数集合操作符延迟属性——lazy类型别名——typealias构造函数初始化属性数据类——data class命名参数高级函数结语 前言 2019 年 5 月 8 日,谷歌宣布,Kotlin 编程语言现在是 Android 应用程序开发人员的首选语言。 Kotlin语言对于Android开发人员的重要程度...

weixin_46091661的博客 647

[AI] 本地部署 Dify 遇到 Internal Error?一文彻底解决密码不一致引发的问题

摘要:Dify本地部署常见InternalError错误主要由配置残留或密码不一致导致,特别是修改.env文件后未同步清除数据库记录。彻底解决方案包括:1)清理Docker数据;2)删除旧配置和数据库;3)还原源代码;4)重建.env文件;5)重新初始化部署。关键提醒:记录部署路径、备份.env文件并妥善保存密码。该错误本质是配置冲突,最有效方法是完全重置而非调试,保持清晰的部署管理可避免多数InternalError问题。(149字)

技术小甜甜:传播可落地技术,帮助个人和企业降低解决真实场景问题的成本 432

Kotlin优雅地对多变量判空

我们都知道Kotlin使用?.可以优雅地处理判空 使用?.判空 对于以下Java代码 //java if(user != null) { system.out.println("name: " + user.name) } Kotlin中处理如下 //kt user?.let { println("name: ${it.name}")} 多变量判空 如果有多个变量需要判空呢? if(u...

移动端开发干货分享 4840

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

Android 使用Kotlin优雅的添加Fragment

综述 在上面文章聊一聊Kotlin扩展函数run,with,let,also和apply的使用和区别中讲解Kotlin的几个扩展函数的使用和区别。那么在这篇文章中去自己定义一些扩展函数来更加优雅的去将添加Fragment到Activity中。 回顾Fragment使用 在使用Kotlin之前,首先回顾一下在Java中是如何添加一个Fragment到Activity当中的。 Fragmen...

油焖茄子 的博客 4704

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

优雅地使用Kotlin Scope Functions

Kotlin 本身是一种很灵活的语言,用好它来写代码不是一件容易的事情,需要不断地去学习和总结。本文仅仅是抛砖引玉,希望能给大家带来更多的启发性。

hirezy 1317

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

Kotlin魔法——优雅实现多函数回调

在网络请求时,经常面临一类情况:网络请求有可能成功,也有可能失败,这就需要两个回调函数来分别对成功和失败的情况来进行处理,那么,在Kotlin这门无比强大的语言中,有没有一种“魔法”,能够优雅地实现这一类同时可能需要多个回调的场景呢?实现方式就介绍到这里啦,当然,第三种方式并不是没有缺点,如果说,需要多次实现onSuccess回调,那么第三种方式,以上面的代码就不方便做到啦,只能把override里改成add,然后成员变量block们用一个List存起来,然后依次触发~

m0_64365896的博客 2367

Android Kotlin中如何优雅地退出循环?

本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点。

AntDream的程序世界 1711

Kotlin 如何优雅地使用 Scope Functions

一. Scope FunctionsScope Functions :The Kotlin standard library contains several funct...

Java与Android技术栈 466

Kotlin 优雅的接口实现

利用 Java 的动态代理和 kotlin 的委托模式优雅实现接口

HJXASLZYY的专栏 1490

优雅的Kotlin编程 内联扩展函数 let函数

let扩展函数的实际上是一个作用域函数,当你需要去定义一个变量在一个特定的作用域范围内,let函数的是一个不错的选择; let函数另一个作用就是可以避免写一些判断null的操作。 let函数的使用的一般结构 object.let{ it.todo()//在函数体内使用it替代object对象去访问其公有的属性和方法 ... } //另一种用途 判断object为null的操作 object?.let{//表示object不为null的条件下,才会去执行let函数体 it.todo().

天涯海风的博客 652

Kotlin带参单例模式的优雅实现

kotlin经典单例实现 我们都知道 Kotlin 为我们实现单例提供了很方便的实现,一个关键词就可以搞定:那就是 object object SomeSingleton 反编译成 Java 代码: public final class SomeSingleton { public static final SomeSingleton INSTANCE; private SomeSingleton() { INSTANCE = (SomeSingleton)this; }

錦小白的博客 506

Kotlin协程的取消机制:深入理解和优雅实现

本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点Kotlin协程提供了一种高效的方式来处理并发和异步任务。在协程的生命周期管理中,取消协程是一项重要的操作。本文将深入探讨Kotlin协程的取消机制,介绍除了直接使用Job的cancel方法之外的其他方式,并提供优雅的实现策略。

AntDream的程序世界 1787

kotlin方法返回可能为null的优雅处理方式

kotlin方法返回null的优雅处理方式Java中的处理方式kotlin的处理方式 Java中的处理方式 /** * 被调用的方法可能返回null或者正常的字符串 **/ public String getA(String code){ if(code.equals("a")){ return "a"; }e...

笨鸟不会飞 6522
上一篇: 优雅的监听软键盘隐藏
下一篇: Gradle-自定义plugin
Bobby_lyc
博客等级 码龄16年 1164粉丝 89原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值