Kotlin 优雅的接口实现

1. 日常遇到的冗余的接口方法实现

日常开发中,经常会要实现接口,但是很多场景中,只需要用到其中一两个方法,例如 ActivityLifecycleCallbacks,它有很多个接口需要实现,但是很多时候我们只需要用到其中的一两个

复制代码
    val myActivityLifecycleCallbacks = object : Application.ActivityLifecycleCallbacks  {

        /**
         * 例如我们只需要监听 Activity 的创建和销毁,那么 onActivityStarted, onActivityResumed, onActivityPaused
         * onActivityStopped,onActivityStopped,onActivitySaveInstanceState 这 6 个方法是完全没必要实现的,
         * 即使是空实现
         */
        override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
            TODO("Not yet implemented")
        }

        override fun onActivityStarted(activity: Activity) {
            TODO("Not yet implemented")
        }

        override fun onActivityResumed(activity: Activity) {
            TODO("Not yet implemented")
        }

        override fun onActivityPaused(activity: Activity) {
            TODO("Not yet implemented")
        }

        override fun onActivityStopped(activity: Activity) {
            TODO("Not yet implemented")
        }

        override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
            TODO("Not yet implemented")
        }

        override fun onActivityDestroyed(activity: Activity) {
            TODO("Not yet implemented")
        }
    }

如果有多个不同业务需要实现这个接口,就这样很容易产生代码冗余。有没有一种优雅的方式,只需要实现自己需要的方法而不再需要去关注其他方法?有的,那就是利用 Java 的动态代理和 kotlin 的委托模式

2. 利用 Java 的动态代理和 Kotlin 的委托模式

首先需要实现一个通用的动态代理,新建一个 Kotlin 文件 DelegateObject.kt,这里通过 inlinereified 关键字,获取到泛型的 class 信息

复制代码
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Proxy

inline fun <reified T> noOpDelegate() : T {
    val javaClass = T::class.java
    return Proxy.newProxyInstance(javaClass.classLoader, arrayOf(javaClass), no_op_invocationHandler) as T
}

val no_op_invocationHandler = InvocationHandler { _, _, _ -> }

这样就可以获取到任意一个接口的一个对象,只是没有具体的实现。接着再利用 Kotlin 的 by 关键字实现对象委托

复制代码
    val myActivityLifecycleCallbacks = object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
        
    }

由于 Kotlin 委托模式的原理,实际上在编译期间也是会生成 ActivityLifecycleCallbacks 的所有方法,先来看看转译后的实现

复制代码
   private final Application.ActivityLifecycleCallbacks myActivityLifecycleCallbacks = (Application.ActivityLifecycleCallbacks)(new Application.ActivityLifecycleCallbacks() {
      // $FF: synthetic field
      private final Application.ActivityLifecycleCallbacks $$delegate_0;

      {
         int $i$f$noOpDelegate = false;
         Class javaClass$iv = Application.ActivityLifecycleCallbacks.class;
         Object var10001 = Proxy.newProxyInstance(javaClass$iv.getClassLoader(), new Class[]{javaClass$iv}, DelegateObjectKt.getNo_op_invocationHandler());
         if (var10001 == null) {
            throw new NullPointerException("null cannot be cast to non-null type android.app.Application.ActivityLifecycleCallbacks");
         } else {
            this.$$delegate_0 = (Application.ActivityLifecycleCallbacks)var10001;
         }
      }

      public void onActivityCreated(@NonNull @NotNull Activity activity, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
         Intrinsics.checkNotNullParameter(activity, "activity");
         this.$$delegate_0.onActivityCreated(activity, savedInstanceState);
      }

      public void onActivityDestroyed(@NonNull @NotNull Activity activity) {
         Intrinsics.checkNotNullParameter(activity, "activity");
         this.$$delegate_0.onActivityDestroyed(activity);
      }

      public void onActivityPaused(@NonNull @NotNull Activity activity) {
         Intrinsics.checkNotNullParameter(activity, "activity");
         this.$$delegate_0.onActivityPaused(activity);
      }

      public void onActivityResumed(@NonNull @NotNull Activity activity) {
         Intrinsics.checkNotNullParameter(activity, "activity");
         this.$$delegate_0.onActivityResumed(activity);
      }

      public void onActivitySaveInstanceState(@NonNull @NotNull Activity activity, @NonNull @NotNull Bundle outState) {
         Intrinsics.checkNotNullParameter(activity, "activity");
         Intrinsics.checkNotNullParameter(outState, "outState");
         this.$$delegate_0.onActivitySaveInstanceState(activity, outState);
      }

      public void onActivityStarted(@NonNull @NotNull Activity activity) {
         Intrinsics.checkNotNullParameter(activity, "activity");
         this.$$delegate_0.onActivityStarted(activity);
      }

      public void onActivityStopped(@NonNull @NotNull Activity activity) {
         Intrinsics.checkNotNullParameter(activity, "activity");
         this.$$delegate_0.onActivityStopped(activity);
      }
   });

