昨天有同学说Kotlin语法不舒服?那你一定没试过DSL

640?wx_fmt=jpeg


/   今日科技快讯   /


近日有创业者爆料称,自己花3000多元购买了时尚博主张雨晗的一条微博,虽然“一夜爆红”,但转化率却为零。微博发出后,初步效果良好。用时49分钟观看量达12.1万,有几百条评论,几千条赞,一百多次转发。最终视频流量达到350万,已经算是爆款级视频。然而创业者发现,最终这条视频给电商部门带来的转化几乎为0,不仅如此,连店铺的浏览量也没有什么提升。经对比后发现,这几百万的流量几乎全是刷量刷出来的。


/   作者简介   /


不知为何,就是感觉这周很漫长,不过好在明天就是周六啦,祝大家周末愉快!


本篇文章来自谭嘉俊的投稿,分享了Kotlin中DSL的讲解,希望对大家有所帮助!同时也感谢作者贡献的精彩文章。


谭嘉俊的博客地址:
https://www.jianshu.com/u/257511d0c878


/   什么是DSL?   /


DSL是domin specific language的缩写,中文名叫做领域特定语言,指的是专注于某个应用程序领域的计算机语言,比如显示网页的HTML、用于数据库处理的SQL、用于检索或者替换文本的正则表达式,它们都是DSL。

与DSL相对的就是GPL,GPL是General Purpose Language的简称,即通用编程语言,就是我们非常熟悉的Java、C、Objective-C等等。

DSL分为外部DSL和内部DSL。

外部DSL是一种可以独立解析的语言,就像SQL,它专注于数据库的操作;内部DSL是通用语言暴露的用来执行特定任务的API,它利用语言本身的特性,将API以特殊的形式暴露出去,例如Android的Gradle和iOS的依赖管理组件CocosPods,Gradle是基于Groovy的,Groovy是一种通用语言,但是Gradle基于Groovy的语法,构建了自己一套DSL,所以在配置Gradle的时候,必须遵循Groovy的语法,还要遵循Gradle的DSL标准。


Android的Gradle文件


 
  
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

