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
            }
        }
相关推荐
10km12 分钟前
java:Apache Commons Configuration2占位符解析异常的正确解法:${prefix:name:-default}
java·apache·configuration2·变量插值·interpolation
customer0812 分钟前
【开源免费】基于SpringBoot+Vue.JS个人博客系统(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
灰色人生qwer20 分钟前
SpringBoot 项目配置日志输出
java·spring boot·后端
2301_7930698230 分钟前
Spring Boot +SQL项目优化策略,GraphQL和SQL 区别,Spring JDBC 等原理辨析(万字长文+代码)
java·数据库·spring boot·sql·jdbc·orm
阿华的代码王国36 分钟前
【从0做项目】Java搜索引擎(6)& 正则表达式鲨疯了&优化正文解析
java·后端·搜索引擎·正则表达式·java项目·从0到1做项目
服务端相声演员36 分钟前
Oracle JDK、Open JDK zulu下载地址
java·开发语言
是姜姜啊!37 分钟前
java连接redis
java·redis
hhw19911239 分钟前
spring boot知识点5
java·数据库·spring boot
EQUINOX141 分钟前
lab4 CSAPP:Cachelab
java·后端·spring
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS打卡健康评测系统(JAVA毕业设计)
java·vue.js·spring boot·后端·开源