Kotlin 优雅的接口实现

news2025/2/23 3:46:12
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
            }
        }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2303734.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

纯手工搭建整套CI/CD流水线指南

目录 一、前言 二、环境准备 1、服务器开荒&#xff08;192.168.1.200&#xff09; 2、离线资源清单&#xff08;提前用U盘拷好&#xff09; 三、硬核安装&#xff1a;比拧螺丝还细的步骤 Step1&#xff1a;搭建GitLab&#xff08;注意&#xff01;这是只内存饕餮&#xf…

智能硬件新时代,EasyRTC开启物联音视频新纪元

在万物互联的时代浪潮中&#xff0c;智能硬件正以前所未有的速度融入我们的生活&#xff0c;从智能家居的便捷控制&#xff0c;到智能穿戴设备的健康监测&#xff0c;再到工业物联网的高效管理&#xff0c;智能硬件的应用场景不断拓展。而在这个智能硬件蓬勃发展的背后&#xf…

Rust编程语言入门教程(八)所有权 Stack vs Heap

Rust 系列 &#x1f380;Rust编程语言入门教程&#xff08;一&#xff09;安装Rust&#x1f6aa; &#x1f380;Rust编程语言入门教程&#xff08;二&#xff09;hello_world&#x1f6aa; &#x1f380;Rust编程语言入门教程&#xff08;三&#xff09; Hello Cargo&#x1f…

交易所开发:数字市场的核心动力

数字资产交易所作为连接用户与市场的核心枢纽&#xff0c;已成为推动数字经济发展的关键引擎。其开发不仅需要技术创新&#xff0c;还需兼顾用户体验、合规安全与生态构建&#xff0c;以下是交易所开发的核心要素与实践路径分析&#xff1a; 一、交易所的核心定位与技术架构…

Jmeter进阶篇(34)如何解决jmeter.save.saveservice.timestamp_format=ms报错?

问题描述 今天使用Jmeter完成压测执行,然后使用命令将jtl文件转换成html报告时,遇到了报错! 大致就是说jmeter里定义了一个jmeter.save.saveservice.timestamp_format=ms的时间格式,但是jtl文件中的时间格式不是标准的这个ms格式,导致无法正常解析。对于这个问题,有如下…

Navicat17详细安装教程(附最新版本安装包和补丁)2025最详细图文教程安装手册

目录 前言&#xff1a;为什么选择Navicat 17&#xff1f; 一、下载Navicat17安装包 二、安装Navicat 1.运行安装程序 2.启动安装 3.同意“协议” 4.设置安装位置 5.创建桌面图标 6.开始安装 7.安装完成 三、安装补丁 1.解押补丁包 2.在解压后的补丁包目录下找到“w…

一文详解U盘启动Legacy/UEFI方式以及GPT/MBR关系

对于装系统的老手而说一直想研究一下装系统的原理&#xff0c;以及面对一些问题时的解决思路&#xff0c;故对以前的方法进行原理上的解释&#xff0c;主要想理解其底层原理。 引导模式 MBR分区可以同时支持UEFI和Legacy引导&#xff0c;我们可以看一下微pe制作的启动盘&#…

LeetCode 热题 100_搜索二维矩阵(64_74_中等_C++)(二分查找)(暴力破解法;Z字形查找;一次二分查找)

LeetCode 热题 100_搜索二维矩阵&#xff08;64_74&#xff09; 题目描述&#xff1a;输入输出样例&#xff1a;题解&#xff1a;解题思路&#xff1a;思路一&#xff08;暴力破解法&#xff09;&#xff1a;思路二&#xff08;Z字形查找&#xff09;&#xff1a;思路三&#x…

学习量化交易的环境安装记录

1、安装anaconda 因为使用python&#xff0c;需要安装anaconda&#xff0c;具体是下面的官方地址&#xff0c;根据自己需要下载相应的版本 https://www.anaconda.com/download 运行上面下载的文件&#xff0c;安装anaconda 可以根据自己需要安装到相应的盘上面 同时环境变量…

MySQL数据库入门到大蛇尚硅谷宋红康老师笔记 高级篇 part 1