现在已经将 ActivityLifecycleCallbacks 的匿名内部类对象委托给了 noOpDelegate 生成的代理对象。这样需要用到具体哪个方法时,只需要再次重写即可,例如文章最开始的例子可以变为

复制代码
    val myActivityLifecycleCallbacks = object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
        override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
            TODO("Not yet implemented")
        }

        override fun onActivityDestroyed(activity: Activity) {
            TODO("Not yet implemented")
        }
    }

经过精简的代码可以使代码更加简洁,可以更好的聚焦业务实现

3. 注意接口方法存在返回值问题

如果实现的接口中的方法带有返回值,务必要重写该方法,不然会报 IllegalArgumentException 异常。 这也算是这种优雅方式中一个缺点。来看个例子,首先定义一个接口

复制代码
interface TestNoDelegateInterface {
    fun testFun1()

    fun testFun2(): Int
}

该接口定义了两个方法,其中一个方法有 int 类型的返回值,使用 noOpDelegate 实现该接口

复制代码
val testNoDelegateInterface = object : TestNoDelegateInterface by noOpDelegate() {
}

testNoDelegateInterface 去调用 testFun2() 方法

复制代码
testNoDelegateInterface.testFun2()

控制台将打印报错信息

复制代码
AndroidRuntime: FATAL EXCEPTION: main
AndroidRuntime: Process: com.example.mydemoapplication, PID: 25135
AndroidRuntime: java.lang.IllegalArgumentException: result has type int, got kotlin.Unit
AndroidRuntime: 	at $Proxy2.testFun2(Unknown Source)

而当 testNoDelegateInterface 去调用 testFun1() 方法时则没有这个问题

原因是在使用动态代理反射实现 TestNoDelegateInterface 接口的代理对象时,传入的 InvocationHandler 实际是个空对象,当通过 Kotlin 委托生成的接口方法需要一个返回值,而代理对象在实际执行方法时由于没有具体实现,导致两个方法的返回类型不一致,最终报错。先看下 testNoDelegateInterface 转译成的 java 代码

复制代码
      final <undefinedtype> testNoDelegateInterface = new TestNoDelegateInterface() {
         // 委托模式将 testNoDelegateInterface 的能力委托给了由动态代理创建的 $$delegate_0 对象
         private final TestNoDelegateInterface $$delegate_0;

         {
            int $i$f$noOpDelegate = false;
            Class javaClass$iv = TestNoDelegateInterface.class;
            Object var10001 = Proxy.newProxyInstance(javaClass$iv.getClassLoader(), new Class[]{javaClass$iv}, DelegateObjectKt.getNo_op_invocationHandler());
            if (var10001 == null) {
               throw new NullPointerException("null cannot be cast to non-null type com.example.mydemoapplication.TestNoDelegateInterface");
            } else {
               this.$$delegate_0 = (TestNoDelegateInterface)var10001;
            }
         }

         public void testFun1() {
            this.$$delegate_0.testFun1();
         }

        public int testFun2() {
            /**
            * 动态代理的对象的方法调用最终都会执行到 InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) 
            * 的方法实现,因为通用动态代理传入的 no_op_invocationHandler 是个空实现,所以这里调用并不会返回一个期望的返回值
            */
            return this.$$delegate_0.testFun2();
        }
      };

综上,需要实现带返回值的接口,这样就不会报错了

复制代码
        val testNoDelegateInterface = object : TestNoDelegateInterface by noOpDelegate() {
            // 重写带返回的方法,具体返回的值按照业务需求实现
            override fun testFun2(): Int {
                return Int.MIN_VALUE
            }
        }
相关推荐
陈小桔1 小时前
idea中重新加载所有maven项目失败,但maven compile成功
java·maven
小学鸡!1 小时前
Spring Boot实现日志链路追踪
java·spring boot·后端
爱学习的大牛1231 小时前
MVVM 架构 android
android·mvvm
xiaogg36781 小时前
阿里云k8s1.33部署yaml和dockerfile配置文件
java·linux·kubernetes
逆光的July1 小时前
Hikari连接池
java
微风粼粼1 小时前
eclipse 导入javaweb项目,以及配置教程(傻瓜式教学)
java·ide·eclipse
番茄Salad2 小时前
Spring Boot临时解决循环依赖注入问题
java·spring boot·spring cloud
天若有情6732 小时前
Spring MVC文件上传与下载全面详解:从原理到实战
java·spring·mvc·springmvc·javaee·multipart
祈祷苍天赐我java之术2 小时前
Redis 数据类型与使用场景
java·开发语言·前端·redis·分布式·spring·bootstrap
Olrookie3 小时前
若依前后端分离版学习笔记(二十)——实现滑块验证码(vue3)
java·前端·笔记·后端·学习·vue·ruoyi