`

java反射机制详解

    博客分类:
  • java
阅读更多
Java语言反射提供一种动态链接程序组件的多功能方法。它允许程序创建和控制任何类的对象(根据安全性限制),无需提前硬编码目标类。这些特性使得反射特别适用于创建以非常普通的方式与对象协作的库。例如,反射经常在持续存储对象为数据库、XML或其它外部格式的框架中使用。Java reflection 非常有用,它使类和数据结构能按名称动态检索相关信息,并允许在运行着的程序中操作这些信息。Java 的这一特性非常强大,并且是其它一些常用语言,如 C、C++、Fortran 或者 Pascal 等都不具备的。
    但反射有两个缺点。第一个是性能问题。用于字段和方法接入时反射要远慢于直接代码。性能问题的程度取决于程序中是如何使用反射的。如果它作为程序运行中相对很少涉及的部分,缓慢的性能将不会是一个问题。即使测试中最坏情况下的计时图显示的反射操作只耗用几微秒。仅反射在性能关键的应用的核心逻辑中使用时性能问题才变得至关重要。
    许多应用中更严重的一个缺点是使用反射会模糊程序内部实际要发生的事情。程序人员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术会带来维护问题。反射代码比相应的直接代码更复杂,正如性能比较的代码实例中看到的一样。解决这些问题的最佳方案是保守地使用反射——仅在它可以真正增加灵活性的地方 ——记录其在目标类中的使用。

   Reflection是Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说"自审",并能直接操作程序的内部属性。例如,使用它能获得 Java 类中各成员的名称并显示出来。
  Java 的这一能力在实际应用中也许用得不是很多,但是在其它的程序设计语言中根本就不存在这一特性。例如,Pascal、C 或者 C++ 中就没有办法在程序中获得函数定义相关的信息。
  JavaBean 是 reflection 的实际应用之一,它能让一些工具可视化的操作软件组件。这些工具通过 reflection 动态的载入并取得 Java 组件(类) 的属性。

1. 找出类的方法
  找出一个类中定义了些什么方法,这是一个非常有价值也非常基础的 reflection 用法。下面的代码就实现了这一用法:
import java.lang.reflect.*;
public class method1 {
private int f1(Object p, int x) throws NullPointerException {
if (p == null)
throw new NullPointerException();
return x;
}
public static void main(String args[]) {
try {
Class cls = Class.forName("method1");
Method methlist[] = cls.getDeclaredMethods();
for (int i = 0; i < methlist.length; i++) {
Method m = methlist[i];
System.out.println("name = " + m.getName());
System.out.println("decl class = " + m.getDeclaringClass());
Class pvec[] = m.getParameterTypes();
for (int j = 0; j < pvec.length; j++)
System.out.println("param #" + j + " " + pvec[j]);
Class evec[] = m.getExceptionTypes();
for (int j = 0; j < evec.length; j++)
System.out.println("exc #" + j + " " + evec[j]);
System.out.println("return type = " + m.getReturnType());
System.out.println("-----");
}
} catch (Throwable e) {
System.err.println(e);
} 

  这个程序首先取得 method1 类的描述,然后调用 getDeclaredMethods 来获取一系列的 Method 对象,它们分别描述了定义在类中的每一个方法,包括 public 方法、protected 方法、package 方法和 private 方法等。如果你在程序中使用 getMethods 来代替 getDeclaredMethods,你还能获得继承来的各个方法的信息。


  取得了 Method 对象列表之后,要显示这些方法的参数类型、异常类型和返回值类型等就不难了。这些类型是基本类型还是类类型,都可以由描述类的对象按顺序给出。
  输出的结果如下:
name = f1
decl class = class method1
param #0 class java.lang.Object
param #1 int
exc #0 class java.lang.NullPointerException
return type = int
-----
name = main
decl class = class method1
param #0 class [Ljava.lang.String;
return type = void 


2.获取构造器信息
  获取类构造器的用法与上述获取方法的用法类似,如:
import java.lang.reflect.*;
public class constructor1 {
public constructor1() {
}
protected constructor1(int i, double d) {
}
public static void main(String args[]) {
try {
Class cls = Class.forName("constructor1");
Constructor ctorlist[] = cls.getDeclaredConstructors();
for (int i = 0; i < ctorlist.length; i++) {
Constructor ct = ctorlist[i];
System.out.println("name = " + ct.getName());
System.out.println("decl class = " + ct.getDeclaringClass());
Class pvec[] = ct.getParameterTypes();
for (int j = 0; j < pvec.length; j++)
System.out.println("param #" + j + " " + pvec[j]);
Class evec[] = ct.getExceptionTypes();
for (int j = 0; j < evec.length; j++)
System.out.println("exc #" + j + " " + evec[j]);
System.out.println("-----");
}
} catch (Throwable e) {
System.err.println(e);
}
}
} 

这个例子中没能获得返回类型的相关信息,那是因为构造器没有返回类型。
  这个程序运行的结果是:
name = constructor1
decl class = class constructor1
-----
name = constructor1
decl class = class constructor1
param #0 int
param #1 double 


3.获取类的字段(域)
  找出一个类中定义了哪些数据字段也是可能的,下面的代码就在干这个事情:
import java.lang.reflect.*;
public class field1 {
private double d;
public static final int i = 37;
String s = "testing";
public static void main(String args[]) {
try {
Class cls = Class.forName("field1");
Field fieldlist[] = cls.getDeclaredFields();
for (int i = 0; i < fieldlist.length; i++) {
Field fld = fieldlist[i];
System.out.println("name = " + fld.getName());
System.out.println("decl class = " + fld.getDeclaringClass());
System.out.println("type = " + fld.getType());
int mod = fld.getModifiers();
System.out.println("modifiers = " + Modifier.toString(mod));
System.out.println("-----");
}
} catch (Throwable e) {
System.err.println(e);
}
}
} 

 这个例子和前面那个例子非常相似。例中使用了一个新东西 Modifier,它也是一个 reflection 类,用来描述字段成员的修饰语,如“private int”。这些修饰语自身由整数描述,而且使用 Modifier.toString 来返回以“官方”顺序排列的字符串描述 (如“static”在“final”之前)。这个程序的输出是:
name = d
decl class = class field1
type = double
modifiers = private
-----
name = i
decl class = class field1
type = int
modifiers = public static final
-----
name = s
decl class = class field1
type = class java.lang.String
modifiers = 


  和获取方法的情况一下,获取字段的时候也可以只取得在当前类中申明了的字段信息 (getDeclaredFields),或者也可以取得父类中定义的字段 (getFields) 。

4.根据方法的名称来执行方法
  文本到这里,所举的例子无一例外都与如何获取类的信息有关。我们也可以用 reflection 来做一些其它的事情,比如执行一个指定了名称的方法。下面的示例演示了这一操作:
import java.lang.reflect.*;
public class method2 {
public int add(int a, int b) {
return a + b;
}
public static void main(String args[]) {
try {
Class cls = Class.forName("method2");
Class partypes[] = new Class[2];
partypes[0] = Integer.TYPE;
partypes[1] = Integer.TYPE;
Method meth = cls.getMethod("add", partypes);
method2 methobj = new method2();
Object arglist[] = new Object[2];
arglist[0] = new Integer(37);
arglist[1] = new Integer(47);
Object retobj = meth.invoke(methobj, arglist);
Integer retval = (Integer) retobj;
System.out.println(retval.intValue());
} catch (Throwable e) {
System.err.println(e);
}
}
} 

假如一个程序在执行的某处的时候才知道需要执行某个方法,这个方法的名称是在程序的运行过程中指定的 (例如,JavaBean 开发环境中就会做这样的事),那么上面的程序演示了如何做到。
  上例中,getMethod用于查找一个具有两个整型参数且名为 add 的方法。找到该方法并创建了相应的Method 对象之后,在正确的对象实例中执行它。执行该方法的时候,需要提供一个参数列表,这在上例中是分别包装了整数 37 和 47 的两个 Integer 对象。执行方法的返回的同样是一个 Integer 对象,它封装了返回值 84。

5.创建新的对象
  对于构造器,则不能像执行方法那样进行,因为执行一个构造器就意味着创建了一个新的对象 (准确的说,创建一个对象的过程包括分配内存和构造对象)。所以,与上例最相似的例子如下:
import java.lang.reflect.*;
public class constructor2 {
public constructor2() {
}
public constructor2(int a, int b) {
System.out.println("a = " + a + " b = " + b);
}
public static void main(String args[]) {
try {
Class cls = Class.forName("constructor2");
Class partypes[] = new Class[2];
partypes[0] = Integer.TYPE;
partypes[1] = Integer.TYPE;
Constructor ct = cls.getConstructor(partypes);
Object arglist[] = new Object[2];
arglist[0] = new Integer(37);
arglist[1] = new Integer(47);
Object retobj = ct.newInstance(arglist);
} catch (Throwable e) {
System.err.println(e);
}
}
} 


  根据指定的参数类型找到相应的构造函数并执行它,以创建一个新的对象实例。使用这种方法可以在程序运行时动态地创建对象,而不是在编译的时候创建对象,这一点非常有价值。

6.改变字段(域)的值
  reflection 的还有一个用处就是改变对象数据字段的值。reflection 可以从正在运行的程序中根据名称找到对象的字段并改变它,下面的例子可以说明这一点:
import java.lang.reflect.*;
public class field2 {
public double d;
public static void main(String args[]) {
try {
Class cls = Class.forName("field2");
Field fld = cls.getField("d");
field2 f2obj = new field2();
System.out.println("d = " + f2obj.d);
fld.setDouble(f2obj, 12.34);
System.out.println("d = " + f2obj.d);
} catch (Throwable e) {
System.err.println(e);
}
}
}


这个例子中,字段 d 的值被变为了 12.34。

7.使用数组
  本文介绍的 reflection 的最后一种用法是创建的操作数组。数组在 Java 语言中是一种特殊的类类型,一个数组的引用可以赋给 Object 引用。观察下面的例子看看数组是怎么工作的:
import java.lang.reflect.*;
public class array1 {
public static void main(String args[]) {
try {
Class cls = Class.forName("java.lang.String");
Object arr = Array.newInstance(cls, 10);
Array.set(arr, 5, "this is a test");
String s = (String) Array.get(arr, 5);
System.out.println(s);
} catch (Throwable e) {
System.err.println(e);
}
}
} 

  例中创建了 10 个单位长度的 String 数组,为第 5 个位置的字符串赋了值,最后将这个字符串从数组中取得并打印了出来。
  下面这段代码提供了一个更复杂的例子:
import java.lang.reflect.*;
public class array2 {
public static void main(String args[]) {
int dims[] = new int[]{5, 10, 15};
Object arr = Array.newInstance(Integer.TYPE, dims);
Object arrobj = Array.get(arr, 3);
Class cls = arrobj.getClass().getComponentType();
System.out.println(cls);
arrobj = Array.get(arrobj, 5);
Array.setInt(arrobj, 10, 37);
int arrcast[][][] = (int[][][]) arr;
System.out.println(arrcast[3][5][10]);
}
} 

  例中创建了一个 5 x 10 x 15 的整型数组,并为处于 [3][5][10] 的元素赋了值为 37。注意,多维数组实际上就是数组的数组,例如,第一个 Array.get 之后,arrobj 是一个 10 x 15 的数组。进而取得其中的一个元素,即长度为 15 的数组,并使用 Array.setInt 为它的第 10 个元素赋值。
  注意创建数组时的类型是动态的,在编译时并不知道其类型。

例子:
package com.infowarelab.java.test; 
import java.lang.reflect.Field; 
import java.lang.reflect.Method; 
public class ReflectTester { 
    @SuppressWarnings("unchecked") 
    public Object copy(Object object) throws Exception { 
        // 获得对象类型 
        Class classType = object.getClass(); 
        System.out.println("" + classType.getName()); // 通过默认构造方法创建一个新的对象 
        Object objectCopy = classType.getConstructor(new Class[] {}) 
                .newInstance(new Object[] {}); // 获得对象的所有属性 
        Field fields[] = classType.getDeclaredFields(); 
        for (int i = 0; i < fields.length; i++) { 
            Field field = fields[i]; 
            String fieldName = field.getName(); 
            String firstLetter = fieldName.substring(0, 1).toUpperCase(); // 获得和属性对应的getXXX()方法的名字 
            String getMethodName = "get" + firstLetter + fieldName.substring(1); // 获得和属性对应的setXXX()方法的名字 
            String setMethodName = "set" + firstLetter + fieldName.substring(1); // 获得和属性对应的getXXX()方法 
            Method getMethod = classType.getMethod(getMethodName, 
                    new Class[] {}); // 获得和属性对应的setXXX()方法 
            Method setMethod = classType.getMethod(setMethodName, 
                    new Class[] { field.getType() }); // 调用原对象的getXXX()方法 
            Object value = getMethod.invoke(object, new Object[] {}); 
            System.out.println(fieldName + ":" + value); // 调用拷贝对象的setXXX()方法 
            setMethod.invoke(objectCopy, new Object[] { value }); 
        } 
        return objectCopy; 
    } 
    public static void main(String[] args) throws Exception { 
        Customer customer = new Customer("lunzi", 26); 
        customer.setId(new Long(1)); 
        Customer customerCopy = (Customer) new ReflectTester().copy(customer); 
        System.out.println("Copy information:" + customerCopy.getName() + " " 
                + customerCopy.getAge()); 
    } 
} 
class Customer { 
    private long id; 
    private String name; 
    private int age; 
    public Customer() { 
    } 
    public Customer(String name, int age) { 
        this.name = name; 
        this.age = age; 
    } 
    public String getName() { 
        return name; 
    } 
    public void setName(String name) { 
        this.name = name; 
    } 
    public int getAge() { 
        return age; 
    } 
    public void setAge(int age) { 
        this.age = age; 
    } 
    public long getId() { 
        return id; 
    } 
    public void setId(long id) { 
        this.id = id; 
    } 
}  





反射机制的应用

2.1. 基于类的反射
Class 对象为您提供接入类元数据的反射的所有基本hook。这类元数据包括关于类自身的信息,如包和类的父类,以及该类实施的接口。它还包括该类定义的构造函数、字段和方法的详细信息。这些最后的项目都是编程中最经常使用的项目,因此我将在本小节的稍后部分给出一些与它们协作的实例。

对于以下三类组件中的任何一类来说 -- 构造函数、字段和方法 --java.lang.Class 提供四种独立的反射调用,以不同的方式来获得信息。调用都遵循一种标准格式。以下是用于查找构造函数的一组反射调用:

Constructor getConstructor(Class[] params) -- 获得使用特殊的参数类型的公共构造函数,
Constructor[] getConstructors() -- 获得类的所有公共构造函数
Constructor getDeclaredConstructor(Class[] params) -- 获得使用特定参数类型的构造函数(与接入级别无关)
Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数(与接入级别无关)
每类这些调用都返回一个或多个 java.lang.reflect.Constructor 函数。这种 Constructor 类定义 newInstance 方法,它采用一组对象作为其唯一的参数,然后返回新创建的原始类实例。该组对象是用于构造函数调用的参数值。作为解释这一工作流程的实例,假设您有一个 TwoString 类和一个使用一对 String s的构造函数,如清单1所示:

清单1:从一对字符串创建的类

public class TwoString {

private String m_s1, m_s2;

public TwoString(String s1, String s2) {

m_s1 = s1;

m_s2 = s2;

}

}




清单2中的代码获得构造函数并使用它来创建使用 String s "a" 和 "b" 的 TwoString 类的一个实例:


清单2:构造函数的反射调用

Class[] types = new Class[] { String.class, String.class };

Constructor cons = TwoString.class.getConstructor(types);

Object[] args = new Object[] { "a", "b" };

TwoString ts = cons.newInstance(args); 

清单2中的代码忽略了不同反射方法抛出的多种可能选中的例外类型。例外在 Javadoc API 描述中详细记录,因此为了简明起见,我将在所有程序实例中忽略它们。

尽管我在讨论构造函数主题,Java编程语言还定义了一种您可以用来使用无参数(或缺省)构造函数创建类的一个实例的特殊快捷方式。这种快捷方式嵌入到 Class 定义中,如下:

Object newInstance() -- 使用缺省函数创建新的实例

即使这种方法只允许您使用一种特殊的构造函数,如果这正是您需要的,那么它将提供一种非常方便的快捷方式。当与JavaBeans协作时这项技术尤其有用,JavaBeans需要定义公共、无参数构造函数。

2.2. 基于字段的反射
字段,可以理解为类的属性。获得字段信息的 Class 反射调用不同于那些用于接入构造函数的调用,在参数类型数组中使用了字段名:

Field getField(String name) -- 获得命名的公共字段
Field[] getFields() -- 获得类的所有公共字段
Field getDeclaredField(String name) -- 获得类声明的命名的字段
Field[] getDeclaredFields() -- 获得类声明的所有字段
尽管与构造函数调用类似,在字段方面仍存在一个重要的区别:前两个变量返回可以通过类接入的公共字段的信息 -- 即使它们来自于祖先类。后两个变量返回类直接声明的字段的信息 -- 与字段的接入类型无关。调用返回的 java.lang.reflect.Field 实例定义所有主类型的 getXXX 和 setXXX 方法,以及与对象引用协作的通用 get 和 set 方法。您可以根据实际的字段类型自行选择一种适当的方法,而 getXXX 方法将自动处理扩展转换(如使用 getInt 方法来检索一个字节值)。

清单3显示使用字段反射方法的一个实例,以方法的格式根据名称增加对象的 int 字段:

清单3:通过反射增加一个字段

public int incrementField(String name, Object obj) throws... {

Field field = obj.getClass().getDeclaredField(name);

int value = field.getInt(obj) + 1;

field.setInt(obj, value);

return value;

}


这种方法开始展示了反射带来的某些灵活性。与特定的类协作不同, incrementField 使用传入的对象的 getClass 方法来查找类信息,然后直接在该类中查找命名的字段。

2.3. 基于方法的反射
获得方法信息的 Class 反射调用与用于构造函数和字段的调用非常类似:

Method getMethod(String name, Class[] params) -- 使用特定的参数类型,获得命名的公共方法
Method[] getMethods() -- 获得类的所有公共方法
Method getDeclaredMethod(String name, Class[] params) -- 使用特写的参数类型,获得类声明的命名的方法
Method[] getDeclaredMethods() -- 获得类声明的所有方法
与字段调用一样,前两个变量返回可以通过类接入的公共方法的信息 -- 即使它们来自于祖先类。后两个变量返回类声明的方法的信息,与方法的接入类型无关。调用返回的 java.lang.reflect.Method 实例定义一种 invoke 方法,您可以用来在正在定义的类的一个实例上调用方法。这种 invoke 方法使用两个参数,为调用提供类实例和参数值数组。

清单4进一步阐述字段实例,显示反射正在运行的方法的一个实例。这种方法增加一个定义有 get 和 set 方法的 int JavaBean属性。例如,如果对象为一个整数 count 值定义了 getCount 和 setCount 方法,您可以在一次调用中向该方法传递“count”作为 name 参数,以增加该值。


清单4:通过反射增加一个JavaBean 属性
public int incrementProperty(String name, Object obj) {

String prop = Character.toUpperCase(name.charAt(0)) +

name.substring(1);

String mname = "get" + prop;

Class[] types = new Class[] {};

Method method = obj.getClass().getMethod(mname, types);

Object result = method.invoke(obj, new Object[0]);

int value = ((Integer)result).intValue() + 1;

mname = "set" + prop;

types = new Class[] { int.class };

method = obj.getClass().getMethod(mname, types);

method.invoke(obj, new Object[] { new Integer(value) });

return value;

} 


为了遵循JavaBeans惯例,我把属性名的首字母改为大写,然后预先考虑 get 来创建读方法名, set 来创建写方法名。JavaBeans读方法仅返回值,而写方法使用值作为唯一的参数,因此我规定方法的参数类型以进行匹配。最后,该惯例要求方法为公共,因此我使用查找格式,查找类上可调用的公共方法。

这一实例是第一个我使用反射传递主值的实例,因此现在我们来看看它是如何工作的。基本原理很简单:无论什么时候您需要传递主值,只需用相应封装类的一个实例(在 java.lang 包中定义)来替换该类主值。这可以应用于调用和返回。因此,当我在实例中调用 get 方法时,我预计结果为实际 int 属性值的 java.lang.Integer 封装。

2.4. 数组的反射
数组是Java编程语言中的对象。与所有对象一样,它们都有类。如果您有一个数组,使用标准 getClass 方法,您可以获得该数组的类,就象任何其它对象一样。但是,不通过现有的实例来获得类不同于其它类型的对象。即使您有一个数组类,您也不能直接对它进行太多的操作 -- 反射为标准类提供的构造函数接入不能用于数组,而且数组没有任何可接入的字段,只有基本的 java.lang.Object 方法定义用于数组对象。数组的特殊处理使用 java.lang.reflect.Array 类提供的静态方法的集合。该类中的方法使您能够创建新数组,获得数组对象的长度,读和写数组对象的索引值。

清单5显示了一种重新调整现有数组大小的有效方法。它使用反射来创建相同类型的新数组,然后在返回新数组之前,在老数组中复制所有数据。


清单 5:通过反射来扩展一个数组
public Object growArray(Object array, int size) {

Class type = array.getClass().getComponentType();

Object grown = Array.newInstance(type, size);

System.arraycopy(array, 0, grown, 0,

Math.min(Array.getLength(array), size));

return grown;

} 


3. 实例中的经典运用
1. 得到某个对象的属性
public Object getProperty(Object owner, String fieldName) throws Exception {

Class ownerClass = owner.getClass();



Field field = ownerClass.getField(fieldName);



Object property = field.get(owner);



return property;

} 


Class ownerClass = owner.getClass():得到该对象的Class。

Field field = ownerClass.getField(fieldName):通过Class得到类声明的属性。

Object property = field.get(owner):通过对象得到该属性的实例,如果这个属性是非公有的,这里会报IllegalAccessException。

2. 得到某个类的静态属性
public Object getStaticProperty(String className, String fieldName)

throws Exception {

Class ownerClass = Class.forName(className);



Field field = ownerClass.getField(fieldName);



Object property = field.get(ownerClass);



return property;

} 



Class ownerClass = Class.forName(className) :首先得到这个类的Class。

Field field = ownerClass.getField(fieldName):和上面一样,通过Class得到类声明的属性。

Object property = field.get(ownerClass) :这里和上面有些不同,因为该属性是静态的,所以直接从类的Class里取。

3. 执行某对象的方法
public Object invokeMethod(Object owner, String methodName, Object[] args) throws Exception {

Class ownerClass = owner.getClass();

Class[] argsClass = new Class[args.length];

for (int i = 0, j = args.length; i < j; i++) {

argsClass[i] = args[i].getClass();

}

Method method = ownerClass.getMethod(methodName, argsClass);

return method.invoke(owner, args);

}


Class owner_class = owner.getClass() :首先还是必须得到这个对象的Class。

3~6行:配置参数的Class数组,作为寻找Method的条件。

Method method = ownerClass.getMethod(methodName, argsClass):通过Method名和参数的Class数组得到要执行的Method。

method.invoke(owner, args):执行该Method,invoke方法的参数是执行这个方法的对象,和参数数组。返回值是Object,也既是该方法的返回值。

4. 执行某个类的静态方法
public Object invokeStaticMethod(String className, String methodName,

Object[] args) throws Exception {

Class ownerClass = Class.forName(className);

Class[] argsClass = new Class[args.length];

for (int i = 0, j = args.length; i < j; i++) {

argsClass[i] = args[i].getClass();

}

Method method = ownerClass.getMethod(methodName, argsClass);

return method.invoke(null, args);

} 


基本的原理和实例3相同,不同点是最后一行,invoke的一个参数是null,因为这是静态方法,不需要借助实例运行。

5. 新建实例
public Object newInstance(String className, Object[] args) throws Exception {

Class newoneClass = Class.forName(className);

Class[] argsClass = new Class[args.length];

for (int i = 0, j = args.length; i < j; i++) {

argsClass[i] = args[i].getClass();

}

Constructor cons = newoneClass.getConstructor(argsClass);

return cons.newInstance(args);

} 

这里说的方法是执行带参数的构造函数来新建实例的方法。如果不需要参数,可以直接使用newoneClass.newInstance()来实现。

Class newoneClass = Class.forName(className):第一步,得到要构造的实例的Class。

第6~第10行:得到参数的Class数组。

Constructor cons = newoneClass.getConstructor(argsClass):得到构造子。

cons.newInstance(args):新建实例。

6. 判断是否为某个类的实例
public boolean isInstance(Object obj, Class cls) {

return cls.isInstance(obj);

}


7. 得到数组中的某个元素
public Object getByArray(Object array, int index) {

return Array.get(array,index);

} 

4. 性能问题和缺点
反射是一种强大的工具,但也存在一些不足。一个主要的缺点是对性能有影响。使用反射基本上是一种解释操作,您可以告诉JVM您希望做什么并且它满足您的要求。这类操作总是慢于只直接执行相同的操作。为了阐述使用反射的性能成本,我为本文准备了一组基准程序。

清单6是字段接入性能测试的一个摘用,包括基本的测试方法。每种方法测试字段接入的一种形式 -- accessSame 与同一对象的成员字段协作, accessOther 使用可直接接入的另一对象的字段, accessReflection 使用可通过反射接入的另一对象的字段。在每种情况下,方法执行相同的计算 -- 循环中简单的加/乘顺序。


清单 6:字段接入性能测试代码
public int accessSame(int loops) {

m_value = 0;

for (int index = 0; index < loops; index++) {

m_value = (m_value + ADDITIVE_VALUE) *

MULTIPLIER_VALUE;

}

return m_value;

}

public int accessReference(int loops) {

TimingClass timing = new TimingClass();

for (int index = 0; index < loops; index++) {

timing.m_value = (timing.m_value + ADDITIVE_VALUE) *

MULTIPLIER_VALUE;

}

return timing.m_value;

}

public int accessReflection(int loops) throws Exception {

TimingClass timing = new TimingClass();

try {

Field field = TimingClass.class.

getDeclaredField("m_value");

for (int index = 0; index < loops; index++) {

int value = (field.getInt(timing) +

ADDITIVE_VALUE) * MULTIPLIER_VALUE;

field.setInt(timing, value);

}

return timing.m_value;

} catch (Exception ex) {

System.out.println("Error using reflection");

throw ex;

}

}



测试程序重复调用每种方法,使用一个大循环数,从而平均多次调用的时间衡量结果。平均值中不包括每种方法第一次调用的时间,因此初始化时间不是结果中的一个因素。在为本文进行的测试中,每次调用时我使用1000万的循环数,在1GHz PIIIm系统上运行。三个不同Linux JVM的计时结果如图1所示。所有测试使用每个JVM的缺省设置。


图 1:字段接入时间


上表的对数尺度可以显示所有时间,但减少了差异看得见的影响。在前两副图中(Sun JVM),使用反射的执行时间超过使用直接接入的1000倍以上。通过比较,IBM JVM可能稍好一些,但反射方法仍旧需要比其它方法长700倍以上的时间。任何JVM上其它两种方法之间时间方面无任何显著差异,但IBM JVM几乎比Sun JVM快一倍。最有可能的是这种差异反映了Sun Hot Spot JVM的专业优化,它在简单基准方面表现得很糟糕。

反射有两个缺点。第一个是性能问题。当用于字段和方法接入时反射要远慢于直接代码。性能问题的程度取决于程序中是如何使用反射的。如果它作为程序运行中相对很少涉及的部分,缓慢的性能将不会是一个问题。即使测试中最坏情况下的计时图显示的反射操作只耗用几微秒。仅反射在性能关键的应用的核心逻辑中使用时性能问题才变得至关重要。

许多应用更严重的一个缺点是使用反射会模糊程序内部实际要发生的事情。程序人员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术会带来维护问题。反射代码比相应的直接代码更复杂,正如性能比较的代码实例中看到的一样。解决这些问题的最佳方案是保守地使用反射-- 仅在它可以真正增加灵活性的地方 -- 记录其在目标类中的使用
分享到:
评论

相关推荐

    java 反射机制详解

    Java 反射机制是 Java 语言中的一个重要特性,它允许程序在运行时动态地获取类的信息(如类名、属性、方法等)并调用对象的方法,甚至修改对象的状态。这一机制极大地增强了 Java 程序的灵活性和可扩展性,尤其是在...

    JAVA反射机制详解

    ### JAVA反射机制详解 #### 一、JAVA反射机制概述 反射是Java编程语言的一个核心特性,它允许程序在运行时获取类的信息,并且可以动态地创建对象和调用方法。这种能力使得Java程序具备了高度的灵活性和扩展性。...

    基于Python的上海交通大学开源硬件实践课程设计源码仓库

    本项目是一个上海交通大学开源硬件实践课程的设计源码仓库,主要采用Python编程语言。该仓库包含62个文件,包括27个Python源代码文件、13个PNG图像文件、5个JPG图片文件、4个Markdown文档文件、4个XML配置文件、2个Git忽略文件、2个MP4视频文件以及1个项目许可证文件。这些资源共同构成了一个全面的开源硬件实践课程作业项目。

    长春大学在辽宁2020-2024各专业最低录取分数及位次表.pdf

    那些年,与你同分同位次的同学都去了哪里?全国各大学在辽宁2020-2024年各专业最低录取分数及录取位次数据,高考志愿必备参考数据

    支路电气介数的matlab仿真, 并对比HVDC,FACTS-TCSC,FACTS-UPFC HVDC、FACTS(包含TCS

    支路电气介数的matlab仿真, 并对比HVDC,FACTS-TCSC,FACTS-UPFC。 HVDC、FACTS(包含TCSC和UPFC)三种简化模型在电气介数计算中的体现形式为:对测试系统,可以在系统任意数量和位置的线路上对以上三种简化模型进行选择。 得出考虑三种模型后的系统中支路和节点的电气介数计算值和分布情况。

    数据手册-PDIUSBD12-datasheet.zip

    数据手册-PDIUSBD12-datasheet.zip

    白城师范学院在辽宁2020-2024各专业最低录取分数及位次表.pdf

    那些年,与你同分同位次的同学都去了哪里?全国各大学在辽宁2020-2024年各专业最低录取分数及录取位次数据,高考志愿必备参考数据

    基于JavaScript及Java、CSS、HTML的微服务架构学习设计源码

    本项目是专注于微服务架构学习的综合源码库,由152个文件组成,涵盖了72个Java类文件、25个XML配置文件、14个YAML文件、12个JavaScript文件、8个CSS样式表、6个PNG图像文件、4个YAML文件、4个HTML文件、2个Markdown文档和2个字体文件。该库融合了JavaScript、Java、CSS和HTML等技术,旨在帮助开发者深入理解微服务架构的构建与实现。

    基于Jupyter Notebook平台的vosk-api深度学习语音识别设计源码

    该项目为基于Jupyter Notebook平台的vosk-api深度学习语音识别设计源码,总计包含223个文件,涵盖了多种编程语言,包括41个Kotlin文件、23个Shell脚本、21个Python脚本、16个Java类、12个Markdown文档、12个XML配置文件、10个C头文件、8个C#源文件、8个JavaScript文件、7个C++源文件、8个C源文件、8个Ruby源文件。该项目适用于需要进行深度学习语音识别开发的研究者和开发者。

    C#部署yolov11-obb旋转框检测onnx模型源码.zip

    【测试环境】 vs2019,netframework4.7.2,onnxruntime1.16.3 更多实现细节参考博文:https://blog.csdn.net/FL1623863129/article/details/142719514

    和印尼植物相关的图像数据集

    和印尼植物相关的图像数据集 数据说明: 该数据集呈现在您面前的是部分印尼植物图像,包括五种类型的植物,这些植物使印度尼西亚的植物群多样化。 五个类别分别为:1.钛无花果2.爪哇花3.冷冻甘蓝(黑兰)5.拉夫莱西亚阿诺迪亚(巨型棕榈花) 该数据集共包含2158张相关的图像。

    车位锁倍速链组装流水线_三维3D设计图纸.zip

    车位锁倍速链组装流水线_三维3D设计图纸.zip

    pcb上下板机_三维3D设计图纸.zip

    pcb上下板机_三维3D设计图纸.zip

    基于Jupyter Notebook的Python与HTML/CSS作业设计源码

    该项目为基于Jupyter Notebook的Python、HTML和CSS作业设计源码,总计包含37个文件,涵盖15个Python脚本、9个交互式Notebook文件、4个XML配置文件、4个HTML页面、4个CSS样式表、2个Python编译文件、1个IntelliJ IDEA项目文件、1个Markdown文件。这些文件共同构成一个综合性的作业设计,适用于学习与展示如何将Python编程与网页设计相结合。

    液压弯管机_三维3D设计图纸.zip

    液压弯管机_三维3D设计图纸.zip

    自动钢丝折弯机_三维3D设计图纸.zip

    自动钢丝折弯机_三维3D设计图纸.zip

    基于Java语言的if-cms内容管理系统设计源码

    该项目为基于Java语言的if-cms内容管理系统源码,包含4117个文件,涵盖1061个GIF图片、871个Java源文件、668个JavaScript文件、488个HTML文件、333个CSS文件、316个PNG图片、167个JSP文件、42个JPG图片、17个文本文件、16个XML文件。该系统集成了多种编程语言,功能完善,适用于各种内容管理需求。

    基于Java开发的网上书店项目后台设计源码

    该项目为基于Java开发的网上书店后台管理系统源码,共计632个文件,其中Java源代码文件531个,XML配置文件77个,其他类型文件包括19个配置文件、1个Git忽略文件、1个ICO图标文件、1个Maven构建文件、1个命令行文件、1个模板文件。该系统旨在提供完整的后台功能,以支持网上书店的运营管理。

    一键离线安装部署 Docker Community Edition 版本 docker-ce-24.0.7.tar.gz

    内容概要 本安装程序提供一键安装功能,用于部署 Docker Community Edition 版本 docker-ce-24.0.7.tar.gz,专为 x86 架构设计。此版本已在 Linux 内核操作系统如 CentOS 等上进行优化,确保兼容性和稳定性。CentOS 7.9 的安装测试已证明成功。 适用人群 适合系统管理员、软件开发者以及任何需要在 x86 架构的 Linux 系统上快速部署 Docker 环境的专业技术人员。 适用场景及目标 该安装程序旨在简化 Docker 的安装过程,使用户能够快速在各种企业和开发环境中部署容器化应用。特别适合进行持续集成和持续部署(CI/CD)的开发环境,以及需要快速迭代和高可扩展性的生产环境。 其他说明 在安装前,请确保目标系统满足 Docker 的最低系统要求。此外,由于这是一个社区版,用户可能需要自行解决一些技术支持和兼容性问题。建议在测试环境中先行验证安装程序,以确认其在生产环境中的表现。

Global site tag (gtag.js) - Google Analytics