返回首页
当前位置: 主页 > 编程语言 > JAVA教程 >

Java反射实践

时间:2015-11-23 22:11来源:电脑教程学习网 www.etwiki.cn 编辑:admin

 您是否考虑过这些问题: IDE 如何列出类的所有详细信息,包括私有字段和私有方法? IDE 还能够列出 JAR 文件中的类(及其详细信息),它们是如何做到的?

下面是反射的一些例子。

本文将阐述如何在编程中应用反射,以及如何在高级抽象中应用反射。我们将从一个十分简单的例子入手,然后创建一个简单的程序来使用反射。

什么是反射?
反射是一种机制,它允许动态地发现和绑定类、方法、字段,以及由语言组成的所有其他元素。列出类、字段和方法只是反射的基本应用。通过反射,我们实际上还能够在需要时创建实例、调用方法以及访问字段。

大多数程序员曾使用过动态类载入技术来载入他们的 JDBC 驱动程序。这种载入方法类似于下面这一段动态载入 MySQL JDBC 驱动程序实例的代码片段:

Class.forName("com.mysql.jdbc.Driver").newInstance();
使用反射的原因和时机
反射提供了一个高级别的抽象。换句话说,反射允许我们在运行时对手头上的对象进行检查并进行相应的操作。例如,如果您必须在多种对象上执行相同的任 务,如搜索某个实例。则可以为每种不同的对象编写一些代码,也可以使用反射。或许您已经意识到了,反射可以减少近似代码的维护量。因为使用了反射,您的实 例搜索代码将会对其他类起作用。我们稍后会谈到这个示例。我已经将它加入到这篇文章里,以便向您展示我们如何从反射中获益。

动态发现
下面我们从发现一个类的内容并列出它的构造、字段、方法开始。这并不实用,但它能让我们直观地了解反射 API 的原理及其他内容。

创建 Product 类,如下所示。我们的所有示例都保存在名为 ria 的程序包中。

