Android Code Refactoring Series-03-If you use Kotlin, stop writing code with Java thinking (continuously updated)

foreword

I still remember that when I first started writing code in Kotlin on official projects, many codes were directly copied from Java and converted into Kotlin. As a result, when the code was reviewed, it was commented that Kotlin code was written with Java thinking, and Kotlin’s features were not used at all. . So how do we know how Java code can be rewritten with Kotlin features? Don't worry, this article records and analyzes the author's Kotlin-style correct code in the actual project from the perspective of source code, so as to help you avoid detours.

Of course, the author recommends to read more official documents, more source code, and more Java code compiled by Kotlin to deepen understanding, so as to truly master Kotlin. After all, what is achieved on paper is always superficial, and I know that this matter must be done in practice! Sometimes it can even be self-defeating if you don't know it well.

if else

Determine whether the object is null, and if it is not null, call the method of the object.

Java writing method:

private Handler handler;

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

Kotlin writing method:

private val handler: Handler? = null

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

Determine whether the object is null, and initialize it if it is not null.

Java writing method:

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

Kotlin writing method:

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

How to judge whether the object is null or not.

Java writing method:

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

Kotlin writing method:

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")
}

Note that some articles on the Internet are written like this:

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

This is wrong, let's look at the source code of let:

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

let returns block(this), which means that the return value of the function may be null. In this way, the ?: let in the above writing method will be executed because ?.let returns null. Let's test it out:

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

turn out:

handler not null 4
handler null 4

But why not use ?.apply? Let's look at the apply source code:

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

apply returns this, which is T itself, and ?.apply executes the code of apply only when T is not null, so the right side of ?: is not satisfied, that is to say, execute ?: when T is null The code behind, does not execute the code of ?.apply for null.

switch

switch cooperates with enumeration.

Java code:

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;
    }
}

Or use 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 can use enumeration classes:

/**
 * 密封类
 */
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()
    }
}

The advantage of using sealed classes over enumerations is that when using when, the compiler will prompt us that there are missing branches that have not been processed.

Look at the Java code compiled into the following sealed class:

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();
  }
}

Isn't it just if else? However, the compiler has helped us complete this part of the tedious work, so let's just say it is not fragrant.

constructor pattern

We are familiar with constructing this mode. AlertDialog uses this mode. Let’s take a look at the simplified source code:

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;
        }

    }

}

If you use Kotlin or write this code with Java thinking, it is equivalent to a literal translation with Kotlin. Look at the code after Java is converted to 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
        }
    }
}

Unexpectedly, the Kotlin version has only 58 lines of code, which is almost half that of Java, and the Kotlin syntax sugar really lives up to its reputation. However, the above code is not a traditional constructor pattern. For details, please refer to the constructor design pattern . Pulling away, back to the topic.

So how to use Kotlin features to write more elegant code? That is to utilize Kotlin Function Literals with Receiver . The official documentation is as follows:

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.
Machine Translation:
Function literals
with receivers Function types with receivers, such as T.( ) -> R , which can be instantiated with a special form of function literal—a function literal with receiver.

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

In the body of the function literal, the receiver object passed to the caller becomes an implicit this , so you can access members of the receiver object without any additional qualifiers, or use the this expression to access the receiver device object.

This behavior is similar to that of extension functions, which also allow you to access members of the receiving object within the function body .

Readers who may be exposed to this gadget for the first time may not understand it. First of all, it is a Lambda expression . If you don’t understand Lambda expressions, you can read the author’s article: In-depth understanding of Java Lambda expressions, anonymous functions, and closures . However, Kotlin's Lambda expression and Java's Lambda expression are not the same thing. For details, see Kotlin's Lambda expression of Throwing Boss . Most people don't even count the fur .

Lambda expressions and anonymous functions are function literals.
Translation: Lambda expressions and anonymous functions are literals ( function literals )
from the official documentation: https://kotlinlang.org/docs/lambdas.html#lambda-expressions-and-anonymous- functions

Having said all that, what does this lambda expression T.() -> R with receiver do? Don't worry, let's understand how it works first, for example:

For example, to implement an expression for adding two numbers, it is normally written like this:

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

We can even simplify it to:

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

Although simplified, based on the idea of ​​functional programming, we can further transform it. At this time, we can write a sum extension function:

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

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

The Lambda expression is understood, so what does the receiver say? In fact, as mentioned earlier, in the body of the function literal, the receiver object passed to the caller becomes an implicit this , so you can access the members of the receiver object without any additional qualifiers, or The receiver object is accessed using the this expression.

Look at this Lambda expression T.() -> R with a receiver, we split it into two parts to understand:

T.():

Does this form look like an extension function? In fact, it can be simply understood as an extension function of the generic type T (essentially, the object of T is obtained, but the work done by the compiler is different, which will be discussed below), so We can directly access the members of T, using like this.

R:

R is the receiver, receive what? Receive the T object, that is, the receiver object (that is, the object of T). Note that receivers and receiver objects are not the same thing. Let's put it this way, the receiver object (T object) is passed to the receiver , and using this inside the receiver means using the this of the receiver object T object , which is what the implicit this mentioned above means. However, it is our freedom to use this expression or not, and the final effect is that members of the T object can be accessed within the receiver without any additional qualifiers.

Going back to the above example, according to T.() -> R , we can write the following code:

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

fun Int.(other: Int): Int = this + other Is it like an extension function, with the same form and the same use of this.

However, this example does not reflect the essence that the receiver object is an implicit this, modify it as follows:

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

The other in the parameter can be removed, and the plus function can be called directly (this is the embodiment of implicit this), which is actually equivalent to the following code:

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

At this point, you can observe a detail. If you use the extension function, you can only use the following method in actual use:

println(1.sum(2))

