Série de refatoração de código Android-03-Se você usa Kotlin, pare de escrever código com pensamento Java (atualizado continuamente)

prefácio

Ainda me lembro que quando comecei a escrever código em Kotlin em projetos oficiais, muito código foi copiado diretamente de Java e convertido em Kotlin. Como resultado, quando o código foi revisado, foi comentado que o código Kotlin foi escrito com Java pensando, e os recursos do Kotlin não foram usados. . Então, como sabemos como o código Java pode ser reescrito com recursos Kotlin? Não se preocupe, este artigo registra e analisa o código correto no estilo Kotlin do autor no projeto real da perspectiva do código-fonte, para ajudá-lo a evitar desvios.

Obviamente, o autor recomenda ler mais documentos oficiais, mais código-fonte e mais código Java compilado por Kotlin para aprofundar a compreensão, de modo a realmente dominar o Kotlin. Afinal, o que se consegue no papel é sempre superficial, e sei que isso deve ser feito na prática! Às vezes, pode até ser autodestrutivo se você não o conhecer bem.

se mais

Determine se o objeto é nulo e, se não for nulo, chame o método do objeto.

Método de escrita Java:

private Handler handler;

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

Método de escrita Kotlin:

private val handler: Handler? = null

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

Determine se o objeto é nulo e inicialize-o se não for nulo.

Método de escrita Java:

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

Método de escrita Kotlin:

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

Como julgar se o objeto é nulo ou não.

Método de escrita Java:

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

Método de escrita 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")
}

Observe que alguns artigos na Internet são escritos assim:

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

Isso está errado, vamos ver o código-fonte de 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 retorna block(this), o que significa que o valor de retorno da função pode ser nulo Desta forma, o ?: let no método de escrita acima será executado porque ?.let retorna null. Vamos testá-lo:

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

vire para fora:

handler not null 4
handler null 4

Mas por que não usar ?.apply? Vejamos o código-fonte do apply:

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

apply retorna isso, que é o próprio T, e ?.apply executa o código de apply somente quando T não é nulo, então o lado direito de ?: não é satisfeito, ou seja, executa ?: quando T é nulo O código atrás, não executa o código de ?.apply for null.

trocar

switch coopera com a enumeração.

Código 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;
    }
}

Ou 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 pode usar classes de enumeração:

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

A vantagem de usar classes seladas em vez de enumerações é que, ao usar when, o compilador nos avisará que há ramificações ausentes que não foram processadas.

Veja o código Java compilado na seguinte classe selada:

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

Não é apenas se mais? No entanto, o compilador nos ajudou a concluir esta parte do trabalho tedioso, então digamos que não é perfumado.

padrão de construtor

Estamos familiarizados com a construção deste modo. AlertDialog usa este modo. Vamos dar uma olhada no código-fonte simplificado:

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

    }

}

Se você usar Kotlin ou escrever este código com pensamento Java, é equivalente a uma tradução literal com Kotlin. Observe o código após a conversão do Java para 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
        }
    }
}

Inesperadamente, a versão Kotlin tem apenas 58 linhas de código, quase a metade do Java, e o açúcar da sintaxe Kotlin realmente faz jus à sua reputação. No entanto, o código acima não é um padrão de construtor tradicional. Para obter detalhes, consulte o padrão de projeto do construtor . Afastando-se, voltando ao assunto.

Então, como usar os recursos do Kotlin para escrever um código mais elegante? Isso é para utilizar literais de função Kotlin com Receiver . A documentação oficial é a seguinte:

Literais de função com receptorTipos
de função com receptor, como T.() -> R , podem ser instanciados com uma forma especial de literais de função – literais de função com receptor.

Conforme mencionado acima, o Kotlin fornece a capacidade de chamar uma instância de um tipo de função com o receptor enquanto fornece o objeto receptor.

Dentro do corpo do literal de função, o objeto receptor passado para uma chamada torna-se um this implícito, para que você possa acessar os membros desse objeto receptor sem nenhum qualificador adicional ou acessar o objeto receptor usando uma expressão this.

Esse comportamento é semelhante ao das funções de extensão, que também permitem acessar os membros do objeto receptor dentro do corpo da função.
Tradução automática:
literais de função
com receptores Tipos de função com receptores, como T.( ) -> R , que pode ser instanciado com uma forma especial de literal de função — um literal de função com receptor .

Conforme mencionado acima, o Kotlin oferece a capacidade de chamar uma instância de um tipo de função com um receiver , enquanto fornece o objeto receiver .

No corpo do literal de função, o objeto receptor passado para o chamador torna-se um this implícito , portanto, você pode acessar membros do objeto receptor sem nenhum qualificador adicional ou usar a expressão this para acessar o objeto de dispositivo receptor.

Esse comportamento é semelhante ao das funções de extensão, que também permitem acessar membros do objeto receptor dentro do corpo da função .

Os leitores que podem ser expostos a este gadget pela primeira vez podem não entendê-lo. Em primeiro lugar, é uma expressão Lambda . Se você não entende as expressões Lambda, pode ler o artigo do autor: Entendimento profundo das expressões Java Lambda, funções anônimas e fechamentos . No entanto, a expressão Lambda de Kotlin e a expressão Lambda de Java não são a mesma coisa. Para obter detalhes, consulte a expressão Lambda de Kotlin de Throwing Boss . A maioria das pessoas nem mesmo conta o fur .

