KotlinKotlin 中使用 Lambda 表达式替代对象表达式原理分析 ( 尾随 Lambda - Trailing Lambda 语法 | 接口对象表达式 = 接口#函数类型对象 )

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了KotlinKotlin 中使用 Lambda 表达式替代对象表达式原理分析 ( 尾随 Lambda - Trailing Lambda 语法 | 接口对象表达式 = 接口#函数类型对象 )相关的知识,希望对你有一定的参考价值。


文章目录

  • ​​一、尾随 Lambda - Trailing Lambda 语法​​
  • ​​二、Kotlin 中使用 Lambda 表达式替代对象表达式原理​​
  • ​​1、Lambda 替换对象表达式​​
  • ​​2、原理分析​​
  • ​​3、示例分析​​






一、尾随 Lambda - Trailing Lambda 语法



尾随 Lambda - Trailing Lambda 语法 : 满足如下两个条件 , 可以 使用 尾随 Lambda 语法 ;

  • 函数作为参数 ,
  • 并且 该函数参数 是最后一个参数 ,

那么可以 将最后一个参数 放在 括号外面 , 括号外使用 Lambda 表达式作为参数 ;

使用 尾随 Lambda 语法 可以使代码 更简洁 , 提高代码可读性 ;



示例说明 :

下面的函数 的最后一个参数 是 函数类型 ;

fun foo(x: Int, f: (Int) -> Int) 
val result = f(x)
println(result)

调用 foo 函数时 , 第二个参数是函数类型 , 并且该参数是函数的最后一个参数 ,

那么可以 使用 尾随 Lambda 语法 , 将 函数参数 移到括号外面 使用 Lambda 表达式表示 ;



正常调用方式如下 : 函数参数 放在 括号内进行传递 ;

foo(5,  x -> x * x )

使用 尾随 Lambda 语法 的调用方式 : 将第二个函数参数提取到括号外面 , 也就是将 Lambda 表达式 写在括号的外部 ;

foo(5)  
x -> x * x






二、Kotlin 中使用 Lambda 表达式替代对象表达式原理




1、Lambda 替换对象表达式



在使用 Kotlin 开发时 , 经常遇到这种情况 , 最后一个函数是匿名内部类 , 匿名内部类中只实现了一个函数 , 此时使用 Lambda 表达式替代该 匿名内部类 ;

如 : 为按钮添加点击事件 , 对应的 Java 代码是

button.setOnClickListener(new View.OnClickListener() 
@Override
public void onClick(View view)
// 点击事件

);

翻译成 Kotlin 代码后为 : 使用对象表达式

button.setOnClickListener(object : View.OnClickListener 
override fun onClick(view: View)
// 点击事件

)

更进一步简写为 :

button.setOnClickListener 
// 点击事件



2、原理分析



Kotlin 中的 对象表达式 ,

object : View.OnClickListener 
override fun onClick(view: View)
// 点击事件

对应的就是 Java 中的 匿名内部类 ;

new View.OnClickListener() 
@Override
public void onClick(View view)
// 点击事件



Lambda 表达式 其本质 就是 函数类型 的 匿名对象 , 也是一个实例对象 , 在堆内存中分配相应的空间 ;

在下面的代码中 , 使用 对象表达式 创建了匿名对象 , 该匿名类实现了 View.OnClickListener 接口 , 并实现了其中的 onClick 函数 ;

object : View.OnClickListener 
override fun onClick(view: View)
// 点击事件



符合以下两个条件 :

  • 函数 接收一个 接口类型 的匿名内部类 或 对象表达式 ;
  • 该 接口类型 中 只定义了一个函数 ;

可以 省略掉 匿名内部类 也就是 对象表达式的定义 , 直接使用 接口中的函数 类型对象 , 也就是 Lambda 表达式 / 匿名函数 / 闭包 来替代该 接口类型 变量 ;



3、示例分析



View 组件设置 按钮点击事件 的函数原型如下 : View#setOnClickListener 函数 接收一个 OnClickListener 实例对象作为参数 ;

/**
* 注册一个回调,以便在单击此视图时调用。如果这个视图是不可点击的,它就变成了可点击的。
*
* @param l 将运行的回调
*
* @see #setClickable(boolean)
*/
public void setOnClickListener(@Nullable OnClickListener l)
if (!isClickable())
setClickable(true);

getListenerInfo().mOnClickListener = l;

而 OnClickListener 是一个接口 , 接口中只有一个方法 ;

/**
* 在单击视图时调用回调的接口定义。
*/
public interface OnClickListener
/**
* 当视图被单击时调用。
*
* @param v 被单击的视图。
*/
void onClick(View v);

下面是为 View 组件设置 点击事件 时 , 输入 setOnClickListener 出现的代码提示 ;

【Kotlin】Kotlin



View#setOnClickListener 可以设置两种参数 :

  • OnClickListener 类型的对象表达式 :
// 添加按钮点击事件 , 设置一个 对象表达式 表示 OnClickListener 子类
// 作为点击事件
textView.setOnClickListener(object : OnClickListener
override fun onClick(v: View?)
Log.i("TAG", "按钮点击事件")

)
  • OnClickListener#onClick 函数类型的 Lambda 表达式 :
// 添加按钮点击事件 , 设置一个 OnClickListener#onClick 函数类型的 Lambda 表达式
// 作为点击事件
textView.setOnClickListener
Log.i("TAG", "按钮点击事件")



如果设置了 OnClickListener#onClick 函数类型的 Lambda 表达式 , 其 Lambda 表达式类型为

(View) -> Unit

传入 View 类型实例对象 , 返回值为 void , 对应的就是 OnClickListener 接口中的 ​​void onClick(View v);​​ 函数类型 ;



如果 在 setOnClickListener 函数中设置了 (View) -> Unit 类型的 Lambda 表达式 ,

Kotlin 编译时会查找 setOnClickListener 函数真正接收的是 OnClickListener 接口实例对象 ,

传入一个 Lambda 表达式 , 会自动为其创建 OnClickListener 接口对应的匿名内部类 ,

并且将该 Lambda 表达式作为该匿名内部类的函数实体 ;



如果 传入的 Lambda 表达式类型 , 不符合 接口中的唯一的函数类型 ,

也就是再该示例中 Lambda 表达式类型不是 ​​(View) -> Unit​​ 类型的 , 就会在编译时报错 ,

报错信息如下 :

  • 返回值设置错误 : 设置错误的返回值 , 会提示
return is not allowed here

【Kotlin】Kotlin

  • 参数设置错误 : 默认参数是 ​​it:View!​​​ , 如果设置成​​ it:String​​ , 就会报如下错误 ;
Type mismatch.
Required:
((View!) → Unit)?
Found:
(String) → Unit
Expected parameter of type View!

【Kotlin】Kotlin


KotlinKotlin 与 Java 互操作 ② ( @JvmField 注解字段给 Java | @JvmOverloads 注解修饰函数 | @JvmStatic 注解声明静态成员 )

文章目录





一、使用 @JvmField 注解暴露 Kotlin 字段给 Java




1、Java 类中通过 Getter 和 Setter 方法访问 Kotlin 字段


在 Java 中是 不能直接访问 Kotlin 中的字段 的 , 必须 调用相应的 Getter 和 Setter 方法 , 才能进行访问 ;


代码示例 :


Kotlin 类 : 在 Kotlin 中声明的成员属性 , 默认就是 private 私有属性 , 默认为其生成了 Getter 和 Setter 方法 ;

class Hello 
    var name = "Tom"


Java 类直接调用 : 在 Java 类中 , 不能直接调用 Kotlin 字段 ;

public class HelloJava 
    public static void main(String[] args) 
        Hello hello = new Hello();
        System.out.println(hello.name);
    

在 Java 类中会报错 :

'name' has private access in 'Hello'


在 Java 类中 , 只能通过 Getter 和 Setter 方法 , 调用 Kotlin 字段 ;

public class HelloJava 
    public static void main(String[] args) 
        Hello hello = new Hello();
        System.out.println(hello.getName());
    

执行结果 :


2、Java 类中直接访问被 @JvmField 注解修饰的 Kotlin 字段


如果在 Kotlin 中 , 使用 @JvmField 注解 修饰 成员属性 , 其作用是将 Kotlin 字段暴露给 Java , 在 Java 中可以不使用 Getter 和 Setter 方法 而直接访问 Kotlin 字段 ;


Kotlin 代码 :

class Hello 
    @JvmField
    var name = "Tom"

Java 代码 :

public class HelloJava 
    public static void main(String[] args) 
        Hello hello = new Hello();
        System.out.println(hello.name);
    

执行结果 :

@JvmField 注解 相当于 将 Kotlin 中的字段声明为 Java 字段 , 此时 Kotlin 不会为该字段自动生成 Getter 和 Setter 方法 ;





二、使用 @JvmOverloads 注解修饰 Kotlin 函数



在 Kotlin 中 , 函数参数 可以 自带默认值 , 调用时可以 直接传入 想要的参数即可 ;

但是在 Java 调用 Kotlin 函数 中 , Java 语言不支持 函数参数 自带默认值的 语法 , 如果传入指定的参数 , 就需要对函数进行重载 ;

在 Kotlin 中 使用 @JvmOverloads 注解修饰 Kotlin 函数 , 会自动 为 Java 用户实现 一系列的 重载函数 ;
如 : 参数列表是 ( String , age ) , 使用 @JvmOverloads 注解修饰该函数 , 会自动生成

  • 0 个参数 ,
  • 1 个参数 ,
  • 2 个参数

的函数 ;


1、Kotlin 默认参数函数调用示例


Kotlin 代码示例 : 在下面的 helloStudent 函数中 , 两个参数都设置了默认参数值 , Kotlin 中调用该函数 , 可以传入 0 , 1 , 2 个参数 , 其中传入 1 个参数还可以选择传入哪个参数 ;

class Hello 
    fun helloStudent(name: String = "Tom", age: Int = 18) 
        println("Student $name is $age years old , say hello !")
    


fun main() 
    var hello = Hello();
    hello.helloStudent()
    hello.helloStudent("Jerry")
    hello.helloStudent(age = 22)
    hello.helloStudent("Bill", 12)

执行结果 :

Student Tom is 18 years old , say hello !
Student Jerry is 18 years old , say hello !
Student Tom is 22 years old , say hello !
Student Bill is 12 years old , say hello !

分析上述 Kotlin 代码的字节码数据 , 在 Kotlin Bytecode 中查看字节码数据 , 反编译成 Java 代码内容如下 :

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

@Metadata(
   mv = 1, 1, 16,
   bv = 1, 0, 3,
   k = 1,
   d1 = "\\u0000\\u001e\\n\\u0002\\u0018\\u0002\\n\\u0002\\u0010\\u0000\\n\\u0002\\b\\u0002\\n\\u0002\\u0010\\u0002\\n\\u0000\\n\\u0002\\u0010\\u000e\\n\\u0000\\n\\u0002\\u0010\\b\\n\\u0000\\u0018\\u00002\\u00020\\u0001B\\u0005¢\\u0006\\u0002\\u0010\\u0002J\\u001a\\u0010\\u0003\\u001a\\u00020\\u00042\\b\\b\\u0002\\u0010\\u0005\\u001a\\u00020\\u00062\\b\\b\\u0002\\u0010\\u0007\\u001a\\u00020\\b¨\\u0006\\t",
   d2 = "LHello;", "", "()V", "helloStudent", "", "name", "", "age", "", "KotlinDemo"
)
public final class Hello 
   public final void helloStudent(@NotNull String name, int age) 
      Intrinsics.checkParameterIsNotNull(name, "name");
      String var3 = "Student " + name + " is " + age + " years old , say hello !";
      boolean var4 = false;
      System.out.println(var3);
   

   // $FF: synthetic method
   public static void helloStudent$default(Hello var0, String var1, int var2, int var3, Object var4) 
      if ((var3 & 1) != 0) 
         var1 = "Tom";
      

      if ((var3 & 2) != 0) 
         var2 = 18;
      

      var0.helloStudent(var1, var2);
   

// HelloKt.java
import kotlin.Metadata;

@Metadata(
   mv = 1, 1, 16,
   bv = 1, 0, 3,
   k = 2,
   d1 = "\\u0000\\b\\n\\u0000\\n\\u0002\\u0010\\u0002\\n\\u0000\\u001a\\u0006\\u0010\\u0000\\u001a\\u00020\\u0001¨\\u0006\\u0002",
   d2 = "main", "", "KotlinDemo"
)
public final class HelloKt 
   public static final void main() 
      Hello hello = new Hello();
      Hello.helloStudent$default(hello, (String)null, 0, 3, (Object)null);
      Hello.helloStudent$default(hello, "Jerry", 0, 2, (Object)null);
      Hello.helloStudent$default(hello, (String)null, 22, 1, (Object)null);
      hello.helloStudent("Bill", 12);
   

   // $FF: synthetic method
   public static void main(String[] var0) 
      main();
   


2、Java 中调用 Kotlin 默认参数函数


如果 在 Java 代码中 , 想要 像 Kotlin 那样传入任意个数和类型的参数 , 就需要使用 函数重载实现 ;

如果 直接像 Kotlin 中那样调用 , 肯定会报错 :

使用 @JvmOverloads 注解修饰 Kotlin 函数 , 会自动为 Java 用户实现 一系列的 重载函数 ;


Kotlin 代码示例 :

class Hello 
    @JvmOverloads
    fun helloStudent(name: String = "Tom", age: Int = 18) 
        println("Student $name is $age years old , say hello !")
    


fun main() 
    var hello = Hello();
    hello.helloStudent()
    hello.helloStudent("Jerry")
    hello.helloStudent(age = 22)
    hello.helloStudent("Bill", 12)

Java 代码示例 :

public class HelloJava 
    public static void main(String[] args) 
        Hello hello = new Hello();
        hello.helloStudent();
        hello.helloStudent("Jerry");
        hello.helloStudent("Bill", 12);
    

执行结果 :

Student Tom is 18 years old , say hello !
Student Jerry is 18 years old , say hello !
Student Bill is 12 years old , say hello !



分析上述 使用了 @JvmOverloads 注解 的 Kotlin 类对应的字节码数据 , 将字节码反编译回 Java 代码 , 内容如下 :

// Hello.java
import kotlin.Metadata;
import kotlin.jvm.JvmOverloads;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = 1, 1, 16,
   bv = 1, 0, 3,
   k = 1,
   d1 = "\\u0000\\u001e\\n\\u0002\\u0018\\u0002\\n\\u0002\\u0010\\u0000\\n\\u0002\\b\\u0002\\n\\u0002\\u0010\\u0002\\n\\u0000\\n\\u0002\\u0010\\u000e\\n\\u0000\\n\\u0002\\u0010\\b\\n\\u0000\\u0018\\u00002\\u00020\\u0001B\\u0005¢\\u0006\\u0002\\u0010\\u0002J\\u001c\\u0010\\u0003\\u001a\\u00020\\u00042\\b\\b\\u0002\\u0010\\u0005\\u001a\\u00020\\u00062\\b\\b\\u0002\\u0010\\u0007\\u001a\\u00020\\bH\\u0007¨\\u0006\\t",
   d2 = "LHello;", "", "()V", "helloStudent", "", "name", "", "age", "", "KotlinDemo"
)
public final class Hello 
   @JvmOverloads
   public final void helloStudent(@NotNull String name, int age) 
      Intrinsics.checkParameterIsNotNull(name, "name");
      String var3 = "Student " + name + " is " + age + " years old , say hello !";
      boolean var4 = false;
      System.out.println(var3);
   

   // $FF: synthetic method
   public static void helloStudent$default(Hello var0, String var1, int var2, int var3, Object var4) 
      if ((var3 & 1) != 0) 
         var1 = "Tom";
      

      if ((var3 & 2) != 0) 
         var2 = 18;
      

      var0.helloStudent(var1, var2);
   

   @JvmOverloads
   public final void helloStudent(@NotNull String name) 
      helloStudent$default(this, name, 0, 2, (Object)null);
   

   @JvmOverloads
   public final void helloStudent() 
      helloStudent$default(this, (String)null, 0, 3, (Object)null);
   

// HelloKt.java
import kotlin.Metadata;

@Metadata(
   mv = 1, 1, 16,
   bv = 1, 0, 3,
   k = 2,
   d1 = "\\u0000\\b\\n\\u0000\\n\\u0002\\u0010\\u0002\\n\\u0000\\u001a\\u0006\\u0010\\u0000\\u001a\\u00020\\u0001¨\\u0006\\u0002",
   d2 = "main", "", "KotlinDemo"
)
public final class HelloKt 
   public static final void main() 
      Hello hello = new Hello();
      Hello.helloStudent$default(hello, (String)null, 0, 3, (Object)null);
      Hello.helloStudent$default(hello, "Jerry", 0, 2, (Object)null);
      Hello.helloStudent$default(hello, (String)null, 22, 1, (Object)null);
      hello.helloStudent("Bill", 12);
   

   // $FF: synthetic method
   public static void main(String[] var0) 
      main();
   

使用了 @JvmOverloads 注解后 ,
在编译时 , 自动为 helloStudent 函数 , 生成了 0 , 1, 2 个参数的重载函数 ,
这样在 Java 中调用时 , 可以直接调用这些方法 ;

   @JvmOverloads
   public final void helloStudent(@NotNull String name, int age) 
      Intrinsics.checkParameterIsNotNull(name, "name");
      String var3 = "Student " + name + " is " + age + " years old , say hello !";
      boolean var4 = false;
      System.out.println(var3);
   

   @JvmOverloads
   public final void helloStudent(@NotNull String name) 
      helloStudent$default(this, name, 0, 2, (Object)null);
   

   @JvmOverloads
   public final void helloStudent() 
      helloStudent$default(this, (String)null, 0, 3, (Object)null);
   




三、使用 @JvmStatic 注解声明静态成员



在 Kotlin 中 , 没有静态成员概念 , 需要声明静态成员时 , 一般都在其 Companion 伴生对象中声明 ;

在 Java 中 调用 Kotlin 的 Companion 伴生对象 中的成员时 , 需要通过如下形式进行调用 :

Kotlin.Companion.成员属性
Kotlin.Companion.成员函数

如果想要 在不使用 Companion 的前提下 直接调用 Kotlin 中的 Companion 伴生对象 成员 ,

可以 在 companion object 中 ,

使用 @JvmStatic 注解 将伴生对象中的成员 声明 为 Java 静态成员 ,

Java 中可以按照静态成员的方式进行访问 ;


1、Java 正常访问 Kotlin 伴生对象成员


在下面的代码中 , 在 Java 语言中访问 Kotlin 伴生对象成员 , 需要先获取 Hello.Companion 类的伴生对象 , 然后再访问 伴生对象 中的成员 ;


Kotlin 代码 :

class Hello 
    companion object 
        var name = "Tom"
        fun say() 
            println("Hello World")
        
    

Java 代码 :

public class HelloJava 
    public static void main(String[] args) 
        System.out.println(Hello.Companion.getName());
        Hello.Companion.say();
    

执行结果 :

Tom
Hello World


查看该 Kotlin 类生成的字节码 反编译 的 Java 代码 :

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

@Metadata(
   mv = 1, 1, 16,
   bv = 1, 0, 3,
   k = 1,
   d1 = "\\u0000\\f\\n\\u0002\\u0018\\u0002\\n\\u0002\\u0010\\u0000\\n\\u0002\\b\\u0003\\u0018\\u0000 \\u00032\\u00020\\u0001:\\u0001\\u0003B\\u0005¢\\u0006\\u0002\\u0010\\u0002¨\\u0006\\u0004",
   d2 = "LHello;", "", "()V", "Companion", "KotlinDemo"
)
public KotlinKotlin 函数总结 ( 具名函数 | 匿名函数 | Lambda 表达式 | 闭包 | 内联函数 | 函数引用 )

KotlinKotlin 函数总结 ( 具名函数 | 匿名函数 | Lambda 表达式 | 闭包 | 内联函数 | 函数引用 )

Kotlin学习

KotlinKotlin函数那么多,你会几个?

KotlinKotlin 与 Java 互操作 ② ( @JvmField 注解字段给 Java | @JvmOverloads 注解修饰函数 | @JvmStatic 注解声明静态成员 )

Kotlinkotlin中的空指针检查