什么是反思,为什么它有用?

什么是反射,它为什么有用?

我对Java特别感兴趣,但我认为任何语言的原则都是相同的。

1001675 次浏览

名称反射用于描述能够检查同一系统(或其本身)中的其他代码的代码。

例如,假设您在Java中有一个未知类型的对象,如果存在,您想在其上调用一个“do的东西”方法。Java的静态类型系统并不是真正设计来支持这一点的,除非对象符合已知接口,但是使用反射,您的代码可以查看该对象并找出它是否有一个名为“do的东西”的方法,然后根据需要调用它。

所以,在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++

反射是一组函数,它允许您访问程序的运行时信息并修改它的行为(有一些限制)。

它很有用,因为它允许您根据程序的元信息更改运行时行为,也就是说,您可以检查函数的返回类型并更改处理情况的方式。

例如,在C#中,您可以在运行时加载一个程序集(. dll)并检查它,在类中导航并根据您找到的内容采取行动。它还允许您在运行时创建类的实例,调用其方法等。

它在哪里有用?并不是每次都有用,而是用于具体情况。例如,您可以使用它来获取用于日志记录的类的名称,根据配置文件上指定的内容动态创建事件处理程序等等…

并非每种语言都支持反射,但支持反射的语言的原则通常是相同的。

反射是“反射”程序结构的能力。或者更具体。查看您拥有的对象和类,并以编程方式获取有关它们实现的方法、字段和接口的信息。您还可以查看注释等内容。

这在很多情况下都很有用。无论你希望能够动态地将类插入到你的代码中。许多对象关系映射器使用反射来实例化数据库中的对象,而无需事先知道它们将使用什么对象。插件架构是反射很有用的另一个地方。能够动态加载代码并确定那里是否有实现正确接口作为插件使用的类型在这些情况下很重要。

反射是一种语言在运行时检查和动态调用类、方法、属性等的能力。

例如,Java中的所有对象都有方法getClass(),它允许您确定对象的类,即使您在编译时不知道它(例如,如果您将其声明为Object)-这可能看起来微不足道,但在动态度较低的语言(如C++)中无法实现这种反射。更高级的使用允许您列出和调用方法、构造函数等。

反射很重要,因为它允许你编写不必在编译时“知道”所有内容的程序,使它们更具动态性,因为它们可以在运行时捆绑在一起。代码可以针对已知接口编写,但实际使用的类可以使用配置文件的反射实例化。

出于这个原因,许多现代框架广泛使用反射。大多数其他现代语言也使用反射,在脚本语言(如Python)中,它们的集成更加紧密,因为在这些语言的通用编程模型中感觉更自然。

反射是允许应用程序或框架使用可能尚未编写的代码的关键机制!

以典型的web.xml文件为例,它将包含一个servlet元素列表,其中包含嵌套的servlet类元素。servlet容器将处理web.xml文件,并通过反射创建每个servlet类的新实例。

另一个例子是XML Parsing(JAXP)的JavaAPI,其中XML解析器提供程序通过众所周知的系统属性“插入”,这些属性用于通过反射构建新实例。

最后,最全面的例子是Spring,它使用反射来创建它的bean,并且大量使用代理

我最喜欢的反射用法之一是下面的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();}

根据我的理解:

反射允许程序员动态地访问程序中的实体。即,在编写应用程序时,如果程序员不知道类或其方法,他可以通过使用反射动态地(在运行时)使用此类。

它经常用于类名频繁更改的场景。如果出现这种情况,那么程序员重写应用程序并一遍又一遍地更改类名是很复杂的。

相反,通过使用反射,需要担心可能更改的类名。

示例:

以一个远程应用程序为例,它为您的应用程序提供了一个您使用其API方法获得的对象。现在,基于该对象,您可能需要执行某种计算。

提供者保证对象可以是3种类型,我们需要根据对象的类型执行计算。

因此,我们可能会在3个类中实现,每个类都包含不同的逻辑。显然,对象信息在运行时可用,因此你不能静态编码来执行计算,因此反射用于实例化你需要的类的对象,以根据从提供者收到的对象执行计算。

Reflection有许多使用。我更熟悉的是,能够动态创建代码。

IE:动态类、函数、构造函数-基于任何数据(xml/数组/sql结果/硬编码/等…)

反射允许在运行时动态地实例化新对象、调用方法和对类变量进行get/set操作,而无需事先了解其实现。

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参数是您要调用方法的对象。如果方法是静态的,您提供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 fieldprivateStringField.setAccessible(true);
String fieldValue = (String) privateStringField.get(obj);System.out.println("fieldValue = " + fieldValue);
  • 对于类的检查(也称为自省),您不需要导入反射包(java.lang.reflect)。类元数据可以通过java.lang.Class访问。