android {
    compileSdkVersion 28

    defaultConfig {
        applicationId "com.tanjiajun.androidgenericframework"
        minSdkVersion rootProject.minSdkVersion
        targetSdkVersion rootProject.targetSdkVersion
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    androidExtensions 
{
        experimental = true
    }

    dataBinding {
        enabled = true
    }

    packagingOptions {
        pickFirst 'META-INF/kotlinx-io.kotlin_module'
        pickFirst 'META-INF/atomicfu.kotlin_module'
        pickFirst 'META-INF/kotlinx-coroutines-io.kotlin_module'
    }
}


iOS的Podfile文件


 
  
source 'https://github.com/CocoaPods/Specs.git'

platform :ios, '10.0'

use_frameworks!

target 'GenericFrameworkForiOS' **do**

    pod 'SnapKit''~> 4.0'

**end**


/   实现原理   /


这篇文章主要讲的是Kotlin的DSL,在讲之前我们先了解下这几个概念和语法。


扩展函数


声明一个扩展函数,需要用到一个接受者类型(receiver type),也就是被扩展的类型作为前缀,如下代码:

 
  
fun Activity.getViewModelFactory(): ViewModelFactory =
        ViewModelFactory((applicationContext as AndroidGenericFrameworkApplication).userRepository)


声明一个getViewModelFactory函数,它是Activity的扩展函数。


Lambda表达式


Java8以下的版本不支持Lambda表达式,Kotlin解决了与Java的互操作性,Kotlin的Lambda表达式以更加简洁的语法实现功能,使开发者从冗余啰嗦的语法中解放出来。


Lambda表达式分类


  • 普通的Lambda表达式


 
  
() -> Unit


不接受任何参数返回Unit的Lambda表达式。


 
  
(tab: TabLayout.Tab?) -> Unit


接受一个可空的TabLayout.Tab参数返回Unit的Lambda表达式。


  • 带接收者的Lambda表达式


 
  
OnTabSelectedListenerBuilder.() -> Unit


带有OnTabSelectedListenerBuilder接收者对象,不接受任何参数返回Unit的Lambda表达式。

这种带接收者的在Kotlin的标准库函数中很常见,例如如下的作用域函数(Scope Functions):

  • apply函数


 
  
/**
 * Calls the specified function [block] with `this` value as its receiver and returns `this` value.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#apply).
 */

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


  • let函数

 
  
/**
 * Calls the specified function [block] with `this` value as its argument and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#let).
 */

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


我们还可以给这些Lambda表达式起别称,叫做类型别名(Type aliases)

 
  
private typealias OnTabCallback = (tab: TabLayout.Tab?) -> Unit


我们给这个Lambda表达式起了个别称,叫做OnTabCallback。

Lambda表达式在Kotlin里的这些特性是实现DSL的必备的语法糖。

函数类型实例调用


Kotlin提供invoke函数,我们可以这样写:f.invoke(x),其实它相当于f(x),举个例子:

 
  
onTabReselectedCallback?.invoke(tab) ?: Unit


它其实可以写成这样:

 
  
onTabReselectedCallback?.let { it(tab) } ?: Unit


这两段代码是等价的。

中缀表示法


标有infix关键字的函数可以使用中缀表示法(忽略该调用的点与圆括号)调用,中缀函数必须满足以下要求:

  1. 它们必须是成员函数和扩展函数。
  2. 它们必须只有一个参数。
  3. 其参数不得接受可变数量的参数,而且不能有默认值。


举个例子:

 
  
infix fun Int.plus(x: Int)Int =
        this.plus(x)

// 可以中缀表示法调用函数
1 plus 2

// 等同于这样
1.plus(2)


我再举些例子,都是些我们常用的函数:

  • until函数用法

 
  
infix fun Int.plus(x: Int)Int =
for (i in 0 until 4) {
    tlOrder.addTab(tlOrder.newTab().setText("订单$i"))
}


  • until函数源码

 
  
/**
 * Returns a range from this value up to but excluding the specified [to] value.
 * 
 * If the [to] value is less than or equal to `this` value, then the returned range is empty.
 */

public infix fun Int.until(to: Int): IntRange {
    if (to <= Int.MIN_VALUE) return IntRange.EMPTY


  • to函数用法

 
  
mapOf<String,Any>("name" to "TanJiaJun","age" to 25)


  • to函数源码


 
  
/**
 * Creates a tuple of type [Pair] from this and [that].
 *
 * This can be useful for creating [Map] literals with less noise, for example:
 * @sample samples.collections.Maps.Instantiation.mapFromPairs
 */

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)


/   实践   /


Kotlin DSL使我们的代码更加简洁,更加优雅,而且还很有想象力,让我们看下这几个例子:

回调处理


Java中的回调实现


我们实现的步骤一般是这样:

  1. 定义一个接口
  2. 在接口中定义一些回调方法
  3. 定义一个设置回调接口的方法,这个方法的参数是回调接口的实例,一般以匿名对象的形式存在。

  • 实现TextWatcher接口

 
  
EditText etCommonCallbackContent = findViewById(R.id.et_common_callback_content);
etCommonCallbackContent.addTextChangedListener(new TextWatcher() {
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        // no implementation
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        // no implementation
    }

    @Override
    public void afterTextChanged(Editable s) {
        // no implementation
    }
});


  • 实现TabLayout.OnTabSelectedListener接口

 
  
TabLayout tlOrder = findViewById(R.id.tl_order);
tlOrder.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
    @Override
    public void onTabSelected(TabLayout.Tab tab) {
        // no implementation
    }

    @Override
    public void onTabUnselected(TabLayout.Tab tab) {
        // no implementation
    }

    @Override
    public void onTabReselected(TabLayout.Tab tab) {
        // no implementation
    }
});


Kotlin中的回调实现


  • 实现TextWatcher接口


 
  
