INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
INVOKEINTERFACE java/util/List.set (ILjava/lang/Object;)Ljava/lang/Object;
POP
L4
LINENUMBER 11 L4
RETURN
L5
LOCALVARIABLE tmp I L2 L5 3
LOCALVARIABLE $receiver Ljava/util/List; L0 L5 0
LOCALVARIABLE index1 I L0 L5 1
LOCALVARIABLE index2 I L0 L5 2
MAXSTACK = 4
MAXLOCALS = 4
@Lkotlin/Metadata;(mv={1, 1, 7}, bv={1, 0, 2}, k=2, d1={“\u0000\u0012\n\u0000\n\u0002\u0010\u0002\n\u0002\u0010!\n\u0002\u0010\u0008\n\u0002\u0008\u0003\u001a \u0010\u0000\u001a\u00020\u0001*\u0008\u0012\u0004\u0012\u00020\u00030\u00022\u0006\u0010\u0004\u001a\u00020\u00032\u0006\u0010\u0005\u001a\u00020\u0003\u00a8\u0006\u0006”}, d2={“swap”, “”, “”, “”, “index1”, “index2”, “production sources for module app”})
// compiled from: MutableListDemo.kt
}
// ================META-INF/production sources for module app.kotlin_module =================
这里的字节码已经相当直观,更令人惊喜的是Android Studio还具备将字节码转为JAVA文件的能力,点击上面的Decompile按钮,可以得到如下JAVA代码:
import java.util.List;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
@Metadata(
mv = {1, 1, 7},
bv = {1, 0, 2},
k = 2,
d1 = {“\u0000\u0012\n\u0000\n\u0002\u0010\u0002\n\u0002\u0010!\n\u0002\u0010\b\n\u0002\b\u0003\u001a \u0010\u0000\u001a\u00020\u0001*\b\u0012\u0004\u0012\u00020\u00030\u00022\u0006\u0010\u0004\u001a\u00020\u00032\u0006\u0010\u0005\u001a\u00020\u0003¨\u0006\u0006”},
d2 = {“swap”, “”, “”, “”, “index1”, “index2”, “production sources for module app”}
)
public final class MutableListDemoKt {
public static final void swap(@NotNull List KaTeX parse error: Expected '}', got 'EOF' at end of input: …meterIsNotNull(receiver, "
r
e
c
e
i
v
e
r
"
)
;
i
n
t
t
m
p
=
(
(
N
u
m
b
e
r
)
receiver"); int tmp = ((Number)
receiver");inttmp=((Number)receiver.get(index1)).intValue();
$receiver.set(index1, $receiver.get(index2));
$receiver.set(index2, Integer.valueOf(tmp));
}
}
从得到的JAVA文件分析,扩展函数的实现非常简单,它没有修改接受者类型的成员,仅仅是通过静态方法来实现的。这样,我们虽然不必担心扩展函数会带来额外的性能消耗,但是它也不会带来性能上的优化。
3.更复杂的情况
下面来讨论一些更特殊的情况。
3.1 当发生继承时,扩展函数由于本质上是静态方法,它会严格按照参数类型去执行调用,而不会去优先执行或者主动执行父类的方法,如下的例子所示:
open class A
class B:A()
fun A.foo() = “a”
fun B.foo() = “b”
fun printFoo(a:A){
println(a.foo())
}
println(B())
上述例子的输出结果是a,因为扩展函数的入参类型是A,他将会严格按照入参类型执行函数调用。
3.2 如果扩展函数和现有的类成员发生冲突,kotlin将会默认使用类成员,这一步选择是在编译期处理的,生成的字节码是将会是调用类成员的方法,如下例子:
class C{
fun foo() {println(“Member”)}
}
fun C.foo() {println(“Extension”)}
println(C().foo())
上述的例子将会输出Member
。Kotlin不允许扩展一个已有的成员,原因也很好理解,我们不希望扩展函数成为调用三方sdk的漏洞,不过如果你试图使用重载的方式创建扩展函数,这样是可行的。
3.3 Kotlin严格区分了可能为空和不为空的入参类型,同样也应用在扩展函数的中,为了声明一个可能为空的接受者类型,可以参考如下例子:
fun MutableList?.swap(index1:Int,index2:Int){
if(this == null){
println(null)
return
}
val tmp = this[index1]
this[index1] = this[index2]
this[index2] = tmp
}
3.4 我们有时候还希望能够添加类似JAVA的“静态函数”的扩展函数,这时需要借助“伴随对象(Companion Object)”来实现,如下这个例子:
class D{
companion object{
val m = 1
}
}
fun D.Companion.foo(){
println(“$m in extension”)
}
D.foo()
上面的例子会输出1 in extension
,注意这里调用foo这个扩展函数时,并不需要类D的实例,类似于JAVA的静态方法。
3.5 如果留意前面的例子,我们会发现kotlin的this
语法和JAVA不同,使用范围更灵活,仅以扩展函数为例,当在扩展函数里调用this
时,指代的是接受者类型的实例,那么如果这个扩展函数声明在一个类内部,我们如何通过this
获取到类的实例呢?可以参考下面的例子:
class E{
fun foo(){
println(“foo in Class E”)
}
}
class F{
fun foo(){
println(“foo in Class F”)
}
fun E.foo2(){
this.foo()
this@F.foo()
}
}
E().foo2()
这里使用了kotlin的this指定语法,关键字是@,后接指定的类型,上述例子的输出结果是
foo in Class E
foo in Class F
4. 扩展函数的作用域
一般来说,我们习惯将扩展函数直接定义在包内,例如:
package com.example.extension
fun MutableList.swap(index1:Int,index2:Int) {
val tmp = this[index1]
this[index1] = this[index2]
this[index2] = tmp
}
这样,在同一个包内可以直接调用改扩展函数,如果我们需要跨包调用扩展函数,我们需要通过import来指明,以上述的例子为例,可以通过import com.example.extension.swap
来指定这个扩展函数,也可以通过import com.example.extension.*
表示引入该包内的所有扩展函数。得益于Android Studio具备的自动联想能力,通常不需要我们主动输入import
指令。
有时候,我们也会把扩展函数定义在类的内部,例如:
class G {
fun Int.foo(){
println(“foo in Class G”)
}
}
这里的Int.foo()
是一个定义在类G内部的扩展函数,在这个扩展函数里,我们直接使用Int
类型作为接受者类型,因为我们将扩展函数定义在了类的内部,即使我们设置访问权限为public
,它也只能在该类或者该类的子类中被访问,如果我们设置访问权限为private,那么在子类中也不能访问这个扩展函数。
5. 扩展函数的实际应用
5.1 Utils工具类
在JAVA中,我们习惯将工具类命名成*Utils
,例如FileUtils
,StringUtils
等等,著名的java.util.Collections
也是这么实现的。调用这些方法的时候,总觉得这些类名碍手碍脚的,例如这样:
// Java
Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list));
Collections.max(list));
通过静态引用,能让情况看起来好一点,例如这样:
// Java
swap(list, binarySearch(list, max(otherList)), max(list));
但是这样既没有IDE的自动联想提示,方法调用的主体也显得不明确。如果能做成下面这样就好了:
// Java
list.swap(list.binarySearch(otherList.max()), list.max());
但是list是JAVA默认的基础类,在JAVA语言里,如果不使用继承,肯定是没法做到这样的,而在Kotlin中就可以借助扩展函数来实现啦。
5.2 Android View 胶水代码
回到最开始的例子,对于Android开发来说,对findViewById()
这个方法一定不会陌生,为了获取一个View对象,我们总得先调用findViewById()
然后再执行类型转换,这样无意义的胶水代码让Activity
或者Fragment
显得臃肿无比,例如:
// JAVA
public class MainJavaActivity extends Activity {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView label = (TextView) findViewById(R.id.label);
Button btn = (Button) findViewById(R.id.btn);
label.setText(“hello”);
label.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(“Glen”,“onClick TextView”);
}
});
btn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Log.d(“Glen”,“onClick Button”);
}
});
}
}
我们考虑利用扩展函数结合泛型,避免频繁的类型转换,扩展函数定义如下:
//kotlin
fun Activity.find(@IdRes id: Int): T {
return findViewById(id) as T
}
调用的时候,如下:
// Kotlin
…
TextView label = find(R.id.label);
Button btn = find(R.id.btn);
…
只是我们还是需要获取到label
,btn
,这样无意义的中间变量,如果在Int类上扩展,可以直接对R.id.*
操作,这样更直接,再结合高阶函数,函数定义如下:
//Kotlin
fun Int.setText(str:String){
val label = find(this).apply {
text = str
}
}
fun Int.onClick(click: ()->Unit){
val tmp = find(this).apply {
setOnClickListener{
click()
}
}
}
我们就可以这样调用:
//Kotlin
R.id.label.setText(“hello”)
R.id.label.onClick { Log.d(“Glen”,“onClick TextView”) }
R.id.btn.onClick { Log.d(“Glen”,“onClick Button”) }
通常这些扩展函数可以放到基类中,根据扩展函数的作用域知识,我们可以在所有子类中都调用到这些方法,所以kotlin的Activity
可以写成:
// Kotlin
class MainKotlinActivity:KotlinBaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
R.id.label.setText(“hello”)
R.id.label.onClick { Log.d(“Glen”,“onClick TextView”) }
R.id.btn.onClick { Log.d(“Glen”,“onClick Button”) }
}
}
总结
最后对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!
这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司20年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
相信它会给大家带来很多收获:
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
相信它会给大家带来很多收获:
[外链图片转存中…(img-agOoW2eL-1715185546828)]
[外链图片转存中…(img-z41bTCGu-1715185546829)]
当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!