反射是一个非常强大的API,但如果使用过量,它可能会减慢应用程序的速度,因为它会在运行时解析所有类型。

Java反射功能非常强大,非常有用。Java反射使得在运行时检查类、接口、字段和方法,在编译时不知道类、方法等的名称成为可能。也可以实例化新对象,调用方法并使用反射获取/设置字段值。

一个快速Java反射示例,向您展示使用反射的样子:

Method[] methods = MyObject.class.getMethods();
for(Method method : methods){System.out.println("method = " + method.getName());}

此示例从名为MyObject的类中获取Class对象。使用类对象,示例获取该类中方法的列表,迭代方法并打印出它们的名称。

这里解释了这一切是如何工作的

编辑:将近1年后,我正在编辑这个答案,因为在阅读反射时,我很少使用反射。

  • Spring使用bean配置,例如:


<bean id="someID" class="com.example.Foo"><property name="someField" value="someValue" /></bean>

当Spring上下文处理这个元素时,它将使用Class.forName(String)和参数com.example.Foo来实例化该Class。

然后,它将再次使用反射为<属性>元素获取适当的setter并将其值设置为指定值。

  • 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);

我只是想对所有列出的内容补充几点。

使用反射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 superclassesdo {r += "[";Field[] fields = cl.getDeclaredFields();AccessibleObject.setAccessible(fields, true);// get the names and values of all fieldsfor (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虚拟机中运行的应用程序的运行时行为的程序。这是一个相对高级的功能,应该只由对语言基础有很强掌握的开发人员使用。考虑到这一点,反射是一种强大的技术,可以使应用程序执行否则不可能的操作。

可扩展性特性

应用程序可以通过使用完全限定名称创建可扩展性对象的实例来使用外部用户定义的类。类浏览器和可视化开发环境类浏览器需要能够枚举类的成员。可视化开发环境可以从使用反射中可用的类型信息中受益,以帮助开发人员编写正确的代码。调试器和测试工具调试器需要能够检查类中的私有成员。测试工具可以利用反射来系统地调用定义在类上的可发现集API,以确保测试套件中的高水平代码覆盖率。

反思的缺点

反射很强大,但不应该不分青红皂白地使用。如果可以在不使用反射的情况下执行操作,那么最好避免使用它。通过反射访问代码时,应记住以下问题。

  • 性能开销

由于反射涉及动态解析的类型,因此无法执行某些Java虚拟机优化。因此,反射操作的性能比非反射操作慢,应避免在性能敏感应用程序中经常调用的代码段中。

  • 安全限制

反射需要在安全管理器下运行时可能不存在的运行时权限。对于必须在受限安全上下文(例如在Applet中)中运行的代码,这是一个重要的考虑因素。

  • 内部暴露

由于反射允许代码执行在非反射代码中是非法的操作,例如访问私有字段和方法,因此使用反射可能会导致意想不到的副作用,这可能会使代码功能失调并可能破坏可移植性。反射代码打破抽象,因此可能会随着平台升级而改变行为。

来源:反射API

简单的反射示例。在国际象棋游戏中,您不知道用户在运行时将移动什么。反射可用于调用已在运行时实现的方法:

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();//firstMoveChoicemethod[0].invoke(test, null);//secondMoveChoicemethod[1].invoke(test, null);//thirdMoveChoicemethod[2].invoke(test, null);}
}

反思就是让对象看到自己的样子。这个论点似乎与反思无关。事实上,这是“自我认同”的能力。

Reflection本身就是Java、C#这类缺乏自我认知和自我感知能力的语言的代名词。因为它们没有自我认知的能力,所以当我们想要观察它是什么样子的时候,就必须有另外一个东西来反省它是什么样子的。优秀的动态语言比如Ruby、Python都可以在不借助其他个体的情况下感知到自己的倒影。我们可以说,Java的对象在没有镜子的情况下是无法感知它的样子的,而镜子是反射类的对象,但是Python中的对象可以在没有镜子的情况下感知它。这就是为什么我们需要Java中的反射。

java留档页面

java.lang.reflect包提供了用于获取类和对象反射信息的类和接口。反射允许通过编程方式访问已加载类的字段、方法和构造函数的相关信息,以及在安全限制范围内使用反射字段、方法和构造函数对其底层对应物进行操作。

如果必要的ReflectPermission可用,AccessibleObject允许抑制访问检查。

此包中的类与java.lang.Class一起容纳调试器、解释器、对象检查器、类浏览器等应用程序以及Object SerializationJavaBeans等需要访问目标对象的公共成员(基于其运行时类)或给定类声明的成员的服务

它包括以下功能。

  1. 获取类对象,
  2. 检查类的属性(字段、方法、构造函数),
  3. 设置和获取字段值,
  4. 调用方法,
  5. 创建对象的新实例。

查看这个留档链接,了解#0类公开的方法。