findViewById<EditText>(R.id.et_common_callback_content).addTextChangedListener(object :
    TextWatcher {
    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        // no implementation
    }

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        // no implementation
    }

    override fun afterTextChanged(s: Editable?) {
        tvCommonCallbackContent.text = s
    }
})


  • 实现TabLayout.OnTabSelectedListener接口


 
  
tlOrder.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener{
    override fun onTabReselected(tab: TabLayout.Tab?) {
        // no implementation
    }

    override fun onTabUnselected(tab: TabLayout.Tab?) {
        // no implementation
    }

    override fun onTabSelected(tab: TabLayout.Tab?) {
        vpOrder.currentItem = tab?.position ?: 0
    }
})


是不是发现跟Java的写法没什么区别,体验不到Kotlin的优势?

Kotlin DSL


  • TextWatcherBuilder


 
  
package com.tanjiajun.kotlindsldemo

import android.text.Editable
import android.text.TextWatcher

/**
 * Created by TanJiaJun on 2019-10-01.
 */

private typealias BeforeTextChangedCallback =
            (s: CharSequence?, start: Int, count: Int, after: Int) -> Unit

private typealias OnTextChangedCallback =
            (s: CharSequence?, start: Int, before: Int, count: Int) -> Unit

private typealias AfterTextChangedCallback = (s: Editable?) -> Unit

class TextWatcherBuilder : TextWatcher {

    private var beforeTextChangedCallback: BeforeTextChangedCallback? = null
    private var onTextChangedCallback: OnTextChangedCallback? = null
    private var afterTextChangedCallback: AfterTextChangedCallback? = null

    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) =
        beforeTextChangedCallback?.invoke(s, start, count, after) ?: Unit

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) =
        onTextChangedCallback?.invoke(s, start, before, count) ?: Unit

    override fun afterTextChanged(s: Editable?) =
        afterTextChangedCallback?.invoke(s) ?: Unit

    fun beforeTextChanged(callback: BeforeTextChangedCallback) {
        beforeTextChangedCallback = callback
    }

    fun onTextChanged(callback: OnTextChangedCallback) {
        onTextChangedCallback = callback
    }

    fun afterTextChanged(callback: AfterTextChangedCallback) {
        afterTextChangedCallback = callback
    }

}

fun registerTextWatcher(function: TextWatcherBuilder.() -> Unit) =
    TextWatcherBuilder().also(function)


  • OnTabSelectedListenerBuilder


 
  
package com.tanjiajun.androidgenericframework.utils

import com.google.android.material.tabs.TabLayout

/**
 * Created by TanJiaJun on 2019-09-07.
 */

private typealias OnTabCallback = (tab: TabLayout.Tab?) -> Unit

class OnTabSelectedListenerBuilder : TabLayout.OnTabSelectedListener {

    private var onTabReselectedCallback: OnTabCallback? = null
    private var onTabUnselectedCallback: OnTabCallback? = null
    private var onTabSelectedCallback: OnTabCallback? = null

    override fun onTabReselected(tab: TabLayout.Tab?) =
            onTabReselectedCallback?.invoke(tab) ?: Unit

    override fun onTabUnselected(tab: TabLayout.Tab?) =
            onTabUnselectedCallback?.invoke(tab) ?: Unit

    override fun onTabSelected(tab: TabLayout.Tab?) =
            onTabSelectedCallback?.invoke(tab) ?: Unit

    fun onTabReselected(callback: OnTabCallback) {
        onTabReselectedCallback = callback
    }

    fun onTabUnselected(callback: OnTabCallback) {
        onTabUnselectedCallback = callback
    }

    fun onTabSelected(callback: OnTabCallback) {
        onTabSelectedCallback = callback
    }

}

fun registerOnTabSelectedListener(function: OnTabSelectedListenerBuilder.() -> Unit) =
        OnTabSelectedListenerBuilder().also(function)


