原文:http://today.java.net/pub/a/today/2008/02/12/reflection-in-action.html
你曾经为IDE会自动的列举出所有你所编写的类的详情,甚至连私有的字段和方法也“难逃魔掌”而感到惊讶吗?此外,这些IDE居然还能够识别那些并不提供源码并压缩成JAR文件的类。它们是怎么做到的?
这些都是因为反射。
本文将通过逐步列举一个类的内容,来阐明反射是如何被用来“撬动”编程的。同时逐步形成高级别的抽象。我们将会从一个十分简单的例子开始,并一步步地在一个程序中实施反射。
什么是反射?
反射是一种机制,它允许动态发现和绑定类、方法、字段,以及所有其他的由语言所产生的元素。反射可以做的不仅仅是简单地列举类、字段以及方法。通过反射,我们可以还能够在需要时完成创建实例、调用方法以及访问字段的工作。
大多数程序员曾使用过动态类载入技术来载入他们的JDBC驱动。这种载入方法类似于下面这一段载入JDBC驱动实例的代码片段:
Class.forName("com.mysql.jdbc.Driver").newInstance();
为何与何时使用反射?
反射提供了一个高级别的抽象,换句话说,反射允许我们在程序运行时对手头上的对象进行检查并运行。举个例子,想像一下,当你在执行那相同的任务时——如像上面的例子那样在若干的对象中查找一个实例,你可以选择为每一个不同的对象写相同的代码,也可以使用反射来完成这项任务。或许你已经开始意识到了,反射可以减少近似的代码的维护量。因为使用了反射,你的实例查找代码将会对其他类起作用。我们稍后将会演示这个例子。我已经将它加入到这篇文章里,以便向你展示我们如何从反射中得利。
动态发现
下面我们以发现一个类的内容并列出它的构造子,字段,方法作为开始吧。这并不实用,但它能让我们直观地抓住反射的原理及其了解其API。
创建一个Product类,如下所示。所有我们的例子都将放到了相同的package里,叫ria。
package ria;
public class Product {
private String description;
private long id;
private String name;
private double price;
//省略若干Getter与Setter
}
创建好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(Reflection API)中。构造子类(Constructor)、字段类(Field)以及方法类(Method)便是其中的一部分。如同众所周知的Class类一样,它们在Java中被用来在程序中描述对象。为了描述一个对象,我们需要知道这个对象是由什么组成的。我们如何开始呢?那就从类开始吧,它包含了我们所有的代码。
Class<?> clazz = object.getClass();
注意这里的泛型声明Class<?>。泛型,简单地说,就是通过限定给出的实例是某种类型的,从而提供类型安全(type-safe)的操作。我们的方法(describeInstance(Object))并不绑定到一个特定类型上,它被设计为对任意给出的对象都能正常运行。因此,那无限制的通配符,<?>,将会被使用到。
Class类有一些方法,下面我们将集中于那些对我们有用的方法上。下面的代码片段中列出了这些方法。
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
Field[] fields = clazz.getDeclaredFields();
Method[] methods = clazz.getDeclaredMethods();
上面的这些来自Class类的方法返回了一组构造子、字段、方法,这是它们组成了这个对象。
注意那个Class类含有两组getter方法:一组在它们的名字中包含了declared单词,而另一组没有。不同之处在于,getDeclaredMethods()会返回所有属于这个类的方法,而getMethods()则只返回声明为public的方法。这对于理解为何只有在这个类中声明的方法才予以返回的原因同样重要。继承的方法是不会被检索到的。
了解ReflectionUtil类并没有对一个关于Product类的引用十分重要。我们需要另一个类来创建一个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.Product
Summary
-----------------------------------------
Constructors: 1
Fields: 4
Methods: 8
Details
-----------------------------------------
Constructors:
public ria.Product()
Fields:
private java.lang.String ria.Product.description
private long ria.Product.id
private java.lang.String ria.Product.name
private double ria.Product.price
Methods:
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)的方法,它返回给定的实例的相应字段的值。
现在就以我们的Procuct类来举个例子吧。这个类有四个实例变量。由于获取的值依赖于实例,因此不同的实例可能有不同的值。所以,必须向Field提供实例作为参数输入,这样我们才能够获取这个实例的对应的字段的值。如下所示:
field.get(object)
这里的field是Field的一个实例,同时object是为一个任意Java类的实例。
在我们草率地开始增加代码前,我们必须认识到这么一个事实,那就是类的字段的私有访问性是可以修改的。如果我们调用一个标记为private的字段的 Field类的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 + ")");
}
}
}
为了给你显示这段代码的威力,我来创建一个java.awt.Rectangle类的实例吧,用这段增强版的describeInstance(Object)代码来打印出这个实例的详情。
Rectangle rectangle = new Rectangle(1, 2, 100, 200);
ReflectionUtil.describeInstance(rectangle);
上面的这个代码片段应该能输出一些类似下面的这些信息。提示一下,由于显示的信息过长,部分信息被省略掉了。
Description for class: java.awt.Rectangle
Summary
-----------------------------------------
Constructors: 7
Fields: 5
Methods: 39
Details
-----------------------------------------
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.x
public int java.awt.Rectangle.y
public int java.awt.Rectangle.width
public int java.awt.Rectangle.height
private static final long java.awt.Rectangle.serialVersionUID
Methods:
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 = 1
y = 2
width = 100
height = 200
serialVersionUID = -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>))可以用来实例化任何一个拥有默认构造子的类的对象。否则可以使用第二个方法:传递参数的类型以及其值,当匹配上对应的构造子后就会执行相应的实例化。举个例子,类Rectangle就可以通过使用含有四个int类型的参数的构造子来进行实例化。使用的代码如下所示:
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。
累死了!
未完待续。。。。
分享到:
相关推荐
Java Reflection in Action is unique in presenting a clear account of all the cool things you can do with reflection, and at the same time pro- viding the sound conceptual basis that developers need to...
在《Java Reflection in Action》这本书中,作者深入探讨了这一主题,帮助开发者理解并有效地利用Java反射机制。这本书是2005年出版的英文版,对于想要提升Java编程技能,特别是对动态类型和元编程感兴趣的开发者来...
《JAVA反射在行动》这本书由Ira R. Forman和Nate Forman撰写,由MANNING出版社出版,是一本深入探讨Java反射机制及其应用的权威指南。反射是Java语言的一项强大特性,允许运行时检查类的信息并操作类的对象,这在很...
本书《Java Reflection in Action》深入浅出地讲解了Java反射技术的各种应用场景和技术细节。书中通过一系列生动的例子展示了如何安全有效地使用反射技术解决实际问题。例如: 1. **使用反射进行动态代理**:介绍了...
《Java Reflection In Action》这本书深入探讨了这个主题,对于理解和掌握Java动态性有着极大的帮助。以下是关于Java反射的一些关键知识点: 1. **反射基础**:反射API主要包括`java.lang.Class`、`java.lang....
《Java Reflection in Action》这本书深入探讨了这一主题,为开发者提供了理解和运用反射的强大工具。这本书包含了PDF版的全文以及示例源代码,帮助读者通过实践来学习。 1. **反射基础**: - 反射API:`java.lang...
Java Reflection in Action is unique in presenting a clear account of all the cool things you can do with reflection, and at the same time providing the sound conceptual basis that developers need to ...
《Java反射技术实战》这本书由Ira R. Forman和Nate Forman撰写,由Manning出版社出版,是一本深入探讨Java反射技术的专著。反射是Java语言的一个强大特性,它允许运行时检查和修改类、接口和对象的结构与行为。...
《Java反射实战》是2005年出版的一本深入探讨Java反射机制的专业教程,它为开发者揭示了Java编程中的一个重要而复杂的主题。反射在Java编程中扮演着至关重要的角色,尤其是在动态类型、元编程以及扩展框架方面。...
《Java Reflection in Action》一书由Ira R. Forman和Nate Forman共同撰写,旨在帮助读者深入理解Java反射机制的核心概念和技术细节,并通过一系列实用案例展示如何有效地利用反射技术来解决实际问题。 #### 二、...
We wrote this book because reflection inspires us. It produces solutions so elegant that they elicit the same sense of wonderment that we often felt as children. It is this inspiration that has driven...
We wrote this book because reflection inspires us. It produces solutions so elegant that they elicit the same sense of wonderment that we often felt as children. It is this inspiration that has driven...