专栏名称: 郭霖
Android技术分享平台,每天都有优质技术文章推送。你还可以向公众号投稿,将自己总结的技术心得分享给大家。
目录
相关文章推荐
蛋先生工作室  ·  最新淘汰鸡行情 ·  21 小时前  
财融圈  ·  快手财会岗位重磅来袭! ·  2 天前  
财融圈  ·  快手财会岗位重磅来袭! ·  2 天前  
起飞的金牌运营  ·  速卖通精品运营流程(一) ·  2 天前  
起飞的金牌运营  ·  速卖通精品运营流程(一) ·  2 天前  
幸福成长札记  ·  第59天在闲鱼上卖货,售后吐槽过后,继续赚钱 ·  4 天前  
市监学习驿站  ·  网络交易平台经营“十提醒” ·  4 天前  
市监学习驿站  ·  网络交易平台经营“十提醒” ·  4 天前  
51好读  ›  专栏  ›  郭霖

探究「Kotlin语法糖」背后的本质

郭霖  · 公众号  ·  · 2024-03-10 21:47

正文



/   今日科技快讯   /


近日,在召开的第十四届中国国际储能大会上,比亚迪储能及新型电池事业部市场营总监侯铎在会上表示,2023年比亚迪全球储能电池出货量为28.4Gwh,累计出货量达40.4Gwh,业务覆盖107个国家和地区。

/   作者简介   /


大家周一好,新的一周继续加油!


本篇文章转自 沈剑心 的博客,文章主要分享了 Kotlin语法糖相关的故事 ,相信会对大家有所帮助!


原文地址:

https://juejin.cn/post/7318717459074367540


/   前言   /


Kotlin 是一种现代但已经成熟的编程语言,旨在让开发人员更快乐。它简洁、安全、可与 Java 和其他语言互操作。—— Kotlin官网


Kotlin是一门年轻的语言,由世界上IDE做的最好的Jetbrains 公司在2010年面向公众推出,直到2017年Google在I/O大会上宣布推荐Kotlin作为Android开发语言才进入Android开发者视野,2019年的I/O大会上,Google再度加码,宣布Kotlin为Android开发的首选语言,并且Android官方的类库代码将逐渐切换为Kotlin实现,至此确立Kotlin语言在Android开发中的地位。


正如官方文档说的那样,Kotlin语法带有许多方便高效的语法设计,但又与 Java 完美兼容。有人说这些只不过是Kotlin的 「语法糖」,本质还是JVM那一套,本篇文章旨在剥开这层糖衣,探究 Kotlin 的一些语法特性在编译后的本质。


/   函数的作用域   /


顶级函数 top-level functions


在Java中,任何函数与变量都需要声明在一个类中,Kotlin摒弃了这个设定,允许开发者脱离类和接口定义函数与变量,Kotlin称之为顶级函数。我们尝试在Android Studio中声明一个顶级函数来探究本质:


package com.example

fun topFunction(): String {
    return "这是一个顶级方法"
}


借助 IDE 提供的一些能力,我们可以很方便的看到这段Kotlin代码等价的Java代码,具体操作路径如下:



上面这段代码对应的Java代码如下:


package com.example;

public final class TopLevelDemoKt {
    @NotNull
    public static final String topFunction() {
        return "这是一个顶级方法";
    }
}


可以看到,所谓的脱离类限制的顶级函数,实际上在Java中其实还是处于一个类中,这个类的名字由Kotlin文件名 + Kt组成,对应的方法变成了Java中的静态方法。于是我们在Java代码中可以这样调用上面那段Kotlin的顶级函数:


public




    
 class UsingTopLevelFunctionInJava {
    public static void main(String[] args) {
        String text = TopLevelDemoKt.topFunction();
    }
}


我们还也可以通过注解@file:JvmName指定生成的类名:


@file:JvmName("Top")
package com.example

fun topFunction(): String {
    return "这是一个顶级方法"
}


对应的Java代码调用也就变成了Top.topFunction()。


本地函数 local functions


下面展示一段由Kotlin编写的模拟校验用户注册提供的账号密码是否合规的代码,我们假设用户账户或者密码长度小于7为不合规,直接抛出异常:


fun registrationCheck(username: String, password: String)Boolean {
    var variableOutsideTheLocalFunction = 1
    fun validateInput(input: String){
        // 本地方法可以引用到方法外的变量
        variableOutsideTheLocalFunction++
        if (input.length < 7) {
            throw IllegalArgumentException("The length must be greater than 7")
        }
    }
    validateInput(username)
    validateInput(password)
    return true
}


和我们认知中的Java代码不同,该代码的validateInput函数写在了registrationCheck方法体里,这种“函数的函数”的形式,Kotlin官方称之为本地函数Local Functions。可以看到的是,本地函数可以访问外部函数的局部变量,在上面的例子中,variableOutsideTheLocalFunction定义在本地方法之外,但仍旧可以在本地方法内引用到。这又是什么magic呢?我们来看与上面代码等价的Java代码:


public final class KotlinDemoKt {
    public static final boolean registrationCheck(@NotNull String username, @NotNull String password) {
        Intrinsics.checkNotNullParameter(username, "username");
        Intrinsics.checkNotNullParameter(password, "password");
        final Ref.IntRef variableOutsideTheLocalFunction = new Ref.IntRef();
        variableOutsideTheLocalFunction.element = 1;
         $fun$validateInput$1 = new Function1() {
            // $FF: synthetic method
            // $FF: bridge method
            public Object invoke(Object var1) {
                this.invoke((String)var1);
                return Unit.INSTANCE;
            }

               public final void invoke(@NotNull String input) {
                Intrinsics.checkNotNullParameter(input, "input");
                int var10001 = variableOutsideTheLocalFunction.element++;
                if (input.length() < 7) {
                    throw (Throwable)(new IllegalArgumentException("The length must be greater than 7"));
                }
            }
        };
        $fun$validateInput$1.invoke(username);
        $fun$validateInput$1.invoke(password);
        return true;
    }
}


代码相对来说还是比较好理解的,关键就是我们在Kotlin中定义的本地函数validateInput在Java中被声明成了一个Function1的实现,并通过方法invoke进行调用,我们一起来看看这个Funtion1究竟是何方神圣:


/** A function that takes 0 arguments. */
public interface Function0<out R> : Function<R{
    /** Invokes the function. */
    public operator fun invoke(): R
}
/** A function that takes 1 argument. */
public interface Function1<in P1, out R> : Function<R{
    /** Invokes the function with the specified argument. */
    public operator fun invoke(p1: P1): R
}

/** 省略 N 个 相似代码... */


/** A function that takes 22 arguments. */
public interface Function22<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, in P19, in P20, in P21, in P22, out R> : Function<R{
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21, p22: P22): R
}


进入kotlin.jvm.functions.Functions.kt文件下面,我们能看到这里预定义了很多类似的接口,实际上,Lambda 表达式就是实现了这些接口之一的类。


Kotlin 通过本地函数为开发者提供了一种比private更小的、更容易理解和维护的方法块,通过这种方式大大提高代码可读性和可复用性。


同时,我们应该注意到由于其特有的实现方式(匿名内部类),相比于直接使用私有函数,使用本地函数存在额外的性能开销,虽然这点开销微乎其微,但仍然需要开发者进行一定的取舍。


/   拓展   /


Kotlin支持为已经声明好的类扩展新的方法和新的属性。


/**
 * androidx.core.view.View.Kt中为View的扩展的visible属性
 */

public inline var View.isVisible: Boolean
    get() = visibility == View.VISIBLE
    set(value) {
        visibility = if (value) View.VISIBLE else View.GONE
    }

/**
 * androidx.core.view.View.Kt中为View的扩展的用于更新padding的方法
 */

public inline fun View.setPadding(@Px size: Int) {
    setPadding(size, size, size, size)
}


等价的Java代码大概是这样的:


public static final boolean isVisible(@NotNull View $this$isVisible) {
    int $i$f$isVisible = 0;
    Intrinsics.checkNotNullParameter($this$isVisible, "$this$isVisible");
    return $this$isVisible.getVisibility() == 0;
}

public static final void setVisible(@NotNull View $this$isVisible, boolean value) {
    int $i$f$setVisible = 0;
    Intrinsics.checkNotNullParameter($this$isVisible, "$this$isVisible");
    $this$isVisible.setVisibility(value ? 0 : 8);
}

public static final void setPadding(@NotNull View $this$setPadding, @Px int size) {
    int $i$f$setPadding = 0;
    Intrinsics.checkNotNullParameter($this$setPadding, "$this$setPadding");
    $this$setPadding.setPadding(size, size, size, size);
}


可以看到,在等价的Java代码中,拓展函数的第一个参数是被拓展类本身,第二个参数才是拓展函数中定义的参数,同样,拓展属性也是通过拓展对应的getter / setter方法来实现的。


拓展的出现,可以让我们无需修改类的源代码就能向现有的类中添加新的功能,降低了修改代码带来的风险,同时也使得代码的可读性更好。Android官方也在androidx加入了许多好用的拓展属性和拓展方法。


/   默认参数   /


使用Java代码编写Android中的自定义View大概是这样式儿的:


public class MViewWithJava extends View {
    public MViewWithJava(Context context) {
        this(context, null);
    }

    public MViewWithJava(Context context, @Nullable AttributeSet attrs) {
        this






请到「今天看啥」查看全文


推荐文章
蛋先生工作室  ·  最新淘汰鸡行情
21 小时前
财融圈  ·  快手财会岗位重磅来袭!
2 天前
财融圈  ·  快手财会岗位重磅来袭!
2 天前
起飞的金牌运营  ·  速卖通精品运营流程(一)
2 天前
起飞的金牌运营  ·  速卖通精品运营流程(一)
2 天前
市监学习驿站  ·  网络交易平台经营“十提醒”
4 天前
市监学习驿站  ·  网络交易平台经营“十提醒”
4 天前