Android代码重构系列-03-用了Kotlin就别再用Java的思维写代码了(持续更新)

前言

还记得刚开始在正式项目上用Kotlin写代码时,很多代码是直接复制Java过来转成Kotlin的,结果代码Review的时候被评论成是用Java的思维写Kotlin代码,完全没有利用到Kotlin的特性。那我们怎么知道Java的代码如何用Kotlin特性来重写呢?别担心,本文记录并从源码角度剖析笔者在实际项目中那些Kotlin范的正确代码,帮助大家少走弯路。

当然笔者更建议多看看官方文档,多看看源码,多看看Kotlin编译后的Java代码来加深理解,这样才是真正的掌握Kotlin。毕竟,纸上得来终觉浅,绝知此事要躬行!如果不知甚解,有时候甚至可能还会弄巧成拙。

if else

判断对象是为null,如果不为null调用对象的方法。

Java写法:

private Handler handler;

@Test
void testIfNull() {
    if (handler != null) {
        handler.removeCallbacksAndMessages(null);
    }
}

Kotlin写法:

private val handler: Handler? = null

@Test
fun testIfNull(){
    handler?.removeCallbacksAndMessages(null)
}

判断对象是为null,如果不为null则初始化。

Java写法:

@Test
void testIfNullInit() {
    if (handler != null) {
        handler = new Handler();
    }
}

Kotlin写法:

@Test
fun testIfNullInit() {
    handler = handler ?: Handler()
}

判断对象为null怎样,不为null怎样。

Java写法:

    @Test
    void testIfElse() {
        if (handler != null) {
            System.out.println("handler not null ");
        } else {
            System.out.println("handler null ");
        }
    }

Kotlin写法:

handler?.apply {
    println("handler not null 1")
} ?: apply {
    println("handler null 1")
}

handler?.apply {
    println("handler not null 2 ")
} ?: let {
    println("handler null 2")
}

handler?.apply {
    println("handler not null 3")
} ?: run {
    println("handler null 3")
}

注意,网上有些文章的写法是这样的:

handler?.let {
    println("handler not null 4")
} ?: let {
    println("handler null 4")
}

这样是不对的,我们看let的源码:

@kotlin.internal.InlineOnly
public inline fun  T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

let返回的是block(this),这个是函数的返回值是有可能为null的,这样一来,上面的写法中?: let会因为?.let返回null导致执行的。我们测试看看:

handler = handler ?: Handler()
handler?.let {
    println("handler not null 4")
    null
} ?: let {
    println("handler null 4")
}

结果是:

handler not null 4
handler null 4

但为什么用?.apply就不会呢?我们看apply源码:

@kotlin.internal.InlineOnly
public inline fun  T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

apply返回的是this,也就是T自身,而?.apply是在T不为null的情况下才会执行apply的代码,因此?:右边就不被满足了,也就是说T为null执行?:后面的代码,不为null执行?.apply的代码。

switch

switch配合枚举。

Java代码:

private enum WeekDays {
    SUNDAY(0), MONDAY(1), TUESDAY(2), WEDNESDAY(3),
    THURSDAY(4), FRIDAY(5), SATURDAY(6);
    private int value;