第01章_Linux下MySQL的安装与使用 首先在vmware中下载centos7&#xff0c;实际上8更好一点&#xff0c;不过centos已经是时代的眼泪了&#xff0c;我之前已经教过了&#xff0c;不过是忘了&#xff0c;所以重新说一遍&#xff0c;看文档即可 2.开机前修改mac地址 &#xff0…

基于AVue的二次封装:快速构建后台管理系统的CRUD方案

基于AVue的二次封装&#xff1a;快速构建后台管理系统的CRUD方案 在开发后台管理系统时&#xff0c;表格是常见的组件之一。然而&#xff0c;使用原生的Element Plus实现CRUD&#xff08;增删改查&#xff09;功能往往需要编写大量重复代码&#xff0c;过程繁琐。即使借助类似…

第6章:基于LangChain如何开发Agents,附带客户支持智能体示例

本文主要介绍了 LangChain4j 中的 Agent&#xff08;代理&#xff09; 概念&#xff0c;以及如何使用 LangChain4j 构建代理系统&#xff0c;重点提供了一个客户支持系统的智能体样例 代理&#xff08;Agents&#xff09;| LangChain4j 注意&#xff1a; 请注意&#xff0c;“A…

传统的自动化行业的触摸屏和上位机,PLC是否会被取代?

传统的自动化行业的触摸屏和上位机是否会被取代&#xff1f; 在工业自动化领域&#xff0c;触摸屏和上位机长期扮演着核心角色&#xff0c;尤其在污水处理、化工生产等场景中&#xff0c;它们通过实时数据采集、逻辑控制、报警联动等功能&#xff0c;保障了生产设备的稳定运行…

智能合约的部署

https://blog.csdn.net/qq_40261606/article/details/123249473 编译 点击图中的 “Compile 1_Storage.sol” 存和取一个数的合约&#xff0c;remix自带 pragma solidity >0.8.2 <0.9.0; /*** title Storage* dev Store & retrieve value in a variable* custom:d…

word$deepseep

1、进入官网地址。 DeepSeek 2、进入DeepSeek的API文档 3、点击DeepSeek开放平台左侧的“API Keys”, 再点击“创建API Key” 4、在弹出的对话框中&#xff0c;输入自己的API Key名称&#xff0c;点击创建。 sk-0385cad5e19346a0a4ac8b7f0d7be428 5、打开Word文档。 6、Word找…

Mac系统下使用Docker快速部署MaxKB:打造本地知识库问答系统

随着大语言模型的广泛应用&#xff0c;知识库问答系统逐渐成为提升工作效率和个人学习的有力工具。MaxKB是一款基于LLM&#xff08;Large Language Model&#xff09;大语言模型的知识库问答系统&#xff0c;支持多模型对接、文档上传和自动爬取等功能。本文将详细介绍如何在Ma…

如何为自己的 PDF 文件添加密码?在线加密 PDF 文件其实更简单

随着信息泄露和数据安全问题的日益突出&#xff0c;保护敏感信息变得尤为重要。加密 PDF 文件是一种有效的手段&#xff0c;可以确保只有授权用户才能访问或修改文档内容。本文将详细介绍如何使用 CleverPDF 在线工具为你的 PDF 文件添加密码保护&#xff0c;确保其安全性。 为…

华为昇腾910b服务器部署DeepSeek翻车现场

最近到祸一台HUAWEI Kunpeng 920 5250&#xff0c;先看看配置。之前是部署的讯飞大模型&#xff0c;发现资源利用率太低了。把5台减少到3台&#xff0c;就出了他 硬件配置信息 基本硬件信息 按照惯例先来看看配置。一共3块盘&#xff0c;500G的系统盘&#xff0c; 2块3T固态…

hive—常用的函数整理

1、size(split(...))函数用于计算分割后字符串数组的长度 实例1&#xff09;&#xff1a;由客户编号列表计算客户编号个数 --数据准备 with tmp_test01 as ( select tag074445270 tag_id,202501busi_mon , 012399931003,012399931000 index_val union all select tag07444527…

Unity Mirror 多房间匹配

文章目录 一 、一些唠叨二 、案例位置三、多房间匹配代码解析四、关于MatchInterestManagement五、总结 一 、一些唠叨 最近使用Mirror开发了一款多人同时在线的肉鸽塔防游戏,其目的是巩固一下Mirror这个插件的熟练度,另一方面是想和身边的朋友一起玩一下自己开发的游戏. 但是…