从这个文章(由Sosnoski软件解决方案公司总裁Dennis Sosnoski撰写)和这个文章(安全探索pdf):

我可以看到相当多的缺点比使用反射

反射用户:

  1. 它提供了动态链接程序组件的通用方式
  2. 它对于创建以非常一般的方式处理对象的库很有用

反射的缺点:

  1. 当用于字段和方法访问时,反射比直接代码慢得多。
  2. 它可以掩盖你的代码里到底发生了什么
  3. 它绕过源代码会造成维护问题
  4. 反射代码也比相应的直接代码更复杂
  5. 它允许违反密钥Java安全约束,例如作为数据访问保护和类型安全

一般侵权行为:

  1. 加载受限制的类,
  2. 获取对受限类的构造函数、方法或字段的引用,
  3. 创建新对象实例、方法调用、获取或设置受限类的字段值。

看看这个关于滥用反射功能的SE问题:

如何读取Java中的私有字段?

总结:

从系统代码中不安全地使用其功能也很容易导致Java安全模式的妥协l.所以少用这个功能

反射使您能够编写更通用的代码。它允许您在运行时创建一个对象并在运行时调用其方法。因此,程序可以高度参数化。它还允许内省对象和类以检测暴露在外部世界的变量和方法。

反射是一个API,用于在运行时检查或修改方法、类、接口的行为。

  1. 反射所需的类在java.lang.reflect package下提供。
  2. 反射为我们提供了有关对象所属类的信息,以及可以使用该对象执行的该类的方法。
  3. 通过反射,我们可以在运行时调用方法,而不管与它们一起使用的访问说明符。

java.langjava.lang.reflect包为java反射提供了类。

反射可用于获取有关-

  1. getClass()方法用于获取对象所属类的名称。

  2. getConstructors()方法用于获取对象所属类的公共构造函数。

  3. getMethods()方法用于获取对象所属类的公共方法。

反射API主要用于:

IDE(集成开发环境),例如Eclipse、MyEclipse、NetBeans等。
调试器和测试工具等。

使用反射的优点:

可扩展性特点:应用程序可以通过使用完全限定名称创建可扩展性对象的实例来使用外部用户定义的类。

调试和测试工具:调试器使用反射属性来检查类上的私有成员。

缺点:

绩效成本:反射操作的性能比非反射操作慢,应该避免在性能敏感应用程序中经常调用的代码部分。

内部曝光:反射代码打破了抽象,因此可能会随着平台的升级而改变行为。

参考:Java反射javarevisited.blogspot.in

正如名称本身所暗示的那样,它反映了它所包含的内容,例如类方法等,除了提供功能来调用在运行时动态创建实例的方法。

许多框架和应用程序都使用它来调用服务,而无需实际了解代码。

我想举例回答这个问题。首先,Hibernate项目使用Reflection API生成CRUD语句来弥合正在运行的应用程序和持久性存储之间的鸿沟。当域中的事情发生变化时,Hibernate必须了解它们才能将它们持久化到数据存储中,反之亦然。

或者工作Lombok Project。它只是在编译时注入代码,导致代码被插入到您的域类中。(我认为对于getter和setter来说可以)

Hibernate选择了reflection,因为它对应用程序的构建过程影响最小。

从Java7开始,我们有MethodHandles,它与Reflection API一样工作。在项目中,要使用记录器,我们只需复制粘贴下一段代码:

Logger LOGGER = Logger.getLogger(MethodHandles.lookup().lookupClass().getName());

因为在这种情况下很难犯错字错误。

因为我发现最好用例子来解释,没有一个答案似乎能做到这一点…

使用反射的一个实际示例是Java编写的Java语言服务器或用PHP编写的PHP语言服务器等。Language Server为您提供了IDE功能,如自动完成,跳转到定义,上下文帮助,提示类型等。为了拥有所有标记名称(可以自动完成的单词)以在您键入时显示所有可能的匹配项,Language Server必须检查有关类的所有内容,包括doc块和私有成员。为此,它需要所述类的反射。

另一个例子是私有方法的单元测试。一种方法是在测试的设置阶段创建一个反射并将方法的作用域更改为public。当然,人们可以争辩私有方法不应该直接测试,但这不是重点。

重要

从Java9开始,您不能再使用反射,除非package-info.java打开模块进行反射访问。

默认情况下,模块中的所有包都拒绝“反射”访问。

Java9模块

我使用反射创建一个基于类名的对象(String中的类名)并调用该类的方法

Object obj = Class.forName(config.getClassPath()).getDeclaredConstructor().newInstance();Method method = obj.getClass().getMethod("getCustomer", SearchObject.class, ObjectConfig.class,HttpServletRequest.class);method.invoke(obj, searchObject, config, request);

但一个主要问题是,如果您在该类上自动拥有一些将重新初始化为null