Expressões lambda e funções anônimas são literais de função.
Tradução: expressões lambda e funções anônimas são literais ( literais de função )
da documentação oficial: https://kotlinlang.org/docs/lambdas.html#lambda-expressions-and-anonymous-functions

Dito tudo isso, o que essa expressão lambda T.() -> R com receiver faz? Não se preocupe, vamos primeiro entender como funciona, por exemplo:

Por exemplo, para implementar uma expressão para somar dois números, normalmente é escrito assim:

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

Podemos até simplificá-lo para:

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

Embora simplificado, com base na ideia de programação funcional, podemos transformá-lo ainda mais. Neste momento, podemos escrever uma função de extensão de soma:

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

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

A expressão Lambda é entendida, então o que o receptor diz? Na verdade, como mencionado anteriormente, no corpo da função literal, o objeto receptor passado para o chamador torna-se um this implícito , para que você possa acessar os membros do objeto receptor sem nenhum qualificador adicional, ou O objeto receptor é acessado usando o método esta expressão.

Veja essa expressão Lambda T.() -> R com um receiver, dividimos em duas partes para entender:

T.():

Essa forma parece uma função de extensão? Na verdade, ela pode ser simplesmente entendida como uma função de extensão do tipo genérico T (essencialmente, o objeto de T é obtido, mas o trabalho feito pelo compilador é diferente, o que será discutido abaixo), para que possamos acessar diretamente os membros do T, usando assim.

R:

R é o receptor, recebe o quê? Receba o objeto T, ou seja, o objeto receptor (ou seja, o objeto de T). Observe que receptores e objetos receptores não são a mesma coisa. Vamos colocar desta forma, o objeto receptor (objeto T) é passado para o receptor , e usar isso dentro do receptor significa usar o this do objeto receptor T object , que é o que significa o this implícito mencionado acima. No entanto, é nossa liberdade usar essa expressão ou não, e o efeito final é que os membros do objeto T podem ser acessados ​​dentro do receptor sem quaisquer qualificadores adicionais.

Voltando ao exemplo acima, de acordo com T.() -> R , podemos escrever o seguinte código:

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

fun Int.(outros: Int): Int = this + other É como uma função de extensão, com a mesma forma e o mesmo uso de this.

No entanto, este exemplo não reflete a essência de que o objeto receptor é um this implícito, modifique-o da seguinte maneira:

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

O outro no parâmetro pode ser removido e a função plus pode ser chamada diretamente (esta é a personificação do implícito this), que é realmente equivalente ao seguinte código:

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

Neste ponto, você pode observar um detalhe. Se você usar a função de extensão, poderá usar apenas o seguinte método em uso real:

println(1.sum(2))

Mas a expressão lambda T.() -> R com receptor suporta ambos:

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

Compare como eles são escritos:

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

Por que a expressão Lambda T.() -> R com receptor suporta sum(1,2) chamada assim, mas a função de extensão não? Você ainda precisa examinar a descompilação e o código-fonte para entender.

Primeiro olhe para a função de extensão:

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

Pode-se ver que há um KotlinLambdaKt adicional, e a soma da função de extensão original torna-se a soma da função estática de KotlinLambdaKt. Como é uma função estática, não deve ser chamada diretamente como sum(1,2), deve ser KotlinLambdaKt.sum(1, 2). Ou seja, a essência da função de extensão é gerar uma classe chamada XXXKt, que contém uma função estática com o mesmo nome, e o primeiro parâmetro na função estática é o objeto da chamada atual. É por isso que as funções de extensão permitem que você acesse membros do objeto receptor dentro do corpo da função. Não é mágica, é culpa do compilador.

Portanto, se o Java quiser chamar a função de extensão do Kotlin, ele precisa chamar a função estática com o mesmo nome por meio da classe correspondente:

KotlinLambdaKt.sum(1,2);

Mas, novamente, o primeiro parâmetro, o objeto da função de extensão T, por causa do tipo básico de Int, portanto, não há problema em passar um valor, mas se for um objeto estendido de uma classe personalizada, você precisará passar em uma instância da classe.

Olhe novamente

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

O que é esta Function2? O código fonte é assim:

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
}

Na verdade, o oficial define muitas interfaces, de Function0 a Function22, que suportam 0-22 parâmetros respectivamente, basta olhar o código-fonte para obter detalhes. Neste momento, sabemos que a declaração da expressão Lambda é a interface Function, e o corpo da expressão Lambda é a implementação da interface Function correspondente.

Portanto, é óbvio por que a expressão Lambda T.() -> R with receiver oferece suporte às duas formas de invocação a seguir.

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

Veja, 1.sum(2) é na verdade um açúcar sintático, descompilado em Java, é o método de chamada da instância de interface de Function, os parâmetros de entrada são os parâmetros do próprio objeto de chamada e a função de extensão, e o retorno value é o valor de retorno da função de extensão. O mesmo vale para sum(1,2) .

