建商城网站多少钱,郑州网页设计制作,网站开发工程师前景,企业营销策划如果您曾经问过自己以下问题#xff1a; –“如何在字符串中仅包含其名称的方法调用#xff1f;” –“如何动态列出类中的所有属性#xff1f;” –“如何编写一种将任何给定对象的状态重置为默认值的方法#xff1f;” 然后您可能已经听说过Java的Reflection API… 如果您曾经问过自己以下问题 –“如何在字符串中仅包含其名称的方法调用” –“如何动态列出类中的所有属性” –“如何编写一种将任何给定对象的状态重置为默认值的方法” 然后您可能已经听说过Java的Reflection API如果您还没有听说过这是一个很好的机会了解它的全部含义以及可用于什么。 此功能确实功能强大但一如既往必须使用一些良好的判断力。 它为表带来的好处是能够分析有关类的信息包括其属性方法注释甚至实例的状态所有这些都可以在运行时进行。 由此获得的动力听起来真的很有用不是吗 在本教程中我打算展示Reflection API的一些基本用法。 可以做什么不应该做什么优缺点。 所以……可以吗 起点 使用Reflection时最重要的事情之一就是知道从哪里开始知道什么类别可以让我们访问所有这些信息。 答案是 java.lang.Class T 假设我们有以下课程 package com.pkg;public class MyClass{private int number;private String text;} 我们可以通过3种不同的方式获得Class的引用。 直接从类名称或实例开始 Class? clazz1 MyClass.class;
Class? clazz2 Class.forName(com.pkg.MyClass);MyClass instance new MyClass();
Class? clazz3 instance.getClass(); 提示 这里我们看到一个重要的细节。 通常将Class实例的标识符命名为clazz或clz之类的名称这似乎很奇怪但这只是因为class已经是Java语言中的保留字。 从类的参考中我们可以浏览所有内容找出它的成员注释甚至是包或ClassLoader但是稍后我们将更详细地介绍所有这些因此现在让我们集中讨论方法给我们有关班级本身的信息 int getModifiers 返回int内的类或接口的修饰符要确切找出要应用的修饰符我们应该使用Modifier类提供的静态方法 布尔 isArray 确定该类是否表示一个数组 boolean isEnum 确定该类是否在源代码中声明为枚举 boolean isInstanceObject obj 如果可以将通知对象分配给此Class表示的类型的对象则返回true boolean isInterface 确定该类是否表示一个接口 boolean isPrimitive 确定该类是否表示原始类型 T newInstance 创建此类的新实例 类 超级 T getSuperclass 返回超类的引用以防在Object类中被调用它返回null 您可以直接在类文档中看到这些方法以及许多其他方法的完整定义。 提示 要成功使用Modifier类中的方法必须具有一些按位运算的基本知识在这种情况下最常见的是AND运算。 属性 字段表示类的属性就这么简单。 通过此类我们可以获得有关它们的信息但是在此之前我们如何获得对Field的引用 在Class类内部我们有几种不同的方法可以返回类的字段作为破坏者我已经说过 方法和构造函数都具有等效的方法但让我们首先关注属性 。 关于如何找到类的成员我们有一些“重要的要记住”的事情我将首先介绍方法然后详细说明这些细节。 字段getField字符串名称 返回反映给定名称的类的public属性的Field 。 Field [] getFields 返回反映该类的公共属性的字段数组。 字段getDeclaredField字符串名称 返回反映给定名称的类的声明属性的Field 。 Field [] getDeclaredFields 返回反映该类的声明属性的字段数组。 好吧好像我们有两组非常相似的方法而且确实如此。 它们之间的差异是微妙的并且很少有人知道它们。 getField和getFields方法仅返回类的公共属性。 它以某种方式更清洁因为我们并不总是希望而且很多时候不应该与内部属性混为一谈。 这样做的好处是它仍然在超类中搜索属性 沿着层次结构向上移动 这对我们来说很方便但是同样超类中的属性必须是公开的才能被找到。 现在 getDeclaredField和getDeclaredFields方法不是很干净因为它们的任何声明属性都是有效的可以是私有的受保护的或其他任何属性。 因此与普遍看法相反通过反射访问私有成员并不那么复杂。 另一个不同之处在于它们不搜索超类的属性因此它们完全忽略了继承层次结构 。 让我们举例说明如何尝试访问类内的属性以说明上述方法的使用以及Field类提供的其他方法。 以我们之前定义的MyClass类为例假设我们想知道某个实例的text属性中包含的值。 MyClass instance new MyClass();
instance.setText(Reflective Extraction);
Class? clazz instance.getClass();
Field field clazz.getDeclaredField(text); // Accessing private attribute
Object value field.get(instance);
System.out.println(value); 提示 首先使用get方法可能会造成混淆但这很简单。 手中有一个字段您将发送您要从中提取值作为参数的目标实例。 必须记住该字段与类相关而不与实例相关因此如果属性不是静态的则需要一个具体的实例以便可以提取值。 如果我们在谈论静态属性则可以传递任何类型的任何实例甚至可以传递null作为参数。 好的我们使用getDeclaredField方法访问属性传递字段的名称并使用get方法检索其当前值。 完成了吧 错误。 当我们运行代码时我们看到抛出了一个异常更具体地说是IllegalAccessException 这不仅是因为我们拥有该字段的引用而且可以根据自己的意愿对其进行操作但还有一种解决方法。 Field的超类是AccessibleObject类它也是Method和Constructor的超类因此此说明对3中的任何一个都有效。 AccessibleObject类定义2个主要方法 setAccessible boolean 和isAccessible 它们基本上定义了属性是否可访问。 在我们的例子中私有属性是永远无法访问的除非您位于同一类之内在这种情况下您可以毫无问题地访问它们。 因此我们要做的就是将文本配置为可访问。 MyClass instance new MyClass();
instance.setText(Reflective Extraction);
Class? clazz instance.getClass();
Field field clazz.getDeclaredField(text); // Accessing private attribute
field.setAccessible(true); // Setting as accessible
Object value field.get(instance);
System.out.println(value); 然后我们就可以轻松打印出我们的价值 提示 AccessibleObject类具有便捷的静态方法也称为setAccessible但它接收AccessibleObject和boolean标志的数组。 此方法可用于一次性设置多个字段方法或构造函数。 如果要将一个类中的所有属性都设置为可访问则可以执行以下操作 MyClass instance new MyClass();
Class? clazz instance.getClass();
Field[] fields clazz.getDeclaredFields();
AccessibleObject.setAccessible(fields, true); 同样我们可以从属性中提取值也可以分配一个值。 我们使用set方法做到这一点。 调用它时我们需要通知我们要修改的实例以及要设置为相应属性的值。 MyClass instance new MyClass();
Class? clazz instance.getClass();
Field field clazz.getDeclaredField(text);
field.setAccessible(true);
field.set(instance, Reflective attribution);
System.out.println(instance.getText());动作 在开始实际方法之前我想强调一下正如我之前说的它与Field和Constructor类非常相似因此这3种可以通过类引用以相同的方式获得这意味着我们有一个getDeclaredField方法我们还有一个getDeclaredMethod和getDeclaredConstructor 其他方法也是如此。 因此它们的工作方式完全相同因此再次解释它们将毫无意义且浪费时间。 那么让我们开始吧如何使用反射调用方法 一个方法不像一个属性仅靠它的名字是不够的因为我们可以有许多不同的方法使用相同的名字这被称为重载。 因此要获取特定方法我们需要告知其名称以及所接收到的参数列表。 假设我们的类具有以下描述的方法 public void print(String text){System.out.println(text);
}public void printHelloWorld(){System.out.println(Hello World);
}public int sum(int[] values){int sum 0;for(int n : values){sum n;}return sum;
} 为了获得对这些方法的引用按照在示例中声明它们的顺序我们可以这样做 MyClass instance new MyClass();
Class? clazz instance.getClass();
Method print clazz.getMethod(print, String.class);
Method printHelloWorld clazz.getMethod(printHelloWorld);
Method sum clazz.getMethod(sum, int[].class); 为了调用它们我们使用invoke方法巧合 该方法的签名是这样的 Object invoke(Object obj, Object... args) 这意味着我们需要告知将在其上调用该方法的实例目标以及需要传递给它的参数如果有。 除此之外它还返回一个Object 这将是方法的返回值无论它是什么。 如果该方法不返回任何内容 void 则调用invoke将返回null 。 对于上面的3个方法引用调用将如下所示 print.invoke(instance, Im Mr. Reflection by now);
printHelloWorld.invoke(instance);
int sumValue (int) sum.invoke(instance, new int[]{1, 4, 10});
System.out.println(sumValue); 提示 同样如果我们使用静态方法则不必传递有效实例我们可以这样做 staticMethod.invoke(null); 通用参数呢 好了在这种情况下我们需要对语言有所了解然后才能调用它们。 有必要知道通用类型仅在编译时存在并且当Reflection在运行时运行时通用类型不再存在所有这些都归因于一个名为Type Erasure的功能该功能是为了保持与先前Java版本的向后兼容性而创建的。 接收通用参数的方法很可能会接收Object 但是还有另一种可能性。 如果您声明这样的通用类型 T extended CharSequence 则运行时方法可能会收到CharSequence 因为它是最具体的类型仍然保持通用因此对于此方法 public print(T sequence){System.out.println(sequence)
} 反射调用可能如下所示 Method print clazz.getMethod(print, CharSequence.class);
print.invoke(instance);创建实例 众所周知即使构造函数的用法非常相似它也不是方法。 就像我们正在调用方法一样但是后面带有new关键字并且仅当我们要创建类的新实例时才调用它。 在用法上的相似之处导致在反射方面的操作相似。 碰巧的是我们将使用newInstance方法而不是使用invoke但有一些区别。 我们正在创建一个实例因此没有实例与构造函数相关联因此我们不传递任何实例参数。 但是正如我们对“ 方法 ”所做的那样passamos会列出一个论据列表。 Class T也具有newInstance方法但是仅当我们的类具有可访问的构造函数而没有参数时它才有用。如果没有任何构造函数则需要直接引用我们的Constructor T 。 让我们在示例类中定义一些构造函数 public class MyClass{private String text;private int number;public MyClass(){}public MyClass(String text){this.text text;}public MyClass(String text, int number){this.text text;this.number number;}} 现在让我们使用反射来获取它们中的每个 Class clazz MyClass.class;
Constructor c1 clazz.getConstructor();
Constructor c2 clazz.getConstructor(String.class);
Constructor c3 clazz.getConstructor(String.class, int.class); 现在让我们创建3个实例每个构造函数引用一个 MyClass instance1 c1.newInstance();
MyClass instance2 c2.newInstance(text);
MyClass instance3 c3.newInstance(other text, 1); 到此为止我们可以看到我们几乎可以以任何想要的方式操纵一个类列出它的属性获取对方法的引用更改它的状态读取信息但是我们仍然缺少一件事我认为这也是很酷更不用说 元数据 当需要检查与注释有关的信息时可以使用AnnotatedElement接口提供的方法。 仅供参考 实现这些方法的层次结构中的第一个类是AccessibleObject 因此所有子类都可以访问它们。 使用注释我们可以定义有关类属性和/或方法的信息而无需实际编写任何执行代码。 批注将在另一时间处理以使开发更加容易。 因此让我们开始写一个小例子 我们创建了1个名为NotNull的注释并且每当对属性进行注释时它就无法保存值null。 让我们看一下代码 public class MyClass{NotNullprivate String text;} 好的因此我们为文本属性定义了此特征但是如何有效地验证此规则 使用我们的Validator类如下所示 public class Validator{public static void validate(Object target) throws IllegalArgumentException, IllegalAccessException{Class? clazz target.getClass();Field[] fields clazz.getDeclaredFields();for (Field field : fields){validateField(field, target);}}private static void validateField(Field field, Object target) throws IllegalArgumentException, IllegalAccessException{field.setAccessible(true);Object value field.get(target);if (field.isAnnotationPresent(NotNull.class)){if (value null)throw new IllegalArgumentException(This attribute cannot be null);}}
} 因此当我们验证对象时我们可以使用此批注为我们工作验证器将对其进行检查将代码保存在单个位置从而使其更易于维护。 在代码的某些点上我们将有一个这样的调用 Validator.validate(myClassInstance); 我们完成了。 在开发框架时该技术被广泛使用通常您只需查看文档以查看每个注释会做什么并相应地使用它们。 缺点为什么我不应该使用反射 我相信很明显在某些情况下使用反射会给我们带来很多好处和便利但是众所周知当我们拥有的只是一把锤子时任何问题都像钉子一样所以不要全神贯注地思考如何使用反射解决所有问题因为它有缺点 性能开销 使用反射时JVM需要做大量工作来获取我们需要的所有信息进行动态调用以及所有这些操作因此这在处理时间上要付出一定的成本。 运行时安全性 为了运行反射代码必须在虚拟机内部拥有一定级别的权限并且您可能并非一直如此因此在考虑使用它时请记住这一点。 模型安全性 出于某种原因我们的属性具有不同的可见性对吗 并且反射可以完全忽略它们无论做什么都可能在封装中引起一些警报。 基本规则是仅在没有其他替代方法时才使用反射。 如果您可以做一些事而无需反思您可能应该这样做。 您应该根据具体情况进行分析。 可以在Oracle教程中获得更多信息。 我知道谈论“反射”时还有其他功能例如“ 代理” 但是它们更加高级和复杂。 我可能会在以后的文章中写到它们但这超出了本文的范围因为它仅是作为该主题的介绍或对已经知道这一点的人员的复习因此提出了高级主题弊大于利。 希望大家喜欢下次再见 参考来自Rodrigo Sasaki博客博客的JCG合作伙伴 Rodrigo Sasaki的Java反射API 。 翻译自: https://www.javacodegeeks.com/2013/07/javas-reflection-api.html