一般步骤:

  1. 先定义一个类去实现回调接口,并且实现它的回调方法。
  2. 观察回调方法的参数,提取成一个函数类型(function type),并且按照需要使用类型别名给函数类型起一个别称,并且用私有修饰。
  3. 在类里面声明一些可空的函数类型的可变(var)私有成员变量,并且在回调函数中拿到对应的变量实现它的invoke函数,传入对应的参数。
  4. 在类中定义一些跟回调接口一样名字,但是参数是对应的函数类型的函数,并且将函数类型赋值给当前类的对应的成员变量。
  5. 定义一个成员函数,参数是一个带有我们定好那个类的接受者对象并且返回Unit的Lambda表达式,在函数里创建相应的对象,并且使用also函数把Lambda表达式传进去。


如何使用呢?请看下面代码:

  • TextWatcher

 
  
findViewById<EditText>(R.id.et_dsl_callback_content).addTextChangedListener(
    registerTextWatcher {
        afterTextChanged { tvDSLCallbackContent.text = it }
    })


  • TabLayout.OnTabSelectedListener


 
  
tlOrder.addOnTabSelectedListener(registerOnTabSelectedListener {
    onTabSelected { vpOrder.currentItem = it?.position ?: 0 }
})


我再详细地说下为什么可以这样写呢?其实简化之前是这样写的,代码如下:

 
  
findViewById<EditText>(R.id.et_dsl_callback_content).addTextChangedListener(registerTextWatcher({
    this.afterTextChanged({ s: Editable? ->
        tvDSLCallbackContent.text = s
    })
}))


Kotlin语法规定,如果函数最后一个参数是Lambda表达式的话,可以提到小括号外边,同时小括号也可以省略,然后Kotlin可以自己推导出参数的类型,并且使用默认参数it代替命名参数,然后因为这是个带接收者的Lambda表达式,所以我们可以用this拿到对象,并且调用它的afterTextChanged函数,最后就得到我们简化后的代码了。

object对象表达式回调和DSL回调对比


  1. DSL写法object写法会更加符合Kotlin风格。
  2. object写法要实现所有方法,DSL写法可以按照需要实现想要的方法。
  3. 从性能上对比,DSL写法对每个回调函数都会去创建Lambda表达式的实例对象,而object写法不管有多少个回调方法,都只生成一个匿名对象实例,所以object写法比DSL写法性能好。

我这里拿TextWatcher举个例子,把它们反编译成Java代码,代码如下:

  • object对象表达式回调

 
  
((EditText)this.findViewById(-1000084)).addTextChangedListener((TextWatcher)(new TextWatcher() {
   public void beforeTextChanged(@Nullable CharSequence s, int start, int count, int after) {
   }

   public void onTextChanged(@Nullable CharSequence s, int start, int before, int count) {
   }

   public void afterTextChanged(@Nullable Editable s) {
      TextView var10000 = tvCommonCallbackContent;
      Intrinsics.checkExpressionValueIsNotNull(var10000, "tvCommonCallbackContent");
      var10000.setText((CharSequence)s);
   }
}));


  • DSL回调


 
  
