草庐IT

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

韩曙亮 2023-04-10 原文

文章目录


我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=3e60fnr5m3uo0





一、尾随 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);
    }

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


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

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

有关【Kotlin】Kotlin 中使用 Lambda 表达式替代对象表达式原理分析 ( 尾随 Lambda - Trailing Lambda 语法 | 接口对象表达式 = 接口#函数类型对象 )的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  3. ruby - 树顶语法无限循环 - 2

    我脑子里浮现出一些关于一种新编程语言的想法,所以我想我会尝试实现它。一位friend建议我尝试使用Treetop(Rubygem)来创建一个解析器。Treetop的文档很少,我以前从未做过这种事情。我的解析器表现得好像有一个无限循环,但没有堆栈跟踪;事实证明很难追踪到。有人可以指出入门级解析/AST指南的方向吗?我真的需要一些列出规则、常见用法等的东西来使用像Treetop这样的工具。我的语法分析器在GitHub上,以防有人希望帮助我改进它。class{initialize=lambda(name){receiver.name=name}greet=lambda{IO.puts("He

  4. ruby 正则表达式 - 如何替换字符串中匹配项的第 n 个实例 - 2

    在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg

  5. ruby-on-rails - 如何验证非模型(甚至非对象)字段 - 2

    我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss

  6. Ruby 写入和读取对象到文件 - 2

    好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信

  7. ruby - Infinity 和 NaN 的类型是什么? - 2

    我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

  8. ruby - 检查方法参数的类型 - 2

    我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)

  9. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  10. ruby-on-rails - 使用 Sublime Text 3 突出显示 HTML 背景语法中的 ERB? - 2

    所以我在关注Railscast,我注意到在html.erb文件中,ruby代码有一个微弱的背景高亮效果,以区别于其他代码HTML文档。我知道Ryan使用TextMate。我正在使用SublimeText3。我怎样才能达到同样的效果?谢谢! 最佳答案 为SublimeText安装ERB包。假设您安装了SublimeText包管理器*,只需点击cmd+shift+P即可获得命令菜单,然后键入installpackage并选择PackageControl:InstallPackage获取包管理器菜单。在该菜单中,键入ERB并在看到包时选择

随机推荐