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,这里通过 inline 和 reified 关键字,获取到泛型的 class 信息
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Proxyinline fun <reified T> noOpDelegate() : T {val javaClass = T::class.javareturn 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 fieldprivate 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}}
相关文章:
Kotlin 优雅的接口实现
1. 日常遇到的冗余的接口方法实现 日常开发中,经常会要实现接口,但是很多场景中,只需要用到其中一两个方法,例如 ActivityLifecycleCallbacks,它有很多个接口需要实现,但是很多时候我们只需要用到其中的一…...
go 通过ssh连接linux golang.org/x/crypto/ssh
ssh.Dial golang.org/x/crypto/ssh package mainimport ("bytes""log""os""strings""golang.org/x/term""golang.org/x/crypto/ssh" )// go ssh 连接ssh // 参考blog: // // https://www.cnblogs.c…...
纯手工搭建整套CI/CD流水线指南
目录 一、前言 二、环境准备 1、服务器开荒(192.168.1.200) 2、离线资源清单(提前用U盘拷好) 三、硬核安装:比拧螺丝还细的步骤 Step1:搭建GitLab(注意!这是只内存饕餮…...
智能硬件新时代,EasyRTC开启物联音视频新纪元
在万物互联的时代浪潮中,智能硬件正以前所未有的速度融入我们的生活,从智能家居的便捷控制,到智能穿戴设备的健康监测,再到工业物联网的高效管理,智能硬件的应用场景不断拓展。而在这个智能硬件蓬勃发展的背后…...
Rust编程语言入门教程(八)所有权 Stack vs Heap
Rust 系列 🎀Rust编程语言入门教程(一)安装Rust🚪 🎀Rust编程语言入门教程(二)hello_world🚪 🎀Rust编程语言入门教程(三) Hello Cargo…...
spring 狂神说的详细笔记(完整版)
最近在B站找教程视频自学java框架(SSM),最后发现自己迷上了狂神说,不得不说秦疆老师 讲得太好了,通俗易懂,而且在听他的课你会不由衷得到一些思想的启发和转变,而且教程视频 还是无偿免费的&…...
交易所开发:数字市场的核心动力
数字资产交易所作为连接用户与市场的核心枢纽,已成为推动数字经济发展的关键引擎。其开发不仅需要技术创新,还需兼顾用户体验、合规安全与生态构建,以下是交易所开发的核心要素与实践路径分析: 一、交易所的核心定位与技术架构…...
C++ 课程设计 汇总(含源码)
C 课程设计 [C课程设计 个人账务管理系统(含源码)](https://arv000.blog.csdn.net/article/details/145601695)[C课程设计 运动会分数统计(含源码)](https://arv000.blog.csdn.net/article/details/145601819)[C 课程设计打印万年历(含源码&a…...
android调用ffmpeg解析rtsp协议的视频流
文章目录 一、背景二、解析rtsp数据1、C层功能代码2、jni层的定义3、app层的调用 三、源码下载 一、背景 本demo主要介绍android调用ffmpeg中的接口解析rtsp协议的视频流(不解析音频),得到yuv数据,把yuv转bitmap在android设备上显…...
Android 之 AIDL for HAL
Android AIDL for HAL 的作用与实现 作用: Android AIDL for HAL(Android Interface Definition Language for Hardware Abstraction Layer)旨在统一 HAL 开发接口,替代 HIDL(Hardware Interface Definition Language…...
Jmeter进阶篇(34)如何解决jmeter.save.saveservice.timestamp_format=ms报错?
问题描述 今天使用Jmeter完成压测执行,然后使用命令将jtl文件转换成html报告时,遇到了报错! 大致就是说jmeter里定义了一个jmeter.save.saveservice.timestamp_format=ms的时间格式,但是jtl文件中的时间格式不是标准的这个ms格式,导致无法正常解析。对于这个问题,有如下…...
TensorFlow v2.16 Overview
TensorFlow v2.16 Overview 一、模块 Modules二、类 Classes三、函数 Functions TensorFlow v2.16.1 Overview 一、模块 Modules 模块是TensorFlow中组织代码的一种方式,将相关的功能和类封装在一起,方便用户使用和管理。每个模块都提供了特定领域的公共…...
Navicat17详细安装教程(附最新版本安装包和补丁)2025最详细图文教程安装手册
目录 前言:为什么选择Navicat 17? 一、下载Navicat17安装包 二、安装Navicat 1.运行安装程序 2.启动安装 3.同意“协议” 4.设置安装位置 5.创建桌面图标 6.开始安装 7.安装完成 三、安装补丁 1.解押补丁包 2.在解压后的补丁包目录下找到“w…...
机器视觉检测中,2D面阵相机和线扫相机的区别
2D面阵相机和线扫相机是工业视觉系统中常用的两种相机类型,各有其特点和应用场景。 2D面阵相机 特点: 成像方式:通过二维传感器一次性捕捉整个场景的图像。 分辨率:分辨率由传感器的像素数决定,常见的有百万像素到几千…...
【接口封装】——13、登录窗口的标题栏内容设置
解释: 1、封装内容:图标、文本内容、宽度 2、ui.iconLabel:在UI文件中的自定义命名 3、引入头文件:#include<qpixmap.h> 函数定义: #pragma once#include <QWidget> #include "ui_TitleBar.h"cl…...
一文详解U盘启动Legacy/UEFI方式以及GPT/MBR关系
对于装系统的老手而说一直想研究一下装系统的原理,以及面对一些问题时的解决思路,故对以前的方法进行原理上的解释,主要想理解其底层原理。 引导模式 MBR分区可以同时支持UEFI和Legacy引导,我们可以看一下微pe制作的启动盘&#…...
CMake管理依赖实战:多仓库的无缝集成
随着软件复杂度的增加,单个项目可能需要依赖多个外部库或模块。这些依赖项可能是来自不同的代码仓库,如ATest和BTest。为了实现高效的依赖管理,CMake提供了多种方式来处理这种多仓库的情况。下面我们将详细介绍几种常见的方法,并通…...
LeetCode 热题 100_搜索二维矩阵(64_74_中等_C++)(二分查找)(暴力破解法;Z字形查找;一次二分查找)
LeetCode 热题 100_搜索二维矩阵(64_74) 题目描述:输入输出样例:题解:解题思路:思路一(暴力破解法):思路二(Z字形查找):思路三&#x…...
学习量化交易的环境安装记录
1、安装anaconda 因为使用python,需要安装anaconda,具体是下面的官方地址,根据自己需要下载相应的版本 https://www.anaconda.com/download 运行上面下载的文件,安装anaconda 可以根据自己需要安装到相应的盘上面 同时环境变量…...
MySQL数据库入门到大蛇尚硅谷宋红康老师笔记 高级篇 part 1
第01章_Linux下MySQL的安装与使用 首先在vmware中下载centos7,实际上8更好一点,不过centos已经是时代的眼泪了,我之前已经教过了,不过是忘了,所以重新说一遍,看文档即可 2.开机前修改mac地址 ࿰…...
基于AVue的二次封装:快速构建后台管理系统的CRUD方案
基于AVue的二次封装:快速构建后台管理系统的CRUD方案 在开发后台管理系统时,表格是常见的组件之一。然而,使用原生的Element Plus实现CRUD(增删改查)功能往往需要编写大量重复代码,过程繁琐。即使借助类似…...
第6章:基于LangChain如何开发Agents,附带客户支持智能体示例
本文主要介绍了 LangChain4j 中的 Agent(代理) 概念,以及如何使用 LangChain4j 构建代理系统,重点提供了一个客户支持系统的智能体样例 代理(Agents)| LangChain4j 注意: 请注意,“A…...
【分布式理论13】分布式存储:数据存储难题与解决之道
文章目录 一、数据存储面临的问题二、RAID磁盘阵列的解决方案1. RAID概述2. RAID使用的技术3. RAID的代表性等级 三、分布式存储的新思路1. 分布式存储背景与特点2. 分布式存储的组成要素 一、数据存储面临的问题 在单机系统时代,当数据量不断增加、硬盘空间不够时…...
传统的自动化行业的触摸屏和上位机,PLC是否会被取代?
传统的自动化行业的触摸屏和上位机是否会被取代? 在工业自动化领域,触摸屏和上位机长期扮演着核心角色,尤其在污水处理、化工生产等场景中,它们通过实时数据采集、逻辑控制、报警联动等功能,保障了生产设备的稳定运行…...
智能合约的部署
https://blog.csdn.net/qq_40261606/article/details/123249473 编译 点击图中的 “Compile 1_Storage.sol” 存和取一个数的合约,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、在弹出的对话框中,输入自己的API Key名称,点击创建。 sk-0385cad5e19346a0a4ac8b7f0d7be428 5、打开Word文档。 6、Word找…...
Redis中集合(Set)常见命令详解
集合(Set)常见命令详解 集合(Set)在Redis中是一种无序且不可重复的数据结构,非常适合用于存储唯一元素的集合。以下是Redis集合操作的一些常用命令及其详细说明: 添加成员 sadd key member [member ...]…...
Perl 面向对象编程指南
Perl 面向对象编程指南 引言 Perl 是一种强大的编程语言,以其灵活性和强大的文本处理能力而闻名。随着软件工程的发展,面向对象编程(OOP)已经成为现代编程的主流。本文将深入探讨 Perl 的面向对象编程,包括其基本概念…...
用 WOW.js 和 animate.css 实现动画效果
用 wow.js 就可以实现动画效果,但由于里面的动画样式太少,一般还会引入 animated.css 第一步:下载 选择合适的包管理器下载对应的内容 pnpm i wow.js animated.css --save 第二步:引入 在main.js中加入: import …...
Mac系统下使用Docker快速部署MaxKB:打造本地知识库问答系统
随着大语言模型的广泛应用,知识库问答系统逐渐成为提升工作效率和个人学习的有力工具。MaxKB是一款基于LLM(Large Language Model)大语言模型的知识库问答系统,支持多模型对接、文档上传和自动爬取等功能。本文将详细介绍如何在Ma…...