    WeekDays(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

WeekDays today = WeekDays.SUNDAY;

public void setToday(WeekDays today) {
    this.today = today;
}

public WeekDays getToday() {
    return today;
}

@Test
void testEnum() {
    switch (today) {
        case SUNDAY:
            break;
        case MONDAY:
            break;
        case TUESDAY:
            break;
        case WEDNESDAY:
            break;
        case THURSDAY:
            break;
        case FRIDAY:
            break;
        case SATURDAY:
            break;
        default:
            break;
    }
}

或者使用IntDef:

//先定义 常量
public static final int SUNDAY = 0;
public static final int MONDAY = 1;
public static final int TUESDAY = 2;
public static final int WEDNESDAY = 3;
public static final int THURSDAY = 4;
public static final int FRIDAY = 5;
public static final int SATURDAY = 6;

//定义@WeekDays注解
@IntDef({SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY})
@Retention(RetentionPolicy.SOURCE)
public @interface WeekDays {
}

//声明变量,限制变量的取值范围
@WeekDays
int currentDay = SUNDAY;

//添加@WeekDays注解,限制传入值的范围
public void setCurrentDay(@WeekDays int currentDay) {
    this.currentDay = currentDay;
}

//添加@WeekDays注解,限制返回值的范围
@WeekDays
public int getCurrentDay() {
    return currentDay;
}

@Test
void testEnum() {
    // 声明变量,限制变量的取值范围
    @WeekDays int today = getCurrentDay();
    switch (today) {
        case SUNDAY:
            break;
        case MONDAY:
            break;
        case TUESDAY:
            break;
        case WEDNESDAY:
            break;
        case THURSDAY:
            break;
        case FRIDAY:
            break;
        case SATURDAY:
            break;
        default:
            break;
    }
    setCurrentDay(SUNDAY);
}

Kotlin可以使用枚举类:

/**
 * 密封类
 */
sealed class WeekDays(value: Int) {
    object SUNDAY : WeekDays(0)
    object MONDAY : WeekDays(1)
    object TUESDAY : WeekDays(2)
    object WEDNESDAY : WeekDays(3)
    object THURSDAY : WeekDays(4)
    object FRIDAY : WeekDays(5)
    object SATURDAY : WeekDays(6)
}

private var today: WeekDays = WeekDays.SUNDAY

@Test
fun testEnum() {
    when (today) {
        WeekDays.FRIDAY -> TODO()
        WeekDays.MONDAY -> TODO()
        WeekDays.SATURDAY -> TODO()
        WeekDays.SUNDAY -> TODO()
        WeekDays.THURSDAY -> TODO()
        WeekDays.TUESDAY -> TODO()
        WeekDays.WEDNESDAY -> TODO()
    }
}

使用密封类比枚举好的地方在于使用when时编译器会提示我们有遗漏的分支没有处理。

看以下密封类编译成的Java代码:

public abstract static class WeekDays {
    private WeekDays(int value) {
    }
    
    // $FF: synthetic method
    public WeekDays(int value, DefaultConstructorMarker $constructor_marker) {
     this(value);
    }
    
    
    public static final class SUNDAY extends KotlinUnitTest.WeekDays {
     @NotNull
     public static final KotlinUnitTest.WeekDays.SUNDAY INSTANCE;
    
     private SUNDAY() {
        super(0, (DefaultConstructorMarker)null);
     }
    
     static {
        KotlinUnitTest.WeekDays.SUNDAY var0 = new KotlinUnitTest.WeekDays.SUNDAY();
        INSTANCE = var0;
     }
    }
}

@Test
public final void testEnum() {
  KotlinUnitTest.WeekDays var1 = this.today;
  if (Intrinsics.areEqual(var1, KotlinUnitTest.WeekDays.FRIDAY.INSTANCE)) {
     throw (Throwable)(new NotImplementedError((String)null, 1, (DefaultConstructorMarker)null));
  } else if (Intrinsics.areEqual(var1, KotlinUnitTest.WeekDays.MONDAY.INSTANCE)) {
     throw (Throwable)(new NotImplementedError((String)null, 1, (DefaultConstructorMarker)null));
  } else if (Intrinsics.areEqual(var1, KotlinUnitTest.WeekDays.SATURDAY.INSTANCE)) {
     throw (Throwable)(new NotImplementedError((String)null, 1, (DefaultConstructorMarker)null));
  } else if (Intrinsics.areEqual(var1, KotlinUnitTest.WeekDays.SUNDAY.INSTANCE)) {
     throw (Throwable)(new NotImplementedError((String)null, 1, (DefaultConstructorMarker)null));
  } else if (Intrinsics.areEqual(var1, KotlinUnitTest.WeekDays.THURSDAY.INSTANCE)) {
     throw (Throwable)(new NotImplementedError((String)null, 1, (DefaultConstructorMarker)null));
  } else if (Intrinsics.areEqual(var1, KotlinUnitTest.WeekDays.TUESDAY.INSTANCE)) {
     throw (Throwable)(new NotImplementedError((String)null, 1, (DefaultConstructorMarker)null));
  } else if (Intrinsics.areEqual(var1, KotlinUnitTest.WeekDays.WEDNESDAY.INSTANCE)) {
     throw (Throwable)(new NotImplementedError((String)null, 1, (DefaultConstructorMarker)null));
  } else {
     throw new NoWhenBranchMatchedException();
  }
}

不就是if else吗?不过编译器帮我们完成了这部分繁琐的工作,就说香不香吧。

构造者模式

构造这模式我们很熟悉了,AlertDialog就是用到了这个模式,我们看下精简过后的源码:

public class AlertDialog extends Dialog implements DialogInterface {
    private final String title;
    private final String message;
    private CharSequence mPositiveButtonText;
    private DialogInterface.OnClickListener mPositiveButtonListener;
    private CharSequence mNegativeButtonText;
    private DialogInterface.OnClickListener mNegativeButtonListener;

    public AlertDialog(@NonNull Context context, String title, String message) {
        super(context);
        this.title = title;
        this.message = message;
    }

    public CharSequence getPositiveButtonText() {
        return mPositiveButtonText;
    }

    public void setPositiveButtonText(CharSequence mPositiveButtonText) {
        this.mPositiveButtonText = mPositiveButtonText;
    }

    public OnClickListener getPositiveButtonListener() {
        return mPositiveButtonListener;
    }

    public void setPositiveButtonListener(OnClickListener mPositiveButtonListener) {
        this.mPositiveButtonListener = mPositiveButtonListener;
    }

    public CharSequence getNegativeButtonText() {
        return mNegativeButtonText;
    }

    public void setNegativeButtonText(CharSequence mNegativeButtonText) {
        this.mNegativeButtonText = mNegativeButtonText;
    }

    public OnClickListener getNegativeButtonListener() {
        return mNegativeButtonListener;
    }

    public void setNegativeButtonListener(OnClickListener mNegativeButtonListener) {
        this.mNegativeButtonListener = mNegativeButtonListener;
    }

    public static class Builder {

        private final Context context;
        private final String title;
        private final String message;
        private CharSequence mPositiveButtonText;
        private DialogInterface.OnClickListener mPositiveButtonListener;
        private CharSequence mNegativeButtonText;
        private DialogInterface.OnClickListener mNegativeButtonListener;

        public Builder(Context context, String title, String message) {
            this.context = context;
            this.title = title;
            this.message = message;
        }

        public Builder setPositiveButtonListener(CharSequence text, DialogInterface.OnClickListener positiveButtonListener) {
            mPositiveButtonText = text;
            mPositiveButtonListener = positiveButtonListener;
            return this;
        }

        public Builder setNegativeButtonListener(CharSequence text, DialogInterface.OnClickListener negativeButtonListener) {
            mNegativeButtonText = text;
            mNegativeButtonListener = negativeButtonListener;
            return this;
        }

        public AlertDialog create() {
            final AlertDialog dialog = new AlertDialog(context, title, message);
            if (mPositiveButtonText != null) {
                dialog.setPositiveButtonText(mPositiveButtonText);
            }
            if (mPositiveButtonListener != null) {
                dialog.setPositiveButtonListener(mPositiveButtonListener);
            }
            if (mNegativeButtonText != null) {
                dialog.setNegativeButtonText(mNegativeButtonText);
            }
            if (mNegativeButtonListener != null) {
                dialog.setPositiveButtonListener(mNegativeButtonListener);
            }
            return dialog;
        }

        public AlertDialog show() {
            final AlertDialog dialog = create();
            dialog.show();
            return dialog;
        }

    }

}

如果使用Kotlin还是用Java的思维写这个代码,就相当于用Kotlin直译了一遍而已。看看Java转Kotlin后的代码:

class AlertDialog(context: Context, private val title: String, private val message: String) :
    Dialog(context), DialogInterface {
    var positiveButtonText: CharSequence? = null
    var positiveButtonListener: DialogInterface.OnClickListener? = null
    var negativeButtonText: CharSequence? = null
    var negativeButtonListener: DialogInterface.OnClickListener? = null

    class Builder(
        private val context: Context,
        private val title: String,
        private val message: String
    ) {
        private var mPositiveButtonText: CharSequence? = null
        private var mPositiveButtonListener: DialogInterface.OnClickListener? = null
        private var mNegativeButtonText: CharSequence? = null
        private var mNegativeButtonListener: DialogInterface.OnClickListener? = null
        fun setPositiveButtonListener(
            text: CharSequence?,
            positiveButtonListener: DialogInterface.OnClickListener?
        ): Builder {
            mPositiveButtonText = text
            mPositiveButtonListener = positiveButtonListener
            return this
        }

        fun setNegativeButtonListener(
            text: CharSequence?,
            negativeButtonListener: DialogInterface.OnClickListener?
        ): Builder {
            mNegativeButtonText = text
            mNegativeButtonListener = negativeButtonListener
            return this
        }

        fun create(): AlertDialog {
            val dialog = AlertDialog(context, title, message)
            if (mPositiveButtonText != null) {
                dialog.positiveButtonText = mPositiveButtonText
            }
            if (mPositiveButtonListener != null) {
                dialog.positiveButtonListener = mPositiveButtonListener
            }
            if (mNegativeButtonText != null) {
                dialog.negativeButtonText = mNegativeButtonText
            }
            if (mNegativeButtonListener != null) {
                dialog.positiveButtonListener = mNegativeButtonListener
            }
            return dialog
        }

        fun show(): AlertDialog {
            val dialog = create()
            dialog.show()
            return dialog
        }
    }
}

没想到,Kotlin版本只有58行代码,差不多是Java的一半,Kotlin语法糖果然名不虚传。不过,上面的代码并不是一个传统的构造者模式。具体可以参考构造者设计模式。扯远了,回到正题。

那如何利用Kotlin特性写出更优雅的代码?那就是利用Kotlin Function Literals with Receiver。官方文档的介绍如下:

Function literals with receiver
Function types with receiver, such as T.() -> R, can be instantiated with a special form of function literals – function literals with receiver.

As mentioned above, Kotlin provides the ability to call an instance of a function type with receiver while providing the receiver object.

Inside the body of the function literal, the receiver object passed to a call becomes an implicit this, so that you can access the members of that receiver object without any additional qualifiers, or access the receiver object using a this expression.

This behavior is similar to that of extension functions, which also allow you to access the members of the receiver object inside the function body.
机器翻译:
带有接收器的函数字面量
带接收器的函数类型,如 T.() -> R,可以用函数字面量的特殊形式来实例化--带接收器的函数字面量。

如上所述,Kotlin提供了调用 带接收器的函数类型实例的能力,同时 提供接收器对象

在函数字面量的主体中,传递给调用者的 接收器对象成为一个隐含的this,因此你可以访问该接收器对象的成员,而不需要任何额外的限定词,或者使用this表达式访问接收器对象。

这种行为 类似于扩展函数的行为,扩展函数也允许你在函数体中访问接收对象的成员

可能首次接触个玩意的读者会有点不理解。首先它是一个Lamdba表达式,对Lamdba表达式不理解的可以看笔者这篇文章:深入理解Java Lambda表达式,匿名函数,闭包。不过Kotlin的Lamdba表达式和Java的Lamdba表达式并不是同一个东西,具体见扔物线大佬的Kotlin 的 Lambda 表达式,大多数人学得连皮毛都不算。

Lambda expressions and anonymous functions are function literals.
翻译:Lambda表达式和匿名函数是字面量( function literals
来自官方文档: https://kotlinlang.org/docs/lambdas.html#lambda-expressions-and-anonymous-functions

说了这么多,这个带接收器的Lambda表达式T.() -> R有什么作用?别急,我们先理解它怎么用,举个例子:

比如实现一个两数相加的表达式,正常会这么写:

val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
println(sum.invoke(1, 1))
println(sum(1, 2))

我们甚至可以简化成:

val sum = { x: Int, y: Int -> x + y }
println(sum.invoke(1, 1))
println(sum(1, 2))

虽然简化了,但是基于函数式编程的思想我们可以进一步改造,这时候就可以使用写一个sum扩展函数了:

fun Int.sum(other: Int): Int {
    return this + other
}

//1+2
println(1.sum(2))

Lambda表达式是理解了,那接收器怎么说?其实前面已经提到了,在函数字面量的主体中,传递给调用者的接收器对象成为一个隐含的this,因此你可以访问该接收器对象的成员,而不需要任何额外的限定词,或者使用this表达式访问接收器对象。

再看这个带接收器的Lambda表达式T.() -> R,我们拆分为两个部分来理解:

T.():

这个形式看起来像不像扩展函数,其实可以简单理解为是T这个泛型的扩展函数(本质都是获取到了T的对象,不过编译器做的工作有所不同,下面会讲到),因此我们可以直接访问T的成员,就像this那样使用。

R:

R就是接收器,接收什么?接收T对象,也就是接收器对象(即为T的对象)。注意接收器接收器对象不是一回事。这么说吧,接收器对象(T对象)传给了接收器,在接收器内部使用this就是使用接收器对象T对象的this,前面说的隐含的this就是这么个意思。不过用不用this表达式是我们的自由,最终的效果是在接收器内部可以不需要任何额外的限定词即可访问T对象的成员。

回到上面的例子,按照T.() -> R,我们可以写出下面的代码:

val sum = fun Int.(other: Int): Int = this + other
println(1.sum(2))
println(sum(1,2))

fun Int.(other: Int): Int = this + other是不是像极了扩展函数,一样的形式,一样的使用this。

不过这个例子没有体现出接收器对象是一个隐含的this的精髓,修改下:

val sum: Int.(Int) -> Int = { other -> plus(other) }
println(1.sum(2))
println(sum(1,2))

参数里的other可以去掉,并且可以直接调用plus函数(这就是隐含this的体现),实际上就等于下面的代码:

val sum: Int.(other: Int) -> Int = { other -> this.plus(other) }
println(1.sum(2))
println(sum(1,2))

到这里,你可以观察到一个细节,如果使用扩展函数,实际使用的时候只能用下面的方式:

println(1.sum(2))

但是带接收器的Lambda表达式T.() -> R却同时支持:

println(1.sum(2))
println(sum(1,2))

再对比下它们的写法:

//扩展函数
fun Int.sum(other: Int): Int {
    return this + other
}
println(1.sum(2))

//带接收器的Lambda表达式T.() -> R
val sum: Int.(other: Int) -> Int = { other -> this.plus(other) }
println(1.sum(2))
println(sum(1,2))

为什么带接收器的Lambda表达式T.() -> R支持sum(1,2)这样调用,而扩展函数却不支持?还是得看反编译看源码来理解。

先看扩展函数的:

//Kotlin源码
class KotlinLambda {
    @Test
    fun testLambda() {
        /*val sum = { x: Int, y: Int -> x + y }
        println(sum.invoke(1, 1))
        println(sum(1, 2))*/

        1.sum(2)
        println(1.sum(2))
        //println(sum(1,2))
    }
}

fun Int.sum(other: Int): Int {
    return this + other
}

//下面是反编译成Java
// KotlinLambda.java
public final class KotlinLambda {
   @Test
   public final void testLambda() {
      KotlinLambdaKt.sum(1, 2);
      int var1 = KotlinLambdaKt.sum(1, 2);
      System.out.println(var1);
   }
}
// KotlinLambdaKt.java
public final class KotlinLambdaKt {
   public static final int sum(int $this$sum, int other) {
      return $this$sum + other;
   }
}

可以看到,多了个KotlinLambdaKt,原来的扩展函数sum变成了KotlinLambdaKt的静态函数sum。既然是个静态函数,那就肯定不能直接sum(1,2)这样调用,得 KotlinLambdaKt.sum(1, 2)这样才行。也就是说扩展函数的本质是生成了一个名为XXXKt的类,包含一个同名的静态函数,静态函数中的第一个参数,是当前调用的对象。这就是为什么扩展函数允许你在函数体中访问接收对象的成员。这并不是什么魔法,者是编译器的功劳。

所以,如果Java要调用Kotlin的扩展函数,就需要通过对应的类去调用同名的静态函数:

KotlinLambdaKt.sum(1,2);

不过再次强调,第一个参数,扩展函数T的对象,由于Int这种基本类型,所以传入值是没问题的,但是如果是自定义的类的扩展对象,那就需要传入类的实例。

再看

//Kotlin源码
class KotlinLambda {

    @Test
    fun testLambdaWithReceiver() {
        val sum: Int.(other: Int) -> Int = { other -> this.plus(other) }
        println(1.sum(2))
        println(sum(1, 2))
    }


}
//反编译成java
public final class KotlinLambda {
   @Test
   public final void testLambdaWithReceiver() {
      Function2 sum = (Function2)null.INSTANCE;
      int var2 = ((Number)sum.invoke(1, 2)).intValue();
      System.out.println(var2);
      var2 = ((Number)sum.invoke(1, 2)).intValue();
      System.out.println(var2);
   }
}

这个Function2是什么?源码是这样的:

public interface Function2 : Function {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2): R
}

其实官方定义了很多的接口,从Function0到Function22,分别支持0-22个参数,具体看源码就知道了。这时候我们就知道了,Lambda表达式的声明就是这些Function接口,Lambda表达式的主体就是对应Function接口的实现。

所以带接收器的Lambda表达式T.() -> R为什么支持下面这两种形式的调用就显而易见了。

//Kotlin
println(1.sum(2))
println(sum(1,2))
//Java
Function2 sum = (Function2)null.INSTANCE;
int var2 = ((Number)sum.invoke(1, 2)).intValue();
System.out.println(var2);
var2 = ((Number)sum.invoke(1, 2)).intValue();
System.out.println(var2);

看到了吧,1.sum(2)其实就是一个语法糖,反编译成Java,就是调用Function的接口实例的invoke方法,入参是调用对象本身和扩展函数的参数,返回值则是扩展函数的返回值。sum(1,2)也是一样的。

再对比扩展函数,它们的区别就显而易见了。

扩展函数

本质是静态函数,函数的第一个参数传入了调用者的对象,后面则是扩展函数声明的参数。

Lambda表达式

本质是Function的接口,接口的invoke方法传入的参数是Lambda表达式声明的参数。

带接收器的Lambda表达式T.() -> R

本质是Function的接口,接口的invoke方法传入的第一个参数是调用者的对象,后面则是Lambda表达式声明的参数。所谓的接收器就是Function的invoke方法。接收器对象就是调用者T的对象。

附上完整的Kotlin和反编译后Java方便大家理解:

package com.nxg.composeplane

import org.junit.Test

class KotlinLambda {

    var value = 0

    @Test
    fun testLambda() {
        val sum = { x: Int, y: Int -> x + y }
        println(sum.invoke(1, 1))
        println(sum(1, 2))

        1.sum(2)
        println(1.sum(2))
        println(this.sum(2))
    }

    @Test
    fun testLambdaWithReceiver() {
        val sum = fun Int.(other: Int): Int = this + other
        println(1.sum(2))
        println(sum(1, 2))
    }

    @Test
    fun testLambdaWithReceiver2() {
        val sum: Int.(Int) -> Int = { other -> plus(other) }
        println(1.sum(2))
        println(sum(1, 2))
    }

    @Test
    fun testLambdaWithReceiver3() {
        val sum: Int.(other: Int) -> Int = { other -> this.plus(other) }
        println(1.sum(2))
        println(sum(1, 2))
    }

    @Test
    fun testLambdaWithReceiver4() {
        val sum: KotlinLambda.(other: Int) -> Int = { other -> this.value + other }
        println(sum(2))
        println(sum(this, 2))
    }

    @Test
    fun testLambdaWithReceiver5() {
        val sum: (other: Int) -> Int = { other -> this.value + other }
        println(sum(2))
    }

}

fun Int.sum(other: Int): Int {
    return this.plus(other)
}

fun KotlinLambda.sum(other: Int): Int {
    return this.value + other
}
// KotlinLambda.java
package com.nxg.composeplane;

import kotlin.Metadata;
import kotlin.jvm.functions.Function1;
import kotlin.jvm.functions.Function2;
import org.junit.Test;

@Metadata(
   mv = {1, 5, 1},
   k = 1,
   d1 = {"\u0000\u001c\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\b\n\u0002\b\u0005\n\u0002\u0010\u0002\n\u0002\b\u0006\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\b\u0010\t\u001a\u00020\nH\u0007J\b\u0010\u000b\u001a\u00020\nH\u0007J\b\u0010\f\u001a\u00020\nH\u0007J\b\u0010\r\u001a\u00020\nH\u0007J\b\u0010\u000e\u001a\u00020\nH\u0007J\b\u0010\u000f\u001a\u00020\nH\u0007R\u001a\u0010\u0003\u001a\u00020\u0004X\u0086\u000e¢\u0006\u000e\n\u0000\u001a\u0004\b\u0005\u0010\u0006\"\u0004\b\u0007\u0010\b¨\u0006\u0010"},
   d2 = {"Lcom/nxg/composeplane/KotlinLambda;", "", "()V", "value", "", "getValue", "()I", "setValue", "(I)V", "testLambda", "", "testLambdaWithReceiver", "testLambdaWithReceiver2", "testLambdaWithReceiver3", "testLambdaWithReceiver4", "testLambdaWithReceiver5", "ComposerPlane.app.unitTest"}
)
public final class KotlinLambda {
   private int value;

   public final int getValue() {
      return this.value;
   }

   public final void setValue(int var1) {
      this.value = var1;
   }

   @Test
   public final void testLambda() {
      Function2 sum = (Function2)null.INSTANCE;
      int var2 = ((Number)sum.invoke(1, 1)).intValue();
      System.out.println(var2);
      var2 = ((Number)sum.invoke(1, 2)).intValue();
      System.out.println(var2);
      KotlinLambdaKt.sum(1, 2);
      var2 = KotlinLambdaKt.sum(1, 2);
      System.out.println(var2);
      var2 = KotlinLambdaKt.sum(this, 2);
      System.out.println(var2);
   }

   @Test
   public final void testLambdaWithReceiver() {
      Function2 sum = (Function2)null.INSTANCE;
      int var2 = ((Number)sum.invoke(1, 2)).intValue();
      System.out.println(var2);
      var2 = ((Number)sum.invoke(1, 2)).intValue();
      System.out.println(var2);
   }

   @Test
   public final void testLambdaWithReceiver2() {
      Function2 sum = (Function2)null.INSTANCE;
      int var2 = ((Number)sum.invoke(1, 2)).intValue();
      System.out.println(var2);
      var2 = ((Number)sum.invoke(1, 2)).intValue();
      System.out.println(var2);
   }

   @Test
   public final void testLambdaWithReceiver3() {
      Function2 sum = (Function2)null.INSTANCE;
      int var2 = ((Number)sum.invoke(1, 2)).intValue();
      System.out.println(var2);
      var2 = ((Number)sum.invoke(1, 2)).intValue();
      System.out.println(var2);
   }

   @Test
   public final void testLambdaWithReceiver4() {
      Function2 sum = (Function2)null.INSTANCE;
      int var2 = ((Number)sum.invoke(this, 2)).intValue();
      System.out.println(var2);
      var2 = ((Number)sum.invoke(this, 2)).intValue();
      System.out.println(var2);
   }

   @Test
   public final void testLambdaWithReceiver5() {
      Function1 sum = (Function1)(new Function1() {
         // $FF: synthetic method
         // $FF: bridge method
         public Object invoke(Object var1) {
            return this.invoke(((Number)var1).intValue());
         }

         public final int invoke(int other) {
            return KotlinLambda.this.getValue() + other;
         }
      });
      int var2 = ((Number)sum.invoke(2)).intValue();
      System.out.println(var2);
   }
}
// KotlinLambdaKt.java
package com.nxg.composeplane;

import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = {1, 5, 1},
   k = 2,
   d1 = {"\u0000\u000e\n\u0000\n\u0002\u0010\b\n\u0002\u0018\u0002\n\u0002\b\u0002\u001a\u0012\u0010\u0000\u001a\u00020\u0001*\u00020\u00022\u0006\u0010\u0003\u001a\u00020\u0001\u001a\u0012\u0010\u0000\u001a\u00020\u0001*\u00020\u00012\u0006\u0010\u0003\u001a\u00020\u0001¨\u0006\u0004"},
   d2 = {"sum", "", "Lcom/nxg/composeplane/KotlinLambda;", "other", "ComposerPlane.app.unitTest"}
)
public final class KotlinLambdaKt {
   public static final int sum(int $this$sum, int other) {
      return $this$sum + other;
   }

