题
什么是反射,为什么它有用?
我对 Java 特别感兴趣,但我认为任何语言的原理都是相同的。
解决方案
名称反射用于描述能够检查同一系统(或自身)中的其他代码的代码。
例如,假设您有一个 Java 中未知类型的对象,并且您希望对其调用“doSomething”方法(如果存在)。Java 的静态类型系统并不是真正设计来支持这一点,除非对象符合已知的接口,但是使用反射,您的代码可以查看该对象并找出它是否有一个名为“doSomething”的方法,然后在您需要的情况下调用它想要。
因此,给您一个 Java 代码示例(假设有问题的对象是 foo):
Method method = foo.getClass().getMethod("doSomething", null);
method.invoke(foo, null);
Java 中一种非常常见的用例是使用注释。例如,JUnit 4 将使用反射来查找类中带有 @Test 注释标记的方法,然后在运行单元测试时调用它们。
有一些很好的反思示例可以帮助您开始 http://docs.oracle.com/javase/tutorial/reflect/index.html
最后,是的,这些概念在其他支持反射的静态类型语言(如 C#)中非常相似。在动态类型语言中,上述用例不太必要(因为编译器将允许在任何对象上调用任何方法,如果不存在则在运行时失败),但第二种情况是查找标记或以某种方式工作仍然很常见。
从评论更新:
检查系统中的代码并看到对象类型的能力不是反射,而是输入内省。然后,反射是通过内省进行在运行时进行修改的能力。在这里有区别是必要的,因为某些语言支持内省,但不支持反思。一个这样的例子是C ++
其他提示
反射 是一种语言检查和动态调用类、方法、属性等的能力。在运行时。
例如,Java中的所有对象都有方法 getClass()
, ,它可以让您确定对象的类,即使您在编译时不知道它(例如如果你将其声明为 Object
) - 这可能看起来微不足道,但这种反射在不太动态的语言中是不可能的,例如 C++
. 。更高级的用途让您可以列出和调用方法、构造函数等。
反射很重要,因为它可以让您编写不必在编译时“了解”所有内容的程序,从而使它们更加动态,因为它们可以在运行时绑定在一起。可以针对已知接口编写代码,但可以使用配置文件的反射来实例化要使用的实际类。
正是出于这个原因,许多现代框架广泛使用反射。大多数其他现代语言也使用反射,并且在脚本语言(例如 Python)中,它们的集成更加紧密,因为在这些语言的通用编程模型中感觉更自然。
我最喜欢的反射用法之一是下面的 Java 转储方法。它接受任何对象作为参数,并使用 Java 反射 API 打印出每个字段名称和值。
import java.lang.reflect.Array;
import java.lang.reflect.Field;
public static String dump(Object o, int callCount) {
callCount++;
StringBuffer tabs = new StringBuffer();
for (int k = 0; k < callCount; k++) {
tabs.append("\t");
}
StringBuffer buffer = new StringBuffer();
Class oClass = o.getClass();
if (oClass.isArray()) {
buffer.append("\n");
buffer.append(tabs.toString());
buffer.append("[");
for (int i = 0; i < Array.getLength(o); i++) {
if (i < 0)
buffer.append(",");
Object value = Array.get(o, i);
if (value.getClass().isPrimitive() ||
value.getClass() == java.lang.Long.class ||
value.getClass() == java.lang.String.class ||
value.getClass() == java.lang.Integer.class ||
value.getClass() == java.lang.Boolean.class
) {
buffer.append(value);
} else {
buffer.append(dump(value, callCount));
}
}
buffer.append(tabs.toString());
buffer.append("]\n");
} else {
buffer.append("\n");
buffer.append(tabs.toString());
buffer.append("{\n");
while (oClass != null) {
Field[] fields = oClass.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
buffer.append(tabs.toString());
fields[i].setAccessible(true);
buffer.append(fields[i].getName());
buffer.append("=");
try {
Object value = fields[i].get(o);
if (value != null) {
if (value.getClass().isPrimitive() ||
value.getClass() == java.lang.Long.class ||
value.getClass() == java.lang.String.class ||
value.getClass() == java.lang.Integer.class ||
value.getClass() == java.lang.Boolean.class
) {
buffer.append(value);
} else {
buffer.append(dump(value, callCount));
}
}
} catch (IllegalAccessException e) {
buffer.append(e.getMessage());
}
buffer.append("\n");
}
oClass = oClass.getSuperclass();
}
buffer.append(tabs.toString());
buffer.append("}\n");
}
return buffer.toString();
}
反射的用途
反射通常由需要检查或修改在 Java 虚拟机中运行的应用程序的运行时行为的能力的程序使用。这是一个相对高级的功能,只能由对语言基础知识有很强掌握的开发人员使用。考虑到这一点,反射是一种强大的技术,可以使应用程序执行原本不可能完成的操作。
可扩展性特征
应用程序可以通过使用完全限定名称创建可扩展性对象的实例来使用外部的用户定义的类。类浏览器和视觉开发环境类浏览器需要能够列举课程成员。可视化开发环境可以受益于利用反射中可用的类型信息来帮助开发人员编写正确的代码。调试器和测试工具调试器需要能够检查课堂上的私人成员。测试工具可以利用反射来系统地调用在类上定义的可发现的 API 集,以确保测试套件中的高水平代码覆盖率。
反射的缺点
反射很强大,但不应该乱用。如果可以在不使用反射的情况下执行操作,那么最好避免使用它。通过反射访问代码时应牢记以下问题。
- 性能开销
由于反射涉及动态解析的类型,因此无法执行某些 Java 虚拟机优化。因此,反射操作的性能比非反射操作慢,因此应避免在性能敏感的应用程序中频繁调用的代码段中使用反射操作。
- 安全限制
反射需要运行时权限,在安全管理器下运行时可能不存在该权限。对于必须在受限安全上下文(例如 Applet)中运行的代码,这是一个重要的考虑因素。
- 内部结构暴露
由于反射允许代码执行在非反射代码中非法的操作,例如访问私有字段和方法,因此使用反射可能会导致意外的副作用,这可能会导致代码功能失调并可能破坏可移植性。反射代码破坏了抽象,因此可能会随着平台的升级而改变行为。
来源: 反射 API
并非每种语言都支持反射,但支持反射的语言的原理通常是相同的。
反思是“反思”程序结构的能力。或者更具体。查看您拥有的对象和类,并以编程方式获取有关它们实现的方法、字段和接口的信息。您还可以查看注释之类的内容。
它在很多情况下都很有用。无论您希望能够在哪里动态地将类插入到您的代码中。许多对象关系映射器使用反射来实例化数据库中的对象,而无需事先知道它们将使用什么对象。插件架构是反射有用的另一个地方。在这些情况下,能够动态加载代码并确定是否存在实现正确接口以用作插件的类型非常重要。
反射允许在运行时动态实例化新对象、调用方法以及对类变量进行获取/设置操作,而无需事先了解其实现。
Class myObjectClass = MyObject.class;
Method[] method = myObjectClass.getMethods();
//Here the method takes a string parameter if there is no param, put null.
Method method = aClass.getMethod("method_name", String.class);
Object returnValue = method.invoke(null, "parameter-value1");
在上面的示例中,空参数是您要调用该方法的对象。如果该方法是静态的,则提供 null。如果该方法不是静态的,那么在调用时您需要提供一个有效的 MyObject 实例而不是 null。
反射还允许您访问类的私有成员/方法:
public class A{
private String str= null;
public A(String str) {
this.str= str;
}
}
.
A obj= new A("Some value");
Field privateStringField = A.class.getDeclaredField("privateString");
//Turn off access check for this field
privateStringField.setAccessible(true);
String fieldValue = (String) privateStringField.get(obj);
System.out.println("fieldValue = " + fieldValue);
- 对于类的检查(也称为内省),您不需要导入反射包(
java.lang.reflect
)。类元数据可以通过以下方式访问java.lang.Class
.
反射是一个非常强大的 API,但如果过度使用,它可能会减慢应用程序的速度,因为它会在运行时解析所有类型。
例子 :
以远程应用程序为例,它为您的应用程序提供一个对象,您可以使用其 API 方法获取该对象。现在,根据对象,您可能需要执行某种计算。
提供者保证对象可以是3种类型,我们需要根据对象的类型进行计算。
因此,我们可以在 3 个类中实现,每个类包含不同的逻辑。显然,对象信息在运行时可用,因此您无法静态编码来执行计算,因此反射用于实例化您需要基于以下内容执行计算的类的对象:从提供者收到的对象。
Java Reflection 非常强大并且非常有用。Java 反射使之成为可能 在运行时检查类、接口、字段和方法, 不知道类的名称、方法等。在编译时。也可以 使用反射实例化新对象、调用方法以及获取/设置字段值。
一个快速的 Java 反射示例向您展示如何使用反射:
Method[] methods = MyObject.class.getMethods();
for(Method method : methods){
System.out.println("method = " + method.getName());
}
此示例从名为 MyObject 的类获取 Class 对象。该示例使用类对象获取该类中的方法列表,迭代这些方法并打印出它们的名称。
编辑: :近一年后,我正在编辑这个答案,因为在阅读有关反射的内容时,我很少再使用反射。
- Spring使用bean配置,例如:
<bean id="someID" class="com.example.Foo">
<property name="someField" value="someValue" />
</bean>
当 Spring 上下文处理这个 <bean> 元素时,它将使用 Class.forName(String) 和参数“com.example.Foo”来实例化该 Class。
然后,它将再次使用反射来获取 <property> 元素的适当设置器,并将其值设置为指定值。
- Junit 特别使用反射来测试私有/受保护的方法。
对于私有方法,
Method method = targetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);
对于私人领域,
Field field = targetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
据我的理解:
反射允许程序员动态访问程序中的实体。IE。在编写应用程序时,如果程序员不知道某个类或其方法,他可以通过使用反射动态(在运行时)使用此类。
常用于类名频繁变化的场景。如果出现这种情况,那么程序员一次又一次地重写应用程序并更改类的名称是很复杂的。
相反,通过使用反射,需要担心可能更改的类名。
反射 是一个API,用于检查或修改 方法、类、接口 在运行时。
- 反思所需的课程在下面提供
java.lang.reflect package
. - 反射为我们提供了有关对象所属类的信息以及可以使用该对象执行的该类的方法。
- 通过反射,我们可以在运行时调用方法,而不管它们使用的访问说明符如何。
这 java.lang
和 java.lang.reflect
包提供了java反射的类。
反射 可用于获取有关以下内容的信息:
班级 这
getClass()
方法用于获取对象所属类的名称。构造函数 这
getConstructors()
方法用于获取对象所属类的公共构造函数。方法 这
getMethods()
method 用于获取对象所属类的公共方法。
这 反射API 主要用于:
IDE(集成开发环境)例如Eclipse、MyEclipse、NetBeans 等
调试器和测试工具等
使用反射的优点:
可扩展性特点: 应用程序可以通过使用完全限定名称创建可扩展性对象的实例来使用外部的用户定义的类。
调试和测试工具: 调试器使用反射属性来检查类的私有成员。
缺点:
性能开销: 反射操作的性能比非反射操作慢,因此应避免在性能敏感的应用程序中频繁调用的代码段中使用反射操作。
内部结构暴露: 反射代码破坏了抽象,因此可能会随着平台的升级而改变行为。
反射是一组函数,允许您访问程序的运行时信息并修改其行为(有一些限制)。
它很有用,因为它允许您根据程序的元信息更改运行时行为,也就是说,您可以检查函数的返回类型并更改处理情况的方式。
例如,在 C# 中,您可以在运行时加载程序集(.dll)并检查它,浏览类并根据您发现的内容采取操作。它还允许您在运行时创建类的实例、调用其方法等。
能用在什么地方呢?不是每次都有用,而是针对具体情况。例如,您可以使用它来获取用于登录目的的类名称,根据配置文件上指定的内容动态创建事件处理程序等等......
简单的反思例子。在国际象棋游戏中,您不知道用户在运行时会移动什么。反射可用于调用运行时已实现的方法。
public class Test {
public void firstMoveChoice(){
System.out.println("First Move");
}
public void secondMOveChoice(){
System.out.println("Second Move");
}
public void thirdMoveChoice(){
System.out.println("Third Move");
}
public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Test test = new Test();
Method[] method = test.getClass().getMethods();
//firstMoveChoice
method[0].invoke(test, null);
//secondMoveChoice
method[1].invoke(test, null);
//thirdMoveChoice
method[2].invoke(test, null);
}
}
反射就是让物体看到自己的样子。这个论点似乎与反思无关。其实,这就是“自我认同”能力。
反射本身就是Java、C#等缺乏自知之明、自感知能力的语言的代名词。因为他们没有自知之明的能力,所以当我们想要观察它是什么样子的时候,我们必须有另外一个东西来反思它是什么样子。优秀的动态语言如Ruby和Python可以感知自己的反射而无需其他个体的帮助。可以说,Java的对象在没有镜子的情况下无法感知到自己是什么样子,它是反射类的对象,但是Python中的对象在没有镜子的情况下可以感知到它的样子。这就是为什么我们在Java中需要反射。
我只想对列出的所有内容添加一些观点。
和 反射API 你可以写通用的 toString()
任何对象的方法。
它在调试时很有用。
这是一些例子:
class ObjectAnalyzer {
private ArrayList<Object> visited = new ArrayList<Object>();
/**
* Converts an object to a string representation that lists all fields.
* @param obj an object
* @return a string with the object's class name and all field names and
* values
*/
public String toString(Object obj) {
if (obj == null) return "null";
if (visited.contains(obj)) return "...";
visited.add(obj);
Class cl = obj.getClass();
if (cl == String.class) return (String) obj;
if (cl.isArray()) {
String r = cl.getComponentType() + "[]{";
for (int i = 0; i < Array.getLength(obj); i++) {
if (i > 0) r += ",";
Object val = Array.get(obj, i);
if (cl.getComponentType().isPrimitive()) r += val;
else r += toString(val);
}
return r + "}";
}
String r = cl.getName();
// inspect the fields of this class and all superclasses
do {
r += "[";
Field[] fields = cl.getDeclaredFields();
AccessibleObject.setAccessible(fields, true);
// get the names and values of all fields
for (Field f : fields) {
if (!Modifier.isStatic(f.getModifiers())) {
if (!r.endsWith("[")) r += ",";
r += f.getName() + "=";
try {
Class t = f.getType();
Object val = f.get(obj);
if (t.isPrimitive()) r += val;
else r += toString(val);
} catch (Exception e) {
e.printStackTrace();
}
}
}
r += "]";
cl = cl.getSuperclass();
} while (cl != null);
return r;
}
}
来自java文档 页
java.lang.reflect
包提供了用于获取有关类和对象的反射信息的类和接口。反射允许在安全限制内以编程方式访问有关加载类的字段、方法和构造函数的信息,并使用反射的字段、方法和构造函数对其底层对应项进行操作。
AccessibleObject
如有必要,允许禁止访问检查 ReflectPermission
可用。
该包中的类以及 java.lang.Class
容纳诸如调试器、解释器、对象检查器、类浏览器和服务等应用程序 Object Serialization
和 JavaBeans
需要访问目标对象的公共成员(基于其运行时类)或给定类声明的成员
它包括以下功能。
- 获取类对象,
- 检查类的属性(字段、方法、构造函数),
- 设置和获取字段值,
- 调用方法,
- 创建对象的新实例。
看看这个 文档 公开的方法的链接 Class
班级。
由此 文章 (作者:Dennis Sosnoski,Sosnoski Software Solutions, Inc. 总裁) 文章 (安全探索 pdf):
我可以看到与使用反射相比有相当大的缺点
反思的使用者:
- 它提供了动态链接程序组件的非常通用的方法
- 它对于创建以非常通用的方式处理对象的库很有用
反射的缺点:
- 当用于字段和方法访问时,反射比直接代码慢得多。
- 它可能会掩盖代码中实际发生的情况
- 它绕过源代码可能会造成维护问题
- 反射代码也比相应的直接代码更复杂
- 它允许违反关键Java安全限制,例如数据访问保护和类型安全
一般滥用行为:
- 加载受限类,
- 获取对受限类的构造函数、方法或字段的引用,
- 创建新的对象实例、方法调用、获取或设置受限类的字段值。
看看这个关于滥用反射功能的 SE 问题:
概括:
在系统代码中对其功能的不安全使用也很容易导致 Java 安全模式受到损害湖 所以请谨慎使用此功能
正如名称本身所暗示的那样,它除了提供在运行时动态调用方法创建实例的功能之外,还反映了它所拥有的内容,例如类方法等。
许多框架和应用程序都使用它来调用服务,而无需真正了解代码。
Reflection
有很多 用途. 。我更熟悉的是能够动态创建代码。
IE:动态类,功能,构造函数 - 基于任何数据(XML/array/sql Resultes/hardcoded/etc。)
反射使您能够编写更通用的代码。它允许您在运行时创建对象并在运行时调用其方法。因此,可以使程序高度参数化。它还允许内省对象和类以检测其暴露于外部世界的变量和方法。
我想通过例子来回答这个问题。首先 Hibernate
项目用途 Reflection API
生成 CRUD
语句来弥合正在运行的应用程序和持久性存储之间的鸿沟。当域中的事物发生变化时, Hibernate
必须了解它们才能将它们保存到数据存储中,反之亦然。
或者工作 Lombok Project
. 。它只是在编译时注入代码,导致代码被插入到您的域类中。(我认为 getter 和 setter 都可以)
Hibernate
选择了 reflection
因为它对应用程序的构建过程影响最小。
从 Java 7 开始我们有 MethodHandles
, ,其工作原理为 Reflection API
. 。在项目中,要使用记录器,我们只需复制粘贴以下代码:
Logger LOGGER = Logger.getLogger(MethodHandles.lookup().lookupClass().getName());
因为在这种情况下很难犯错。
我发现最好通过例子来解释,但似乎没有一个答案能做到这一点......
使用反射的一个实际示例是用 Java 编写的 Java 语言服务器或用 PHP 编写的 PHP 语言服务器等。语言服务器为您的 IDE 提供自动完成、跳转到定义、上下文帮助、提示类型等功能。为了让所有标签名称(可以自动完成的单词)在您按下时匹配,比方说, 标签 并显示所有提示,语言服务器必须检查有关该类的所有内容,包括其文档块和私有成员。为此,它需要反映该类。