Comparando as funções de extensão novamente, a diferença entre elas é óbvia.

função de extensão

A essência é uma função estática.O primeiro parâmetro da função é passado no objeto do chamador, seguido pelos parâmetros da declaração da função estendida.

expressão lambda

A essência é a interface de Function e os parâmetros passados ​​pelo método de chamada da interface são os parâmetros declarados pela expressão Lambda.

Expressão lambda com receptor T.() -> R

A essência é a interface de Function.O primeiro parâmetro passado pelo método de chamada da interface é o objeto do chamador, seguido pelos parâmetros declarados pela expressão Lambda. O chamado receptor é o método de chamada de Function. O objeto receptor é o objeto do chamador T.

Anexe o Kotlin completo e o Java descompilado para sua compreensão:

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

Vamos em frente, alguns leitores podem ter dúvidas sobre isso: (Function2)null.INSTANCE , porque existe um null, porque o chamador é um tipo de dado básico. Se for outra classe, fica assim: Function2 sum = (Function1)(new Function2(){});

O antigo problema foi cometido novamente. Gastei muito espaço para introduzir a expressão lambda T.() -> R com um receptor . Voltando ao todo, como o padrão construtor usa esse recurso para transformação?

Defina um método de construção embutido no objeto de meia-vida de AlertDialog, da seguinte maneira:

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

A chave é o uso da função apply, pois seu argumento é uma expressão Lambda com receiver T.() -> R e retorna T.this :

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

Desta forma, quando o Builder for T, podemos acessar o Builder.this no bloco de código do bloco, simplificando assim a escrita da chamada em cadeia original. Os exemplos são os seguintes:

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

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

No entanto, alguns leitores podem ter dúvidas, seu método não é mais elegante? Posso passar AlertDialog().apply{} diretamente? O efeito é quase o mesmo.

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

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

Sim, incluindo o Builder, você também pode usar AlertDialog.Builder().apply{} diretamente. Não há necessidade de se envolver em outro método de construção.

Aqui estão os três métodos de escrita reunidos para sua comparação:

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

Qual você prefere?

Deixe-me falar sobre o entendimento do autor aqui. Se iniciar no modo de perspectiva do modo construtor, AlertDialog().apply{} usa apenas as características de apply, não a implementação do modo construtor. Então a questão é, nós realmente precisamos do padrão do construtor?

A resposta é que depende. Antes de tudo, temos que entender a definição do padrão construtor e seus cenários de uso e vantagens e desvantagens. Em seguida, decida se deseja usar o modo construtor de acordo com as necessidades reais. Se o processo de construção de um objeto for simples, a aplicação direta é suficiente, não sendo necessário o uso do modo construtor.

Claro, se o processo de construção de um objeto é complexo e muitas vezes requer a construção de diferentes objetos, como AlertDialog no Android, é altamente recomendável usar o modo construtor.

No entanto, embora o padrão de design seja bom, existe um ponto de vista na Internet, não abuse do padrão de design. O autor concorda profundamente com este ponto de vista, incluindo os seis princípios de design. Se você não entender os motivos do surgimento desses padrões de design, não entender seus cenários de uso, vantagens e desvantagens, é fácil abusar ou até sair pela culatra.

Um dos exemplos mais diretos é o padrão singleton, que pode ser visto em todo o projeto e não considera o cenário de uso. Devemos saber que uma vez que um singleton é inicializado, ele ocupará a memória por muito tempo, precisamos considerar quais objetos podem existir por muito tempo e quais podem ser reciclados quando se esgotarem. Resumindo, a melhor maneira é considerá-lo sob a perspectiva da engenharia de software.

Outro exemplo é aplicar mecanicamente sem olhar para as necessidades. Por exemplo, para o uso do modo de estado, você pode consultar a série de refatoração de código Android do autor-01-Kotlin para realizar a máquina de estado . Diferentes requisitos têm suas próprias maneiras adequadas de realizar a máquina de estado e não é necessário usar o modo de estado.

Considerando a extensão do artigo, é difícil escrever este artigo uma vez e depois publicá-lo. Portanto, será atualizado de forma irregular. As datas apropriadas serão adicionadas quando atualizadas. Os problemas do artigo serão revisados ​​e republicados a tempo, bem-vindo ao comunicar.

Escrito no final, antes de tudo, muito obrigado por sua paciência em ler todo o artigo. Não é fácil insistir em escrever artigos originais e práticos. Se este artigo for útil para você, você pode gostar e comente o artigo.Seu encorajamento é a insistência do autor Impulso implacável. Escrever um blog não é apenas uma boa maneira de consolidar o aprendizado, mas também uma excelente oportunidade para verificar se há lacunas e preenchê-las.Se houver algo errado com o artigo, você pode corrigi-lo, obrigado novamente.

Referências

Kotlin - padrão de construtor aprimorado

(Tradução) Modo Kotlin Builder eficaz (2)

Você pode implementar o padrão do construtor em Kotlin?

Acho que você gosta

Origin blog.csdn.net/xiangang12202/article/details/129056077
Recomendado
Clasificación