   public static final int sum(@NotNull KotlinLambda $this$sum, int other) {
      Intrinsics.checkNotNullParameter($this$sum, "$this$sum");
      return $this$sum.getValue() + other;
   }
}

再啰嗦下,可能有读者会对这个有疑问:(Function2)null.INSTANCE,为啥有个null,因为调用者是基本数据类型。如果是其它类,则是这样的: Function2 sum = (Function1)(new Function2(){});

老毛病又犯了,花了比较多的篇幅去介绍了带接收器的Lambda表达式T.() -> R,回到整体,构造者模式怎么使用这个特性来进行改造?

在AlertDialog的半生对象中定义一个内联的build方法,如下:

companion object {
    inline fun build(
        context: Context,
        title: String,
        message: String,
        block: Builder.() -> Unit
    ) = Builder(context, title, message).apply(block)
}

关键是使用了函数apply,因为它的参数为带接收器的Lambda表达式T.() -> R,并且返回了T.this

@kotlin.internal.InlineOnly
public inline fun  T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

这样一来,Builder为T时,我们就可以在block代码块中访问Builder.this,从而简化原来链式调用的写法。示例如下:

val appContext = InstrumentationRegistry.getInstrumentation().targetContext
AlertDialog.build(appContext, "title", "message") {
    setPositiveButtonListener("confirm") { _, _ ->

    }
    setNegativeButtonListener("cancel") { _, _ ->
    }
}.show()

不过有的读者可能会有疑问,你这种方式也没有更优雅啊?我直接通过AlertDialog().apply{}这种方式不是也可以吗?效果差不多。

AlertDialog(appContext, "title", "message").apply {
    setPositiveButtonListener("confirm") { _, _ ->

    }
    setNegativeButtonListener("cancel") { _, _ ->
    }
}

是的,包括Builder也可以直接AlertDialog.Builder().apply{}这样用。没必另外搞一个build的方法。

这里把三种写法放到一起供大家比较:

AlertDialog.build(appContext, "title", "message") {
    setPositiveButtonListener("confirm") { _, _ ->

    }
    setNegativeButtonListener("cancel") { _, _ ->
    }
}.show()

AlertDialog(appContext, "title", "message").apply {
    setPositiveButtonListener("confirm") { _, _ ->

    }
    setNegativeButtonListener("cancel") { _, _ ->
    }
}.show()

AlertDialog.Builder(appContext, "title", "message").apply {
    setPositiveButtonListener("confirm") { _, _ ->

    }
    setNegativeButtonListener("cancel") { _, _ ->
    }
}.create().show()

大家倾向于哪一种?

这里谈下笔者的理解。如果是从构造者模式的角度模式出发,AlertDialog().apply{}只是利用了apply的特性,并不是构造者模式的实现。那么问题来了,我们真的需求构造者模式吗?

答案是看情况。首先,我们得了解构造者模式的定义以及它的使用场景和优缺点。然后,再根据实际需求来决定是否需要使用构造者模式。如果构建一个对象的流程是简单的,直接apply足以,并不是非得使用构造者模式不可。

当然,如果构建一个对象的流程是复杂的,并且经常需要构造不同的对象,比如Android中的AlertDialog,那还是非常建议使用构造者模式的。

不过设计模式虽然好,但是,网上有一个观点,不要滥用设计模式。笔者对这个一观点深以为然,包括六大设计原则也是一样。如果不了解这些设计模式的出现的缘由,不了解它们的使用场景,优缺点,就很容易出现滥用甚至适得其反的情况。

最直接的一个例子就是单例模式,在项目里到处可见,而且是不考虑使用场景的。要知道单例一旦初始化,就是长期占用内存的,我们需要考虑哪些对象是可以长期存在的,哪些是可以用完就回收的。总之,更好的方式是从软件工程的思维去考虑。

另外的例子就是不看需求,生搬硬套。比如状态模式的使用,可以参考笔者的Android代码重构系列-01-Kotlin实现状态机,不同的需求实现状态机有其适合的方式,并不是非得使用状态模式。

考虑到篇幅的原因,本篇文章很难一次写完然后再发布。因此改成不定期更新。更新时会加上相应的日期。文章存在的问题也会及时修改并重新发布,欢迎交流。

写在最后,首先非常感谢您耐心阅读完整篇文章,坚持写原创且基于实战的文章不是件容易的事,如果本文刚好对您有点帮助,欢迎您给文章点赞评论,您的鼓励是笔者坚持不懈的动力。写博客不仅仅是巩固学习的一个好方式,更是一个观点碰撞查漏补缺的绝佳机会,若文章有不对之处非常欢迎指正,再次感谢。

参考资料

Kotlin - 改良构建者模式

(译)Effective Kotlin之建造者模式(二)

你会用Kotlin实现构建者模式吗?

你可能感兴趣的:(Android开发实践,kotlin,重构)