package ria;public class Product {private String description;private long id;private String name;private double price;//Getters and setters are omitted for shortness}
创建好 Product 类后,我们下面继续创建第二个类,名为 ReflectionUtil,它将列出第一个类的 (Product) 详细信息。或许您已经预料到了,这个类会包含一些实用的方法,这些方法将执行这个应用程序中所需的所有反射功能。目前,这个类将只包含一个方法 describeInstance(Object),它具有一个类型为 Object 的参数。

下面的清单中演示了 ReflectionUtil 类的代码。

package ria;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;public class ReflectionUtil {public static void describeInstance(Object object) {Class<?> clazz = object.getClass();Constructor<?>[] constructors = clazz.getDeclaredConstructors();Field[] fields = clazz.getDeclaredFields();Method[] methods = clazz.getDeclaredMethods();System.out.println("Description for class: " + clazz.getName());System.out.println();System.out.println("Summary");System.out.println("-----------------------------------------");System.out.println("Constructors: " + (constructors.length));System.out.println("Fields: " + (fields.length));System.out.println("Methods: " + (methods.length));System.out.println();System.out.println();System.out.println("Details");System.out.println("-----------------------------------------");if (constructors.length > 0) {System.out.println();System.out.println("Constructors:");for (Constructor<?> constructor : constructors) {System.out.println(constructor);}}if (fields.length > 0) {System.out.println();System.out.println("Fields:");for (Field field : fields) {System.out.println(field);}}if (methods.length > 0) {System.out.println();System.out.println("Methods:");for (Method method : methods) {System.out.println(method);}}}}
Java 包含一组与反射有关的类,这些类被打包在反射 API 下。类 Constructor、 Field 和 Method 就是属于这个程序包的其中一些类。如同众所周知的 Class 类一样, Java 使用这些类将我们所编写的程序演示为对象。为了描述对象,我们需要知道它的组成。我们从哪里开始呢?那就从这个类开始吧,因为它包含了我们的所有代码。

Class<?> clazz = object.getClass();
注意到这里的泛型声明 Class<?>。泛型,简单地说,就是通过确保给出的实例是某种指定的类型提供类型安全的操作。我们的方法 (describeInstance(Object)) 并未绑定到某个特定的类型,而是设计为与任何给定的对象共同工作。因此,使用无限制的通配符 <?>。

Class 类有很多方法。我们将重点介绍与我们有关的方法。在下面的代码片段中列出了这些方法。

Constructor<?>[] constructors = clazz.getDeclaredConstructors();Field[] fields = clazz.getDeclaredFields();Method[] methods = clazz.getDeclaredMethods();
上面的 Class 方法返回了一组组成该对象构造函数、字段以及方法。

请注意,Class 类包含两组 getter 方法:一组在其名称中包含 declared 单词,而另一组则不包含这个单词。不同之处在于, getDeclaredMethods() 将返回属于这个类的所有方法,而 getMethods() 只返回 public 方法。理解只返回在这个类中声明的方法,这一点非常重要。继承的方法是不会被检索到的。

理解 ReflectionUtil 类没有对 Product 类的引用,这一点也非常重要。我们需要另一个创建产品详细信息类的实例并打印其详细信息的类。

package ria;public class Main {public static void main(String[] args) throws Exception {Product product = new Product();product.setId(300);product.setName("My Java Product Name");product.setDescription("My Java Product description...");product.setPrice(10.10);ReflectionUtil.describeInstance(product);}}
上面的这个类应该产生以下输出(或者类似于以下内容的输出):

Description for class: ria.ProductSummary-----------------------------------------Constructors: 1Fields: 4Methods: 8Details-----------------------------------------Constructors:public ria.Product()Fields:private java.lang.String ria.Product.descriptionprivate long ria.Product.idprivate java.lang.String ria.Product.nameprivate double ria.Product.priceMethods:public java.lang.String ria.Product.getName()public long ria.Product.getId()public void ria.Product.setName(java.lang.String)public void ria.Product.setId(long)public void ria.Product.setDescription(java.lang.String)public void ria.Product.setPrice(double)public java.lang.String ria.Product.getDescription()public double ria.Product.getPrice()
若要使该方法更加有用,还应该打印与该类详细信息一起描述的实例的值。 Field 类包含一个名为 get(Object)的方法,该方法返回给定实例的字段的值。

例如,我们的 Product 类。该类具有四个实例变量。检索到的值取决于实例,因为不同的实例可能有不同的值。因此,必须向 Field 提供实例,才能返回如下所示的值:

field.get(object)
其中 field 是 Field 的一个实例,并且 object 是任何 Java 类的一个实例。

在我们开始草率地添加任何代码之前,我们必须认识到这么一个事实,那就是类的字段具有 private 访问修改程序。如果我们按原样调用 get(Object) 方法,将会抛出一个异常。我们需要调用 Field 类的方法setAccessible(boolean),并将 true 作为参数传递,然后我们再尝试访问该字段的值。

field.setAccessible(true);
现在,我们已经知道了获得字段的值时的所有技巧,我们可以在 decribeInstance(Object) 方法的底部添加以下代码。

if (fields.length > 0) {System.out.println();System.out.println();System.out.println("Fields' values");System.out.println("-----------------------------------------");for (Field field : fields) {System.out.print(field.getName());System.out.print(" = ");try {field.setAccessible(true);System.out.println(field.get(object));} catch (IllegalAccessException e) {System.out.println("(Exception Thrown: " + e + ")");}}}
为了向您显示这段代码的效果,我来创建 of the java.awt.Rectangle 类的一个实例并使用describeInstance(Object) 方法打印其详细信息。

Rectangle rectangle = new Rectangle(1, 2, 100, 200);ReflectionUtil.describeInstance(rectangle);
上面的这个代码片段应该产生类似于以下内容的输出。 请注意,某些输出可能会由于过长而无法显示被截断。

Description for class: java.awt.RectangleSummary-----------------------------------------Constructors: 7Fields: 5Methods: 39Details-----------------------------------------Constructors:public java.awt.Rectangle()public java.awt.Rectangle(java.awt.Rectangle)public java.awt.Rectangle(int,int,int,int)public java.awt.Rectangle(int,int)public java.awt.Rectangle(java.awt.Point,java.awt.Dimension)public java.awt.Rectangle(java.awt.Point)public java.awt.Rectangle(java.awt.Dimension)Fields:public int java.awt.Rectangle.xpublic int java.awt.Rectangle.ypublic int java.awt.Rectangle.widthpublic int java.awt.Rectangle.heightprivate static final long java.awt.Rectangle.serialVersionUIDMethods:public void java.awt.Rectangle.add(int,int)public void java.awt.Rectangle.add(java.awt.Point)public void java.awt.Rectangle.add(java.awt.Rectangle)public boolean java.awt.Rectangle.equals(java.lang.Object)public java.lang.String java.awt.Rectangle.toString()public boolean java.awt.Rectangle.contains(int,int,int,int)public boolean java.awt.Rectangle.contains(java.awt.Rectangle)public boolean java.awt.Rectangle.contains(int,int)public boolean java.awt.Rectangle.contains(java.awt.Point)public boolean java.awt.Rectangle.isEmpty()public java.awt.Point java.awt.Rectangle.getLocation()public java.awt.Dimension java.awt.Rectangle.getSize()public void java.awt.Rectangle.setSize(java.awt.Dimension)public void java.awt.Rectangle.setSize(int,int)public void java.awt.Rectangle.resize(int,int)private static native void java.awt.Rectangle.initIDs()public void java.awt.Rectangle.grow(int,int)public boolean java.awt.Rectangle.intersects(java.awt.Rectangle)private static int java.awt.Rectangle.clip(double,boolean)public java.awt.geom.Rectangle2D java.awt.Rectangle.createIntersection(java....public java.awt.geom.Rectangle2D java.awt.Rectangle.createUnion(java.awt.geo...public java.awt.Rectangle java.awt.Rectangle.getBounds()public java.awt.geom.Rectangle2D java.awt.Rectangle.getBounds2D()public double java.awt.Rectangle.getHeight()public double java.awt.Rectangle.getWidth()public double java.awt.Rectangle.getX()public double java.awt.Rectangle.getY()public boolean java.awt.Rectangle.inside(int,int)public java.awt.Rectangle java.awt.Rectangle.intersection(java.awt.Rectangle)public void java.awt.Rectangle.move(int,int)public int java.awt.Rectangle.outcode(double,double)public void java.awt.Rectangle.reshape(int,int,int,int)public void java.awt.Rectangle.setBounds(int,int,int,int)public void java.awt.Rectangle.setBounds(java.awt.Rectangle)public void java.awt.Rectangle.setLocation(java.awt.Point)public void java.awt.Rectangle.setLocation(int,int)public void java.awt.Rectangle.setRect(double,double,double,double)public void java.awt.Rectangle.translate(int,int)public java.awt.Rectangle java.awt.Rectangle.union(java.awt.Rectangle)Fields' values-----------------------------------------x = 1y = 2width = 100height = 200serialVersionUID = -4345857070255674764
创建使用反射的新实例
还可以使用反射来创建一个新对象的实例。关于动态创建对象的实例有许多例子,如前面所说的动态载入 JDBC 驱动程序。此外,我们还可以使用 Constructor 类来创建新实例,尤其是在其实例化的过程中需要参数的实例。向我们的 ReflectionUtil 类中添加以下两个过载的方法。

public static <T> T newInstance(Class<T> clazz)throws IllegalArgumentException, SecurityException,InstantiationException, IllegalAccessException,InvocationTargetException, NoSuchMethodException {return newInstance(clazz, new Class[0], new Object[0]);}public static <T> T newInstance(Class<T> clazz, Class<?>[] paramClazzes,Object[] params) throws IllegalArgumentException,SecurityException, InstantiationException, IllegalAccessException,InvocationTargetException, NoSuchMethodException {return clazz.getConstructor(paramClazzes).newInstance(params);}
请注意,如果提供的构造函数参数不够,则 newInstance(Object[]) 将抛出异常。被实例化的类必须包含具有给定签名的构造函数。

可以使用第一个方法 (newInstance(Class<T>)) 实例化拥有默认构造函数的任何类中的对象。也可以使用第二个方法。通过传递参数类型及其各自参数中的值,将通过匹配构造函数来实现实例化。例如,可以使用具有四个类型为 int 的参数的构造函数对 Rectangle 类进行实例化,使用的代码如下所示:

Object[] params = { 1, 2, 100, 200 };Class[] paramClazzes = { int.class, int.class, int.class, int.class };Rectangle rectangle = ReflectionUtil.newInstance(Rectangle.class, paramClazzes, params);System.out.println(rectangle);
上面代码将产生以下输出。

java.awt.Rectangle[x=1,y=2,width=100,height=200]


通过反射更改字段值

可以通过反射设置字段的值,其方式与读取它们的方式类似。在尝试设置值之前,设置该字段的可访问性,这一点非常重要。因为如果不这样,将抛出一个异常。

field.setAccessible(true);field.set(object, newValue);
我们可以轻松草拟一个可以设置其任何对象的值的方法,如以下实例所示。

public static void setFieldValue(Object object, String fieldName,Object newValue) throws NoSuchFieldException,IllegalArgumentException, IllegalAccessException {Class<?> clazz = object.getClass();Field field = clazz.getDeclaredField(fieldName);field.setAccessible(true);field.set(object, newValue);}
该方法有一个缺陷,它只能从给定的类中检索字段。不包含继承的字段。可以使用以下方法快速解决这个问题,该方法查找所需的 Field 的对象层次结构。

public static Field getDeclaredField(Object object, String name)throws NoSuchFieldException {Field field = null;Class<?> clazz = object.getClass();do {try {field = clazz.getDeclaredField(name);} catch (Exception e) { }} while (field == null & (clazz = clazz.getSuperclass()) != null);if (field == null) {throw new NoSuchFieldException();}return field;}
该方法将返回具有给定名称的 Field(如果找到);否则它将抛出一个异常,表明该对象没有该字段,也没有继承该字段。它从给定类开始搜索,一直沿层次结构搜索,直到找到 Field 或者没有超级类可用为止。

请注意,所有 Java 类都从 Object 类继承(直接或过渡)。您可能已经意识到, Object 类不从自身继承。因此, Object 类没有超级类。

修改前面演示的方法 setFieldValue(Object, String, Object) 以适合这种情况。更改如下面粗体所示。

public static void setFieldValue(Object object, String fieldName,Object newValue) throws IllegalArgumentException,IllegalAccessException, NoSuchFieldException {Field field = getDeclaredField(object, fieldName);field.setAccessible(true);field.set(object, newValue);}
让我们创建另一个名 Book 的类,该类扩展前面讨论的 Product 类,并应用目前我们所学到的内容。

package ria;public class Book extends Product {private String isbn;//Getters and setters are omitted for shortness}
现在,使用 setFieldValue(Object, String, Object) 方法设置 Book 的 id。

Book book = new Book();ReflectionUtil.setFieldValue(book, "id", 1234L);System.out.println(book.getId());
上面的代码将产生以下输出:1234.

通过反射调用方法
或许您已经猜到,调用方法与创建新实例以及访问上面讨论的字段非常类似。

就涉及的反射而言,所有方法都具有参数并且返回值。这听起来可能比较奇怪,但它确实是这样。让我们分析下面的方法:

public void doNothing(){// This method doesn't do anything}
该方法具有一个类型为 void 的返回类型,还有一个空的参数列表。可以采用以下方式通过反射调用该方法。

Class<?> clazz = object.getClass();Method method = Clazz.getDeclaredMethod("doNothing");method.invoke(object, new Object[0]);
invoke 方法来自 Method 类,需要两个参数:将调用方法的实例以及作为对象数组的参数列表。请注意,方法doNothing() 没有参数。尽管这样,我们仍然还需要将参数指定为空的对象数组。

方法还具有一个返回类型;本例中为 void。可以将该返回类型(如果有)另存为 Object,某些内容类似于以下示例。

Object returnValue = method.invoke(object, new Object[0]);
在本例中,返回值为 null,因为该方法不返回任何值。请注意,方法可以故意返回 null,但这样可能会有点混淆。

完成此部分之前,理解可以采用与字段相同的方式继承方法,这一点非常重要。我们可以使用另一种实用方法在层次结构中检索该方法,而不是只从手边的类中检索。

public static Method getDeclaredMethod(Object object, String name)throws NoSuchMethodException {Method method = null;Class<?> clazz = object.getClass();do {try {method = clazz.getDeclaredMethod(name);} catch (Exception e) { }} while (method == null & (clazz = clazz.getSuperclass()) != null);if (method == null) {throw new NoSuchMethodException();}return method;}
最后,下面列出了范型 invoke 方法。请再次注意,方法可以是 private,因此在调用它们之前最好设置它们的可访问性。

 

应用程序中的反射
直到现在,我们仅创建了食用方法并且试验了几个简单的示例。实际的编程需要的不只这些。想像我们需要搜索我们的对象并确定给定对象是否符合某些条件。第一个选项是编写一个接口并在每个对象(如果该实例符合条件,则对象返回 true,否则返回 false)中实现它。不幸的是,该方法要求我们在我们拥有的每个类中执行一个方法。新的类不许实现该接口并为其抽象方法提供主要部分。或者,我们也可以使用反射检索对象的字段并检查它们的值是否符合条件。

让我们首先创建另一个返回该对象字段的方法。请记住,没有一种内置的方法可以返回所有字段,包括继承的字段。因此,我们需要通过逐组提取它们来亲自检索它们,直到我们达到层次结构的顶部为止。可以向ReflectionUtil 类中添加该方法。

public static List <Field> getDeclaredFields(Class clazz) {List<Field> fields = new ArrayList<Field>();do {try {fields.addAll(Arrays.asList(clazz.getDeclaredFields()));} catch (Exception e) { }} while ((clazz = clazz.getSuperclass()) != null);return fields;}
现在,我们只需要让它们的字符串值与给定的条件相匹配,如下面的代码片段中所示。使用 String 方法valueOf(Object) 将字段的值转换为字符串,而不返回 null 或抛出任何异常。请注意,这可能并不总是适合于复杂的数据类型。

public static boolean search(Object object, String criteria)throws IllegalArgumentException, IllegalAccessException {List <Field> fields = ReflectionUtil.getDeclaredFields(object.getClass());for (Field field : fields) {field.setAccessible(true);if (String.valueOf(field.get(object)).equalsIgnoreCase(criteria)) {return true;}}return false;}
让我们创建一个名为 Address 的新类,并用该类进行试验。该类的代码如下所示。

package ria;public class Address {private String country;private String county;private String street;private String town;private String unit;//Getters and setters are omitted for shortness}
现在,让我们创建 Book 和 Address 类的一个实例并应用我们的搜索方法。

Book book = new Book();book.setId(200);book.setName("Reflection in Action");book.setIsbn("123456789-X");book.setDescription("An article about reflection");Address address = new Address();address.setUnit("1");address.setStreet("Republic Street");address.setTown("Valletta");address.setCountry("Malta");System.out.println("Book match? " + search(book, "Valletta"));System.out.println("Address match? " + search(address, "Valletta"));
第一个匹配(针对 Book 实例的匹配)将返回 false,而地址实例将返回 true。可以针对任何对象应用此搜索方法,而无需添加或执行任何内容。

反射的缺点
直到现在,我们仅仅讨论了反射如何好以及它如何使生活更轻松。不幸的是,任何事情都有代价。尽管反射功能非常强大并且提供了很大的灵活性,但是我们 不应该使用反射编写任何内容。如果可能的话,在某些情况下您可能希望避免使用反射。因为反射会引入以下缺点:性能开销、安全限制以及暴露隐藏的成员。

有时,通过访问修改程序保存逻辑。下面的代码片段就是一个鲜明的例子:

public class Student {private String name;public Student(String name){this.name = name;}}
当初始化对象后,只能通过构造函数更改学生的姓名。使用反射,您可以将学生的姓名设置任何 String,甚至在初始化对象之后也可以。正如您所见到的一样,这样会打乱业务逻辑并且可能会使程序行为不可预测。

与大多数其他编译器一样,Java 编译器尝试尽可能多的优化代码。对于反射这是不可能的,因为反射是在运行时解析类型,而编译器是在编译时工作。此外,必须在稍后的阶段即运行时解析类型。

结束语
反射可用于在不同对象中实现相同的逻辑(如搜索), 而不需要为每个新类型都创建新代码。这样也有利于对逻辑进行集中管理。遗憾的是,反射也存在缺点,它有时会增加代码的复杂性。性能是对反射的另一个负面影响,因为无法在此类代码上执行优化。

顶一下
(0)
0%
踩一下
(0)
0%
标签(Tag):java java技巧 java实例教程 java源代码 java基础教程
------分隔线----------------------------
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
验证码:点击我更换图片
推荐内容