((EditText)this.findViewById(-1000121)).addTextChangedListener((TextWatcher)TextWatcherBuilderKt.registerTextWatcher((Function1)(new Function1() {
   // $FF: synthetic method
   // $FF: bridge method
   public Object invoke(Object var1) {
      this.invoke((TextWatcherBuilder)var1);
      return Unit.INSTANCE;
   }

   public final void invoke(@NotNull TextWatcherBuilder $this$registerTextWatcher) {
      Intrinsics.checkParameterIsNotNull($this$registerTextWatcher, "$receiver");
      $this$registerTextWatcher.beforeTextChanged((Function4)(new Function4() {
         // $FF: synthetic method
         // $FF: bridge method
         public Object invoke(Object var1, Object var2, Object var3, Object var4) {
            this.invoke((CharSequence)var1, ((Number)var2).intValue(), ((Number)var3).intValue(), ((Number)var4).intValue());
            return Unit.INSTANCE;
         }

         public final void invoke(@Nullable CharSequence s, int start, int count, int after) {
            TextView var10000 = tvDSLCallbackContent;
            Intrinsics.checkExpressionValueIsNotNull(var10000, "tvDSLCallbackContent");
            var10000.setText(s);
         }
      }));
      $this$registerTextWatcher.onTextChanged((Function4)(new Function4() {
         // $FF: synthetic method
         // $FF: bridge method
         public Object invoke(Object var1, Object var2, Object var3, Object var4) {
            this.invoke((CharSequence)var1, ((Number)var2).intValue(), ((Number)var3).intValue(), ((Number)var4).intValue());
            return Unit.INSTANCE;
         }

         public final void invoke(@Nullable CharSequence s, int start, int before, int count) {
            TextView var10000 = tvDSLCallbackContent;
            Intrinsics.checkExpressionValueIsNotNull(var10000, "tvDSLCallbackContent");
            var10000.setText(s);
         }
      }));
      $this$registerTextWatcher.afterTextChanged((Function1)(new Function1() {
         // $FF: synthetic method
         // $FF: bridge method
         public Object invoke(Object var1) {
            this.invoke((Editable)var1);
            return Unit.INSTANCE;
         }

         public final void invoke(@Nullable Editable it) {
            TextView var10000 = tvDSLCallbackContent;
            Intrinsics.checkExpressionValueIsNotNull(var10000, "tvDSLCallbackContent");
            var10000.setText((CharSequence)it);
         }
      }));
   }
})));


可以看到object写法只生成一个匿名的TextWatcher对象实例,而DSL写法对每个回调函数都会创建Lambda表达式的实例对象(Function1、Function4),符合上述预期。

题外话


Java8引入了default关键字,在接口中可以包含一些默认的方法实现。

 
  
interface Handlers{

    void onLoginClick(View view);

    default void onLogoutClick(View view){

    }

}


用Kotlin实现的话,我们可以加上@JvmDefault注解代码如下:

 
  
interface Handlers{

    fun onLoginClick(view: View)

    @JvmDefault
    fun onLogoutClick(view: View)
{

    }

}


我们可以反编译成Java代码,代码如下:

 
  
@Metadata(
   mv = {1115},
   bv = {103},
   k = 1,
   d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\bf\u0018\u00002\u00020\u0001J\u0010\u0010\u0002\u001a\u00020\u00032\u0006\u0010\u0004\u001a\u00020\u0005H&J\u0010\u0010\u0006\u001a\u00020\u00032\u0006\u0010\u0004\u001a\u00020\u0005H\u0017ø\u0001\u0000\u0082\u0002\u0007\n\u0005\b\u0091(0\u0001¨\u0006\u0007"},
   d2 = {"Lcom/tanjiajun/kotlindsldemo/MainActivity$Handlers;""""onLoginClick""""view""Landroid/view/View;""onLogoutClick""app_debug"}
)
public interface Handlers {
   void onLoginClick(@NotNull View var1);

   @JvmDefault
   default void onLogoutClick(@NotNull View view) {
      Intrinsics.checkParameterIsNotNull(view, "view");
   }
}


在使用@JvmDefault时我们需要注意以下几点:

因为default关键字是Java8才引入的,所以我们需要做些特别处理,从Kotlin官方文档可以看到


Specifies that a JVM default method should be generated for non-abstract Kotlin interface member.


Usages of this annotation require an explicit compilation argument to be specified: either -Xjvm-default=enable or -Xjvm-default=compatibility.


  • with -Xjvm-default=enable, only default method in interface is generated for each @JvmDefault method. In this mode, annotating an existing method with @JvmDefault can break binary compatibility, because it will effectively remove the method from the DefaultImpls class.


  • with -Xjvm-default=compatibility, in addition to the default interface method, a compatibility accessor is generated in the DefaultImpls class, that calls the default interface method via a synthetic accessor. In this mode, annotating an existing method with @JvmDefault is binary compatible, but results in more methods in bytecode.


Removing this annotation from an interface member is a binary incompatible change in both modes.


Generation of default methods is only possible with JVM target bytecode version 1.8 (-jvm-target 1.8) or higher.


@JvmDefault methods are excluded from interface delegation.


翻译一下主要内容,主要有:


1.只有使用JVM目标字节码1.8版本或者更高,才可以生成default方法。


2. 使用这个注解还要指定一个显式的编译参数:-Xjvm-default=enable
-Xjvm-default=compatibility


使用-Xjvm-default=enable的话对于每个@JvmDefault方法,仅仅是生成default方法,同时这样做可能会破坏二进制兼容性,因为它从DefaultImpls类中删除该方法;


使用-Xjvm-default=compatibility的话,除了生成default方法外,还将在DefaultImpls类中生成兼容性访问器,该访问器通过综合访问器调用default方法,在这种模式下,它是二进制兼容的,但是会导致字节码中有更多的方法。


在build.gradle文件中加上如下代码:


 
  
allprojects {
    repositories {
        google()
        jcenter()

    }

    tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
        kotlinOptions {
            jvmTarget = '1.8'
            freeCompilerArgs += '-Xjvm-default=compatibility'
        }
    }
}


