如何调用Java接口中默认方法?

embedded/2024/10/19 6:20:20/

从JDK8开始,接口支持默认方法实现,即在接口中可以有具体的实现,仅需使用关键字 default修饰方法即可,如:

java">public interface MyInterface {default void call(String methodName) {System.out.println("MethodHandles, call method: " + methodName);}
}

那么,如果该接口我们不想有实现类,又想要调用 MyInterface#call 方法怎么办?

此时,可借助JDK给我们提供的方法句柄实现:java.lang.invoke.MethodHandles,从而可动态调用方法。

对于方法句柄的调用实现,不同JDK版本可略有差异,简单说明:

jdk8中如果直接调用{@link MethodHandles#lookup()}获取到的{@link MethodHandles.Lookup}
在调用方法 {@link MethodHandles.Lookup#findSpecial(java.lang.Class, java.lang.String, java.lang.invoke.MethodType, java.lang.Class)}
和{@link MethodHandles.Lookup#unreflectSpecial(java.lang.reflect.Method, java.lang.Class)}
获取父类方法句柄{@link MethodHandle}时
可能出现权限不够, 抛出如下异常, 所以通过反射创建{@link MethodHandles.Lookup}解决该问题.
java.lang.IllegalAccessException: no private access for invokespecial: interface com.example.demo.methodhandle.UserService, from com.example.demo.methodhandle.UserServiceInvoke
at java.lang.invoke.MemberName.makeAccessException(MemberName.java:850)
at java.lang.invoke.MethodHandles$Lookup.checkSpecialCaller(MethodHandles.java:1572)
而jdk11中直接调用{@link MethodHandles#lookup()}获取到的{@link MethodHandles.Lookup},也只能对接口类型才会权限获取方法的方法句柄{@link MethodHandle}.
如果是普通类型Class,需要使用jdk9开始提供的 MethodHandles#privateLookupIn(java.lang.Class, java.lang.invoke.MethodHandles.Lookup)方法.

注意:接口没有实现,不能直接通过反射调用!!

以下是针对不同版本编写的一个工具类

java">import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;public final class MethodHandlesUtil {private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;private static Constructor<MethodHandles.Lookup> java8LookupConstructor;private static Method privateLookupInMethod;static {//jdk9开始提供的java.lang.invoke.MethodHandles.privateLookupIn方法try {privateLookupInMethod = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);} catch (NoSuchMethodException e) {privateLookupInMethod = null;logger.info("There is no [java.lang.invoke.MethodHandles.privateLookupIn(Class, Lookup)] method in this version of JDK");}//jdk8其实也适用于jdk9及以上的版本,但是上面优先,可以避免 jdk9 反射警告if (privateLookupInMethod == null) {try {java8LookupConstructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);java8LookupConstructor.setAccessible(true);} catch (NoSuchMethodException e) {//可能是jdk8 以下版本throw new IllegalStateException("There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",e);}}}/*** java9中的MethodHandles.lookup()方法返回的Lookup对象* 有权限访问specialCaller != lookupClass()的类* 但是只能适用于接口, {@link java.lang.invoke.MethodHandles.Lookup#checkSpecialCaller}*/public static MethodHandles.Lookup lookup(Class<?> callerClass) {//使用反射,因为当前jdk可能不是java9或以上版本if (privateLookupInMethod != null) {try {return (MethodHandles.Lookup) privateLookupInMethod.invoke(MethodHandles.class, callerClass, MethodHandles.lookup());} catch (IllegalAccessException | InvocationTargetException e) {throw new RuntimeException(e);}}//jdk8try {return java8LookupConstructor.newInstance(callerClass, ALLOWED_MODES);} catch (Exception e) {throw new IllegalStateException("no 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.", e);}}public static MethodHandle getSpecialMethodHandle(Method parentMethod) {final Class<?> declaringClass = parentMethod.getDeclaringClass();MethodHandles.Lookup lookup = lookup(declaringClass);try {return lookup.unreflectSpecial(parentMethod, declaringClass);} catch (IllegalAccessException e) {throw new RuntimeException(e);}}
}

简单使用,执行 MyInterface#call方法调用

利用JDK动态代理创建一个代理类

java">public static void main(String[] args) {// 创建JDK代理类MyInterface myInterface = (MyInterface) Proxy.newProxyInstance(MyInterface.class.getClassLoader(),new Class[]{MyInterface.class},(proxy, method, arg) -> MethodHandlesUtil.getSpecialMethodHandle(method).bindTo(proxy).invokeWithArguments(arg));// 调用call方法myInterface.call("MyInterface#call");
}

执行结果

可能有人就要问了,为什么不能直接调用 method.invoke(proxy, arg)?

原因很简单,JDK动态代理接口的对象本身就是java.lang.reflect.InvocationHandler,如果再调用method.invoke(proxy, arg),那么就会陷入无限循环中。因此,只能采取其他方式,比如以上获取的是接口的默认实现方法;当然,如果是接口的抽象方法,那么就可以在代理方法java.lang.reflect.InvocationHandler#invoke实现自己的逻辑,比如调用外部接口的处理逻辑等等。

最后,感谢大佬,借鉴:java8中实现调用对象的父类方法_privatelookupin-CSDN博客


http://www.ppmy.cn/embedded/38113.html

相关文章

《21天学通C++》(第十九章)STL集合类(set和multiset)

为什么需要set和multiset: 1.自动排序&#xff1a; set和multiset会自动按照元素的值进行排序。 2.快速查找&#xff1a; 由于元素是有序的&#xff0c;set和multiset可以提供对元素的快速查找&#xff0c;通常是基于二叉搜索树实现的&#xff0c;查找操作的时间复杂度为O(log …

[HBCPC2023] Sakura(笛卡尔树)

Given A 1 , A 2 , ⋯ , A n A_1,A_2,⋯,A_n A1​,A2​,⋯,An​, please count the number of valid pairs of ( l , r l,r l,r) where l ≤ r l≤r l≤r and A l A r m a x i l r A i A_lA_rmax_{il}^rA_i Al​Ar​maxilr​Ai​. Input format: The first line contai…

每日一博 - 闲聊架构设计中的多级缓存设计

文章目录 方法论概述客户端缓存应用层缓存服务层缓存缓存设计的注意事项总结 思维导图戳这里 方法论概述 从客户端到服务层&#xff0c;缓存的应用广泛而重要。通过合理的缓存设计&#xff0c;能够有效地提高系统的性能并降低延迟。 客户端缓存 在客户端层面&#xff0c;浏览…

QT 信号与槽的初步理解

信号究竟是由谁发出的? 当MainWindow1中的某个button点击触发了clicked事件,这个信号是由ui->button发出的 ...connect(ui->button, SIGNAL(clicked()), this, SLOT(example_slot()));...void MainWindow::example_slot() {//do something } 当同时存在两个窗口时: …

考研入门55问---基础知识篇

考研入门55问---基础知识篇 01 &#xff1e;什么是研究生入学考试&#xff1f; 研究生是指大专和本科之后的深造课程。以研究生为最高学历, 研究生毕业后&#xff0c;也可称研究生&#xff0c;含义为研究生学历的人。在中国大陆地区&#xff0c;普通民众一般也将硕士毕业生称…

人工智能的关键技术

人工智能的关键技术 概念方面学习算法 形式模型价值机器学习 1.自动用“模型”匹配“数据” 2.训练“模型”学习“数据” 机器学习算法及应用(热点&#xff09; 回归、 聚类、 分类神经网络&#xff08;输入、输出、变量权重、特征&#xff09;深度学习&#xff08;多等级特征…

【C++语言】继承

继承&#xff08;Inheritance&#xff09;是面向对象编程&#xff08;Object-Oriented Programming, OOP&#xff09;中的一个重要概念&#xff0c;它允许一个类&#xff08;称为子类或派生类&#xff09;基于另一个类&#xff08;称为父类或基类&#xff09;来构建。在C语言中…

PHPStudy 下载PHP提示“当前网络不稳定,下载失败”

错误信息 当前网络不稳定&#xff0c;下载失败 获取下载链接失败&#xff0c;请检查网络 假查网络 问题原因 xp.cn服务器的网络不稳定&#xff0c;不是你电脑的网络问题。 解决办法 第一步&#xff1a;下载现成的PHP文件 直接下载现成的文件&#xff0c;放到php目录。 将…