But the lambda expression T.() -> R with receiver supports both:

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

Compare how they are written:

//扩展函数
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))

Why does the Lambda expression T.() -> R with receiver support sum(1,2) call like this, but the extension function does not? You still have to look at the decompilation and source code to understand.

First look at the extension function:

//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;
   }
}

It can be seen that there is an additional KotlinLambdaKt, and the original extension function sum becomes the static function sum of KotlinLambdaKt. Since it is a static function, it must not be called directly like sum(1,2), it must be KotlinLambdaKt.sum(1, 2). That is to say, the essence of the extension function is to generate a class named XXXKt, which contains a static function with the same name, and the first parameter in the static function is the object of the current call. That's why extension functions allow you to access members of the receiving object within the body of the function. It's not magic, it's the compiler's fault.

Therefore, if Java wants to call Kotlin's extension function, it needs to call the static function with the same name through the corresponding class:

KotlinLambdaKt.sum(1,2);

But again, the first parameter, the object of the extension function T, because of the basic type of Int, so it is no problem to pass in a value, but if it is an extended object of a custom class, then you need to pass in an instance of the class .

Look again

//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);
   }
}

What is this Function2? The source code is like this:

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

In fact, the official defines a lot of interfaces, from Function0 to Function22, which support 0-22 parameters respectively, just look at the source code for details. At this time, we know that the declaration of the Lambda expression is the Function interface, and the body of the Lambda expression is the implementation of the corresponding Function interface.

So it is obvious why the Lambda expression T.() -> R with receiver supports the following two forms of invocation.

//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);

See, 1.sum(2) is actually a syntactic sugar, decompiled into Java, it is the invoke method of calling the interface instance of Function, the input parameters are the parameters of the calling object itself and the extension function, and the return value is the extension function return value. The same is true for sum(1,2) .

Comparing the extension functions again, the difference between them is obvious.

extension function

The essence is a static function. The first parameter of the function is passed in the object of the caller, followed by the parameters of the extended function declaration.

Lambda expression

The essence is the interface of Function, and the parameters passed in by the invoke method of the interface are the parameters declared by the Lambda expression.

Lambda expression with receiver T.() -> R

The essence is the interface of Function. The first parameter passed in by the invoke method of the interface is the object of the caller, followed by the parameters declared by the Lambda expression. The so-called receiver is the invoke method of Function. The receiver object is the object of the caller T.

Attach the complete Kotlin and decompiled Java for your understanding:

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;
   }
}

Let's go on, some readers may have questions about this: (Function2)null.INSTANCE , why there is a null, because the caller is a basic data type. If it is another class, it is like this: Function2 sum = (Function1)(new Function2(){});

The old problem has been committed again. I spent a lot of space to introduce the lambda expression T.() -> R with a receiver . Back to the whole, how does the constructor pattern use this feature for transformation?

Define an inline build method in the half-life object of AlertDialog, as follows:

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

The key is the use of the function apply, because its argument is a Lambda expression with receiver T.() -> R and it returns T.this :

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

In this way, when the Builder is T, we can access the Builder.this in the block code block, thus simplifying the writing of the original chain call. Examples are as follows:

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

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

However, some readers may have doubts, is your method not more elegant? Isn't it okay for me to pass AlertDialog().apply{} directly? The effect is about the same.

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

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

Yes, including Builder, you can also use AlertDialog.Builder().apply{} directly. There is no need to engage in another build method.

Here are the three writing methods put together for your comparison:

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()

Which one do you prefer?

Let me talk about the author's understanding here. If starting from the perspective mode of the constructor mode, AlertDialog().apply{} only uses the characteristics of apply, not the implementation of the constructor mode. So the question is, do we really need the constructor pattern?

The answer is that it depends. First of all, we have to understand the definition of the constructor pattern and its usage scenarios and advantages and disadvantages. Then, decide whether to use the constructor mode according to actual needs. If the process of constructing an object is simple, direct apply is enough, and it is not necessary to use the constructor mode.

Of course, if the process of constructing an object is complex and often requires the construction of different objects, such as AlertDialog in Android, it is highly recommended to use the constructor mode.

However, although the design pattern is good, there is a point of view on the Internet, do not abuse the design pattern. The author deeply agrees with this point of view, including the six design principles. If you don't understand the reasons for the emergence of these design patterns, don't understand their usage scenarios, advantages and disadvantages, it is easy to abuse or even backfire.

One of the most direct examples is the singleton pattern, which can be seen everywhere in the project, and does not consider the usage scenario. We must know that once a singleton is initialized, it will occupy memory for a long time. We need to consider which objects can exist for a long time and which ones can be recycled when they are used up. In short, a better way is to consider it from the perspective of software engineering.

Another example is to apply mechanically without looking at the needs. For example, for the use of state mode, you can refer to the author's Android code refactoring series-01-Kotlin to realize the state machine . Different requirements have their own suitable ways to realize the state machine, and it is not necessary to use the state mode.

Considering the length of the article, it is difficult to write this article once and then publish it. Therefore, it will be updated irregularly. Appropriate dates will be added when updated. The problems in the article will be revised and republished in time, welcome to communicate.

Written at the end, first of all, thank you very much for your patience in reading the entire article. It is not easy to insist on writing original and practical articles. If this article happens to be helpful to you, you are welcome to like and comment on the article. Your encouragement is the author's insistence Unrelenting drive. Writing a blog is not only a good way to consolidate learning, but also an excellent opportunity to check for gaps and fill in gaps. If there is anything wrong with the article, you are welcome to correct it, thank you again.

References

Kotlin - Improved Builder Pattern

(Translation) Effective Kotlin Builder Mode (2)

Can you implement the builder pattern in Kotlin?

Guess you like

Origin blog.csdn.net/xiangang12202/article/details/129056077