我们试下这两种情况,看下符不符合上述预期,代码如下:


加上-Xjvm-default=enable后反编译的代码


 
  
@Metadata(
   mv = {1115},
   bv = {103},
   k = 1,
   d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\bf\u0018\u00002\u00020\u0001J\u0010\u0010\u0002\u001a\u00020\u00032\u0006\u0010\u0004\u001a\u00020\u0005H&J\u0010\u0010\u0006\u001a\u00020\u00032\u0006\u0010\u0004\u001a\u00020\u0005H\u0017ø\u0001\u0000\u0082\u0002\u0007\n\u0005\b\u0091(0\u0001¨\u0006\u0007"},
   d2 = {"Lcom/tanjiajun/kotlindsldemo/MainActivity$Handlers;""""onLoginClick""""view""Landroid/view/View;""onLogoutClick""app_debug"}
)
public interface Handlers {
   void onLoginClick(@NotNull View var1);

   @JvmDefault
   default void onLogoutClick(@NotNull View view) {
      Intrinsics.checkParameterIsNotNull(view, "view");
   }
}


加上-Xjvm-default=compatibility后反编译的代码


 
  
@Metadata(
   mv = {1115},
   bv = {103},
   k = 1,
   d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0002\bf\u0018\u00002\u00020\u0001J\u0010\u0010\u0002\u001a\u00020\u00032\u0006\u0010\u0004\u001a\u00020\u0005H&J\u0010\u0010\u0006\u001a\u00020\u00032\u0006\u0010\u0004\u001a\u00020\u0005H\u0017ø\u0001\u0000\u0082\u0002\u0007\n\u0005\b\u0091(0\u0001¨\u0006\u0007"},
   d2 = {"Lcom/tanjiajun/kotlindsldemo/MainActivity$Handlers;""""onLoginClick""""view""Landroid/view/View;""onLogoutClick""app_debug"}
)
public interface Handlers {
   void onLoginClick(@NotNull View var1);

   @JvmDefault
   default void onLogoutClick(@NotNull View view) {
      Intrinsics.checkParameterIsNotNull(view, "view");
   }

   @Metadata(
      mv = {1115},
      bv = {103},
      k = 3
   )
   public static final class DefaultImpls {
      @JvmDefault
      public static void onLogoutClick(MainActivity.Handlers $this, @NotNull View view) {
         $this.onLogoutClick(view);
      }
   }
}


可以看到加上-Xjvm-default=compatibility-Xjvm-default=enable多了一个DefaultImpls的静态final类,而且类中也有一个静态的方法,其实-Xjvm-default=compatibility是为了兼容Java8之前的版本在接口中也可以实现方法。

Demo地址:
https://github.com/TanJiaJunBeyond/KotlinDSLDemo



推荐阅读:

欢迎关注我的公众号
学习技术或投稿


640.png?


640?wx_fmt=jpeg
长按上图,识别图中二维码即可关注


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值