网站首页 > 精选教程 正文
从JDK 8开始,我们可以在接口中定义默认方法(在《Java编码问题,第一版》的问题198中有所涉及)。例如,让我们考虑以下接口(为了简洁起见,这些接口中的所有方法都声明为默认方法):
图 2.26 - 接口:Printable、Writable、Draft 和 Book
接下来,假设我们想要使用Java Reflection API来调用这些默认方法。在《Java编码问题,第一版》的第7章(Java反射类、接口、构造函数、方法和字段)中,我们涵盖了大量Java Reflection API主题,包括问题165中的java.lang.reflect.Proxy API。虽然我希望您能查看问题165以获取更多详细信息,但简要提醒一下,Proxy类的目标是提供运行时创建接口动态实现的支持。话虽如此,让我们看看如何使用Proxy API来调用我们的默认方法。
JDK 8
在JDK 8中调用接口的默认方法依赖于一个小技巧。基本上,我们从Lookup API从头开始创建一个包私有构造函数。接下来,我们使这个构造函数可访问——这意味着Java不会检查此构造函数的访问修饰符,因此当我们尝试使用它时不会抛出IllegalAccessException。最后,我们使用此构造函数来包装一个接口实例(例如,Printable),并使用反射访问该接口中声明的默认方法。因此,在代码行中,我们可以按如下方式调用默认方法Printable.print():
// 调用 Printable.print(String)
Printable pproxy = (Printable) Proxy.newProxyInstance(
Printable.class.getClassLoader(),
new Class<?>[]{Printable.class}, (o, m, p) -> {
if (m.isDefault()) {
Constructor<Lookup> cntr = Lookup.class
.getDeclaredConstructor(Class.class);
cntr.setAccessible(true);
return cntr.newInstance(Printable.class)
.in(Printable.class)
.unreflectSpecial(m, Printable.class)
.bindTo(o)
.invokeWithArguments(p);
}
return null;
});
// 调用 Printable.print()
pproxy.print("Chapter 2");
接下来,让我们关注Writable和Draft接口。Draft扩展了Writable并覆盖了默认的write()方法。现在,每次我们明确调用Writable.write()方法时,我们期望在幕后自动调用Draft.write()方法。一个可能的实现如下所示:
// 调用 Draft.write(String) 和 Writable.write(String)
Writable dpproxy = (Writable) Proxy.newProxyInstance(
Writable.class.getClassLoader(),
new Class<?>[]{Writable.class, Draft.class}, (o, m, p) -> {
if (m.isDefault() && m.getName().equals("write")) {
// ...(此处省略了与JDK 8示例类似的代码)
}
return null;
});
// 调用 Writable.write(String)
dpproxy.write("Chapter 1");
最后,让我们关注Printable和Book接口。Book扩展了Printable并没有定义任何方法。因此,当我们调用继承的print()方法时,我们期望调用Printable.print()方法。虽然你可以在捆绑的代码中检查这个解决方案,但让我们使用JDK 9+来处理相同的任务。
JDK 9+,预JDK 16
正如您刚刚看到的,在JDK 9之前,Java Reflection API提供了对非公共类成员的访问。这意味着外部反射代码(例如,第三方库)可以深入访问JDK内部。但是,从JDK 9开始,这是不可能的,因为新的模块系统依赖于强封装。为了从JDK 8到JDK 9的平稳过渡,我们可以使用--illegal-access选项。此选项的值范围从deny(维持强封装,因此不允许非法的反射代码)到permit(最强的封装级别的最宽松级别,仅允许从未命名模块访问平台模块)。在permit(JDK 9中的默认值)和deny之间,我们还有另外两个值:warn和debug。在这种情况下,前面的代码在JDK 9+中可能无法工作,或者它仍然可以工作但您会看到一个警告,如“WARNING: An illegal reflective access operation has occurred”。但是,我们可以通过MethodHandles来“修复”我们的代码,以避免非法的反射访问。除了其他优点外,这个类还暴露了用于为字段和方法创建方法句柄的查找方法。一旦我们有了Lookup,我们就可以依赖其findSpecial()来获取对接口默认方法的访问。基于MethodHandles,我们可以按如下方式调用默认方法Printable.print():
// 调用 Printable.print(String doc)
Printable pproxy = (Printable) Proxy.newProxyInstance(
// ...(与前面的代码类似,但使用MethodHandles)
);
// 调用 Printable.print()
pproxy.print("Chapter 2");
虽然在捆绑的代码中您可以看到更多示例,但让我们从JDK 16开始处理相同的主题。
JDK 16+
从JDK 16开始,我们可以简化前面的代码,这要归功于新的静态方法InvocationHandler.invokeDefault()。顾名思义,此方法对于调用默认方法很有用。在代码行中,我们之前的调用Printable.print()的示例可以通过invokeDefault()简化为如下形式:
// 调用 Printable.print(String doc)
Printable pproxy = (Printable) Proxy.newProxyInstance(
// ...(与前面的代码类似,但使用invokeDefault())
);
// 调用 Printable.print()
pproxy.print("Chapter 2");
在下一个示例中,每次我们明确调用Writable.write()方法时,我们期望在幕后自动调用Draft.write()方法:
// 调用 Draft.write(String) 和 Writable.write(String)
Writable dpproxy = (Writable) Proxy.newProxyInstance(
// ...(与前面的代码类似,但处理Draft和Writable的write方法)
);
// 调用 Writable.write(String)
dpproxy.write("Chapter 1");
在捆绑的代码中,您可以练习更多示例。
- 上一篇: 接口性能优化技巧,有点硬
- 下一篇: java什么是接口?接口有什么作用?接口如何使用?
猜你喜欢
- 2025-01-09 Java之接口的定义与实现举例
- 2025-01-09 pyhton面向对象接口的实现
- 2025-01-09 实战技巧:JMeter接口自动化测试之15分钟完成60个用例!
- 2025-01-09 六个调用第三方接口遇到的大坑
- 2025-01-09 Java 关键字之 native 详解
- 2025-01-09 开放内网API接口的低代码开发工具和解决方案-派框架·接口大师
- 2025-01-09 java高级用法之:无所不能的java,本地方法调用实况
- 2025-01-09 Spring Security 接口认证鉴权入门实践指南
- 2025-01-09 java什么是接口?接口有什么作用?接口如何使用?
- 2025-01-09 接口性能优化技巧,有点硬
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- nginx反向代理 (57)
- nginx日志 (56)
- nginx限制ip访问 (62)
- mac安装nginx (55)
- java和mysql (59)
- java中final (62)
- win10安装java (72)
- java启动参数 (64)
- java链表反转 (64)
- 字符串反转java (72)
- java逻辑运算符 (59)
- java 请求url (65)
- java信号量 (57)
- java定义枚举 (59)
- java字符串压缩 (56)
- java中的反射 (59)
- java 三维数组 (55)
- java插入排序 (68)
- java线程的状态 (62)
- java异步调用 (55)
- java中的异常处理 (62)
- java锁机制 (54)
- java静态内部类 (55)
- java怎么添加图片 (60)
- java 权限框架 (55)
本文暂时没有评论,来添加一个吧(●'◡'●)