漫反射 高光反射
本文是我们名为“ 高级Java ”的学院课程的一部分。
本课程旨在帮助您最有效地使用Java。 它讨论了高级主题,包括对象创建,并发,序列化,反射等。 它将指导您完成Java掌握的旅程! 在这里查看 !
1.简介
在本教程的这一部分中,我们将简要介绍一个非常有趣的主题,即反射 。 反射是程序在运行时检查或自检的能力。 反射是一项非常有用且功能强大的功能,它可以极大地扩展程序的功能,以在执行过程中执行其自己的检查,修改或转换,而无需一行代码更改。 并非每种编程语言实现都支持此功能,但是幸运的是Java自一开始就采用了此功能。
2.反射API
反射API是Java标准库的一部分,它提供了一种在运行时探索内部类详细信息,动态创建新类实例(无需显式使用new
运算符),动态调用方法,自省注释(已添加注释)的方法。在教程的第5部分“ 如何以及何时使用Enums和Annotations”中进行了介绍 ,以及更多其他内容。 它使Java开发人员可以自由编写代码,这些代码可以在运行时自行调整,验证,执行甚至修改自己。
Reflection API以非常直观的方式设计,并托管在java.lang.reflect
包下。 它的结构严格遵循Java语言概念,并具有表示类(包括通用版本),方法,字段(成员),构造函数,接口,参数和注释的所有元素。 Reflection API的入口点是Class< ? >
Class< ? >
类。 例如,列出String类的所有公共方法的最简单方法是使用getMethods()
方法调用:
final Method[] methods = String.class.getMethods();
for( final Method method: methods ) {
System.out.println( method.getName() );
}
按照相同的原则,我们可以使用getFields()
方法调用列出String类的所有公共字段,例如:
final Field[] fields = String.class.getFields();
for( final Field field: fields ) {
System.out.println( field.getName() );
}
继续使用反射类对String类进行实验,让我们尝试创建一个新实例,并在其上调用length()
方法,所有这些仅通过使用反射API即可 。
final Constructor< String > constructor = String.class.getConstructor( String.class );
final String str = constructor.newInstance( "sample string" );
final Method method = String.class.getMethod( "length" );
final int length = ( int )method.invoke( str );
// The length of the string is 13 characters
可能最需要反射的用例围绕注释处理。 注释本身(不包括Java标准库中的注释)对代码没有任何影响。 但是,Java应用程序可以使用反射在运行时检查它们感兴趣的不同Java元素上存在的注释,并根据注释及其属性应用某些逻辑。 例如,让我们看一下自省的方式是否存在于类定义中:
@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.TYPE )
public @interface ExampleAnnotation {
// Some attributes here
}
@ExampleAnnotation
public class ExampleClass {
// Some getter and setters here
}
使用反射API ,可以使用getAnnotation()
方法调用轻松完成。 返回的非null值表示存在注释,例如:
final ExampleAnnotation annotation =
ExampleClass.class.getAnnotation( ExampleAnnotation.class );
if( annotation != null ) {
// Some implementation here
}
如今,大多数Java API都包含注释,以方便开发人员使用和集成它们。 非常流行的Java规范的最新版本,例如RESTful Web服务的Java API ( https://jcp.org/en/jsr/detail?id=339 ), Bean验证 ( https://jcp.org/en/jsr / detail?id = 349 ), Java临时缓存API ( https://jcp.org/en/jsr/detail?id=107 ), Java消息服务 ( https://jcp.org/en/jsr/detail? id = 343 ), Java Persistence ( https://jcp.org/en/jsr/detail?id=338 )以及许多其他构建在注释之上,它们的实现通常大量使用Reflection API来收集有关正在运行的应用程序。
3.访问泛型类型参数
自从引入泛型(本教程的第4部分“ 如何和何时使用泛型”介绍了泛型 )以来, Reflection API已得到扩展,以支持对泛型类型的自省。 在许多不同的应用程序中经常弹出的用例是弄清楚用其声明了特定类,方法或其他元素的通用参数的类型。 让我们看一下示例类声明:
public class ParameterizedTypeExample {
private List< String > strings;
public List< String > getStrings() {
return strings;
}
}
现在,在使用Reflection检查类时,很容易知道将strings
属性声明为具有String
类型参数的泛型类型List
。 下面的代码段说明了如何实现:
final Type type = ParameterizedTypeExample.class
.getDeclaredField( "strings" ).getGenericType();
if( type instanceof ParameterizedType ) {
final ParameterizedType parameterizedType = ( ParameterizedType )type;
for( final Type typeArgument: parameterizedType.getActualTypeArguments() ) {
System.out.println( typeArgument );
}
}
以下通用类型参数将被打印在控制台上:
class java.lang.String
4.反射API和可见性
在本教程的第1部分“ 如何创建和销毁对象”中 ,我们第一次遇到了Java语言支持的可访问性和可见性规则。 可能令人惊讶,但是反射API能够以某种方式修改给定类成员的可见性规则。
让我们看一下带有单个私有字段名称的类的以下示例。 提供了此字段的getter,但没有提供setter,这是有意为之。
public static class PrivateFields {
private String name;
public String getName() {
return name;
}
}
显然,对于任何Java开发人员而言,显然都无法使用Java语言语法构造来设置name
字段,因为该类无法提供实现此目的的方法。 关于救援的反射API ,让我们看看如何通过更改字段的可见性和可访问范围来完成。
final PrivateFields instance = new PrivateFields();
final Field field = PrivateFields.class.getDeclaredField( "name" );
field.setAccessible( true );
field.set( instance, "sample name" );
System.out.println( instance.getName() );
以下输出将打印在控制台上:
sample name
请注意,如果没有field.setAccessible( true )
调用,则会在运行时引发异常,表明无法访问带有修饰符private
的类的成员。
反射API的此功能经常由测试支架或依赖项注入框架使用,以便访问内部(或不可暴露)的实现细节。 除非您别无选择,否则请尝试避免在应用程序中使用它。
5.反射API陷阱
另外,请注意,即使反射API非常强大,也有一些陷阱。 首先,它是安全权限的主题,可能并非在您的代码正在其上运行的所有环境中都可用。 其次,它可能会对您的应用程序性能产生影响。 从执行前景来看,对反射API的调用非常昂贵。
最后, 反射API不能提供足够的类型安全保证,迫使开发人员在大多数地方使用Object实例,并且在转换构造函数/方法参数或方法返回值方面受到很大限制。
自Java 7发行以来,有一些有用的功能可以提供更快,另一种方法来访问某些功能,这些功能以前只能通过反射调用来使用。 下一节将向您介绍它们。
6.方法句柄
Java 7版本向JVM和Java标准库(方法句柄)引入了一个非常重要的新功能。 方法句柄是对基础方法,构造函数或字段(或类似的低级操作)的类型化,直接可执行的引用,具有自变量或返回值的可选转换。 在许多方面,它们是使用Reflection API执行的方法调用的更好替代方法。 让我们看一下使用方法句柄动态调用String
类上的方法length()
的代码片段。
final MethodHandles.Lookup lookup = MethodHandles.lookup();
final MethodType methodType = MethodType.methodType( int.class );
final MethodHandle methodHandle =
lookup.findVirtual( String.class, "length", methodType );
final int length = ( int )methodHandle.invokeExact( "sample string" );
// The length of the string is 13 characters
上面的示例不是很复杂,只是概述了方法处理能力的基本概念。 请将其与使用“ 反射API ”部分中的“ 反射API”的相同实现进行比较。 但是,它看起来确实有些冗长,但是从性能和类型安全性角度来看,预期的方法句柄是更好的选择。
方法句柄是非常强大的工具,它们为在JVM平台上有效实现动态(和脚本)语言奠定了必要的基础。 在本教程的第12部分 , 动态语言支持中 ,我们将介绍其中的几种语言。
7.方法参数名称
Java开发人员多年来面临的一个众所周知的问题是,方法参数名称没有在运行时保留,而是被彻底清除。 几个社区项目,例如Paranamer ( https://github.com/paul-hammant/paranamer ),试图通过向生成的字节码中注入一些其他元数据来解决此问题。 幸运的是,Java 8通过引入新的编译器参数–parameters
改变了这一点,该–parameters
将确切的方法参数名称注入字节码中。 让我们看一下以下方法:
public static void performAction( final String action, final Runnable callback ) {
// Some implementation here
}
在下一步中,让我们使用Reflection API检查该方法的方法参数名称,并确保保留它们:
final Method method = MethodParameterNamesExample.class
.getDeclaredMethod( "performAction", String.class, Runnable.class );
Arrays.stream( method.getParameters() )
.forEach( p -> System.out.println( p.getName() ) );
指定了-parameters
编译器选项后,以下参数名称将打印在控制台上:
action
callback
对于许多Java库和框架的开发人员来说,这一期待已久的功能确实使他们大为欣慰。 从现在开始,仅使用纯Java Reflection API即可提取更多有用的元数据,而无需引入任何其他变通方法(或黑客手段)。
8.接下来
在本教程的这一部分中,我们介绍了反射API ,它是检查代码,从代码中提取有用的元数据甚至进行修改的方法。 尽管存在所有缺点,但反射API如今已在大多数(如果不是全部)Java应用程序中得到了广泛使用。 在本教程的下一部分中,我们将讨论Java中的脚本和动态语言支持。
9.下载源代码
这是高级Java课程的第11部分“ 反射” 课程 。 您可以在此处下载源代码: advanced-java-part-11
翻译自: https://www.javacodegeeks.com/2015/09/how-to-use-reflection-effectively.html
漫反射 高光反射