Java 反射

反射:能够分析类能力的程序。

反射机制的作用:
•在运行时分析类的能力。
•在运行时查看对象, 例如, 编写一个 toString 方法供所有类使用。
•实现通用的数组操作代码。
•利用 Method 对象, 这个对象很像中的函数指针。

本文包含如下内容:

  1. class类
  2. 捕获异常
  3. 利用反射分析类的能力
  4. 在运行时使用反射分析对象
  5. 使用反射编写泛型数组代码
  6. 调用任意方法

1.class类

在程序运行期间,Java 运行时系统始终为所有的对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类。 虚拟机利用运行时类型信息选择相应的方法执行。

可以通过专门的Java类访问这些信息,保存这些信息的类称为Class。Object类中的getClass()方法会返回一个Class类型的实例。

Employee e;
Class cl = e.getClassO;

最常用的Class方法是getName(),这个方法返回类的名字。
例如下面这条语句:

System.out.println(e.getClass().getName() + " " + e.getNameO);

如果 e 是一个雇员,则会打印输出:

Employee Harry Hacker

如果 e 是经理, 则会打印输出:

Manager Harry Hacker

如果类在一个包里,包的名字也作为类名的一部分:

Random generator = new Random0:
Class cl = generator.getClass() ;
String name = cl.getNameQ; // name is set to "java.util.Random"

还可以调用静态方法 forName 获得类名对应的 Class 对象。

String dassName = "java.util .Random";
Class cl = Cl ass.forName(dassName) ;

如果类名保存在字符串中, 并可在运行中改变, 就可以使用这个方法。当然, 这个方法
只有在 dassName 是类名或接口名时才能够执行。 否则,forName 方法将抛出一个 checked
exception ( 已检查异常)。无论何时使用这个方法, 都应该提供一个异常处理器(exception
handler) o 如何提供一个异常处理器,请参看下一节。

获得 Class 类对象的第三种方法非常简单。如果 T 是任意的 Java 类型(或 void 关键字,)
T.class 将代表匹配的类对象。例如:

Class dl = Random,class; // if you import java.util
Gass cl 2 = int.class;
Class cl 3 = Doublet] ,cl ass;

请注意, 一个 Class 对象实际上表示的是一个类型, 而这个类型未必一定是一种类。 例如,
int 不是类, 但 int.class 是一个 Class 类型的对象。

Class 类实际上是一个泛型类。例如, Employee.class 的类型是 Class<Employee。
没有说明这个问题的原因是: 它将已经抽象的概念更加复杂化了。 在大多数实际问题中, 可以忽略类型参数, 而使用原始的 Class 类。

注意:鉴于历史原因, getName 方法在应用于数组类型的时候会返回一个很奇怪的名字:

•Double[ ] class.getName( ) 返回“ [Ljava.lang.Double;’’
•int[ ].class.getName( ) 返回“ [I ” ,

虚拟机为每个类型管理一个 Class 对象。 因此, 可以利用= 运算符实现两个类对象比较的操作。 例如,

if (e.getClass()== Employee, class) . . .

还有一个很有用的方法 newlnstance( ), 可以用来动态地创建一个类的实例例如,

e.getClass0.newlnstance();

创建了一个与 e 具有相同类类型的实例。
newlnstance 方法调用默认的构造器 (没有参数的构造器)初始化新创建的对象。 如果这个类没有默认的构造器, 就会抛出一个异常。
将 forName 与 newlnstance 配合起来使用, 可以根据存储在字符串中的类名创建一个对象

String s = "java.util .Random";
Object m = Cl ass.forName(s) .newlnstance();

注意:
如果需要以这种方式向希望按名称创建的类的构造器提供参数, 就不要使用上面那条语句, 而必须使用 Constructor 类中的 newlnstance 方法。

C++ 注释:newlnstance 方法对应 C++ 中虚拟构造器的习惯用法。 然而,C++ 中的虚拟构造器不是一种语言特性, 需要由专门的库支持。 Class 类与 C++ 中的 typejnfo 类相似,getClass 方法与 C++ 中的 typeid 运算符等价。 但 Java 中的 Class 比 C++ 中的 type_info的功能强。C++ 中的 typejnfo 只能以字符串的形式显示一个类型的名字, 而不能创建那个类型的对象。

2. 捕获异常

捕获异常的处理器可以对异常情况进行处理
如果没有提供处理器, 程序就会终止,并在控制台上打印出一条信息, 其中给出了异常的类型。

异常有两种类型: 未检查异常已检查异常

  1. 对于已检查异常, 编译器将会检查是否提供了处理器。
  2. 然而, 有很多常见的异常, 例如, 访问 null 引用, 都属于未检查异常。 编译器不会査看是否为这些错误提供了处理器。毕竟,应该精心地编写代码来避免这些错误的发生, 而不要将精力花在编写异常处理器上。

并不是所有的错误都是可以避免的。 如果竭尽全力还是发生了异常, 编译器就要求提供一个处理器。
Class.forName 方法就是一个抛出已检查异常的例子。
接下来介绍如何实现最简单的处理器:
将可能抛出已检査异常的一个或多个方法调用代码放在 try 块中,然后在 catch 子句中提供处理器代码

try
{
statements that might throw exceptions
}
catch (Exception e)
{
handler action
}

下面是一个示例:

try
{
String name = . . .; // get class name
Class cl = Class.forName(name) ; // might throw exception
do something with cl
}
catch (Exception e)
{
e.printStackTraceO ;
}

能抛出已检査异常的一个或多个方法调用代码放在 try 块中,然后在 catch 子句中提供处理器代码。

如果类名不存在, 则将跳过 try 块中的剩余代码, 程序直接进人 catch 子句(这里,利用Throwable 类的 printStackTrace 方法打印出栈的轨迹。Throwable 是 Exception 类的超类)。 如果 try 块中没有抛出任何异常, 那么会跳过 catch 子句的处理器代码。
对于已检查异常, 只需要提供一个异常处理器。 可以很容易地发现会抛出已检査异常的方法。如果调用了一个抛出已检查异常的方法, 而又没有提供处理器, 编译器就会给出错误报告.

java.lang.Class 1.0
static Class forName(String className)
返回描述类名为 className 的 Class 对象。
Object newlnstance()
返回这个类的一个新实例。

java.Iang.reflect.Constructor 1.1
Object newlnstance(Object[] args)
构造一个这个构造器所属类的新实例。
参数:args 这是提供给构造器的参数。

java.Iang.Throwable 1.0
void printStackTrace()
将 Throwable 对象和栈的轨迹输出到标准错误流。

3.利用反射分析类的能力

下面简要地介绍一下反射机制最重要的内容—检查类的结构
在 java.lang.reflect 包中有三个类 Field、 Method 和 Constructor 分别用于描述类的域、 方法和构造器。 这三个类都有一个叫做 getName 的方法, 用来返回项目的名称。

Field 类有一个 getType 方法, 用来返回描述域所属类型的 Class 对象。
Method 和 Constructor 类有能够报告参数类型的方法,
Method 类还有一个可以报告返回类型的方法。

这 三个类还有一个叫做 getModifiers 的方法, 它将返回一个整型数值, 用不同的位开关描述 public 和static 这样的修饰符使用状况。另外, 还可以利用java.lang.reflect 包中的 Modifier类的静态方法分析getModifiers 返回的整型数值。 例如, 可以使用 Modifier 类中的 isPublic、 isPrivate 或 isFinal判断方法或构造器是否是 public、 private 或 final。

我们需要做的全部工作就是调用 Modifier类的相应方法, 并对返回的整型数值进行分析, 另外,还可以利用 Modifier.toString方法将修饰符打印出来。

Class类中的 getFields、 getMethods 和 getConstructors 方 法 将 分 别 返 回 类 提 供 的public 域、 方法和构造器数组, 其中包括超类的公有成员。
Class 类的 getDeclareFields、getDeclareMethods 和getDeclaredConstructors 方法将分别返回类中声明的全部域、 方法和构造器, 其中包括私有和受保护成员,但不包括超类的成员。

下面程序显示了如何打印一个类的全部信息的方法。这个程序将提醒用户输入类名,然后输出类中所有的方法和构造器的签名, 以及全部域名。

package reflection;

import java.util
 import java.lang.reflect.*;


11 public class ReflectionTest
12 {
13 public static void main(String[] args)
14 {
is // read class name from command line args or user input
16 String name;
17 if (args.length > 0) name = args[0];
18 else
19 {
20 Scanner in = new Scanner(System.in);
21 System.out.println("Enter class name (e.g. java.util.Date): ");
22 name = in.nextO;
23 }
24
2s try
26 {
27 // print class name and superclass name (if != Object)
28 Class cl = Class.fo「Name(name);
29 Class supercl = cl.getSuperclassO;
30 String modifiers = Modifier.toString(cl.getModifiers());
31 if (modi fiers.lengthO > 0) System,out.print(modifiers + " ");
32 System.out.print("class"+ name);
33 if (s叩ercl != null && supercl != Object,cl ass) System.out.print(" extends "
34 + supercl.getNameO);
35
36 System.out.print("\n{\n");
37 printConstructors(d);
38 System.out.printlnO;
39 printMethods(cl);

System.out.printlnO;
printFields(cl);
System.out.println("}");
}
catch (ClassNotFoundException e)
{
e.printStackTraceO;
}
System.exit(O) ;

public static void printConstructors(Class cl)
{
Constructor ]] constructors = cl.getDedaredConstructors():
for (Constructor c : constructors)
{
String name = c.getNameO ;
System.out.print(" ") ;
String modifiers = Modifier.toString(c.getModifiers());
if (modifiers.lengthO > 0) System,out.print(modifiers + " ");
System,out.print(name + "(");
// print parameter types
Class[] paramTypes = c.getParameterTypes();
for (int j = 0; j < paramTypes.length; j++)
{
if (j > 0) System.out.print(", ");
System.out.print(paramTypes[j].getName());
}
System.out.println(");");

public static void printMethods(Class cl)
{
Method□ methods - cl.getDedaredMethodsO ;
for (Method m : methods)
{
Class retType = m.getReturnType();
String name = m. getName();
System.out.print(" ••);
//print modifiers, return type and method name
String modifiers = Modi fier.toString(m.getModi fiers());
if (modifiers.lengthO > 0) System.out.print(modifiers + n ");
System. out.print(retType. getName() +"" + name +"(");

//print parameter types
Class[] paramTypes = m.getParamete「Types();
for (int j = 0; j < paramTypes.length; j++)
{
if (j > 0) System.out.print(" f ");
System.out.print(paramTypes[j].getName());
}
System.out.println(") ;") ;

public static void printFields(Cl ass cl)
{
Field[] fields = cl.getDedaredFieldsO;
for (Field f : fields)
{
Class type = f.getTypeO;
String name = f.getName();
System.out.print(" ");
String modifiers = Modifier.toString(f.getModifiers());
if (modifiers.lengthO > 0) System,out.print(modifiers + " ";)
System.out.println(type.getName() + " " + name +";");
}

这个程序可以分析 Java 解释器能够加载的任何类, 而不仅仅是编译程序时可以使用的类。

java.lang.Class 1.0

• Field[] getFields() 1.1
• Filed[] getDeclaredFie1ds() 1.1

getFields 方法将返回一个包含 Field 对象的数组, 这些对象记录了这个类或其超类的公有域。getDeclaredField 方法也将返回包含 Field 对象的数组, 这些对象记录了这个类的全部域。 如果类中没有域, 或者 Class 对象描述的是基本类型或数组类型, 这些方法将返回一个长度为 0 的数组。

• Method[] getMethods() 1.1
• Method[] getDeclareMethods() 1.1

返回包含 Method 对象的数组:getMethods 将返回所有的公有方法, 包括从超类继承来的公有方法;getDeclaredMethods 返回这个类或接口的全部方法, 但不包括由超类继承了的方法。

• Constructor[] getConstructors() 1.1
• Constructor[] getDeclaredConstructors() 1.1

返回包含 Constructor 对象的数组, 其中包含了 Class 对象所描述的类的所有公有构造(getConstructors) 或所有构造器(getDeclaredConstructors)。

java.lang.reflect.Field 1.1
java.lang.reflect.Method 1.1
java.lang.reflect.Constructor 1.1

Class getDeclaringClass( )
返冋一个用于描述类中定义的构造器、 方法或域的 Class 对象。

• Class[ ] getExceptionTypes ( ) ( 在 Constructor 和 Method 类 中)

返回一个用于描述方法抛出的异常类型的 Class 对象数组。

• int getModifiers( )

返回一个用于描述构造器、 方法或域的修饰符的整型数值。使用 Modifier 类中的这个方法可以分析这个返回值。

• String getName( )

返冋一个用于描述构造器、 方法或域名的字符串。

• Class[ ] getParameterTypes ( ) ( 在 Constructor 和 Method 类 中)

返回一个用于描述参数类型的 Class 对象数组。

• Class getReturnType( ) ( 在 Method 类 中)

返回一个用于描述返回类型的 Class 对象。

java.lang.reflect.Modifier 1.1

在这里插入图片描述

4.在运行时使用反射分析对象

从前面一节中, 已经知道如何查看任意对象的数据域名称和类型:
•获得对应的 Class 对象。
•通过 Class 对象调用 getDeclaredFields。

本节将进一步查看数据域的实际内容。当然, 在编写程序时, 如果知道想要査看的域名和类型, 查看指定的域是一件很容易的事情。而利用反射机制可以查看在编译时还不清楚的对象域。

查看对象域的关键方法是 Field类中的 get 方法。 如果 f 是一个 Field 类型的对象(例如,通过 getDeclaredFields 得到的对象,) obj 是某个包含 f 域的类的对象,f.get(obj) 将返回一个对象,其值为 obj 域的当前值。

Employee harry = new Employee("Harry Hacker", 35000, 10, 1, 1989);
Class cl = harry.getClass0;
// the class object representing Employee
Field f = cl .getDeclaredFieldC'name"):
// the name field of the Employee class
Object v = f.get(harry);
// the value of the name field of the harry object, i .e., the String object "Harry Hacker"

反射机制的默认行为受限于 Java 的访问控制。然而, 如果一个 Java 程序没有受到安全管理器的控制, 就可以覆盖访问控制。 为了达到这个目的, 需要调用 Field、 Method 或Constructor 对象的 setAccessible 方法。例如:

f.setAtcessible(true); // now OK to call f.get(harry) ;

setAccessible 方法是 AccessibleObject 类中的一个方法, 它是 Field、 Method 和 Constructor类的公共超类。这个特性是为调试、 持久存储和相似机制提供的。

get 方法还有一个需要解决的问题。
name 域是一个 String, 因此把它作为 Object 返回没有什么问题。但是, 假定我们想要查看 salary 域。它属于 double 类型, 而 Java 中数值类型不是对象。
要想解决这个问题, 可以使用 Field 类中的 getDouble方法,也可以调用 get方法, 此时, 反射机制将会自动地将这个域值打包到相应的对象包装器中, 这里将打包成Double。

当然,可以获得就可以设置。 调用 f.set(obj ,value) 可以将 obj 对象的 f 域设置成新值。

程序清单 5-14 显示了如何编写一个可供任意类使用的通用 toString方法。 其中使用getDeclaredFileds 获得所有的数据域, 然后使用 setAccessible 将所有的域设置为可访问的。 对于每个域,获得了名字和值。程序清单 5-14 递归调用 toString方法, 将每个值转换成字符串。

泛型 toString方法需要解释几个复杂的问题。循环引用将有可能导致无限递归。因此,ObjectAnalyzer 将记录已经被访问过的对象。 另外, 为了能够査看数组内部, 需要采用一种不同的方式。
程序清单 5-14 objectAnalyzer/ObjectAnalyzerTest.java

package objectAnalyzer;
import java.util.ArrayList;
public class ObjectAnal yzerTest
 {
	  public static void main(String[] args)
	  {
 			ArrayList<Integer> squares = new ArrayLi st<>() ;
			 for (inti = 1;i <= 5;i++)
			 squares.add(i * i);
 			System,out.println(new ObjectAnalyzer().toString(squares));
	}

}

java.Iang.reflect.AccessibleObject 1.2

• void setAccessible(boolean flag)

为反射对象设置可访问标志。 flag 为 true 表明屏蔽 Java 语言的访问检查,使得对象的私有属性也可以被査询和设置。

• boolean isAccessible( )

返回反射对象的可访问标志的值。

• static void setAccessible(AccessibleObject[ ] array,boolean flag)

是一种设置对象数组可访问标志的快捷方法。

java.lang.Class 1.1

•Field getField( String name )
•Field[] getField()

返回指定名称的公有域, 或包含所有域的数组

• Field getDeclaredField( String name )
•Field[] getDeclaredFields()

返回类中声明的给定名称的域, 或者包含声明的全部域的数组。

java.Iang.reflect.Field 1.1

•Object get( Object obj )

返回 obj 对象中用 Field 对象表示的域值。

• void set(Object obj ,Object newValue)

用一个新值设置 Obj 对象中Field 对象表示的域。

5.使用反射编写泛型数组代码

java.lang.reflect 包中的 Array 类允许动态地创建数组。 例如, 将这个特性应用到 Array类中的 copyOf 方法实现中, 应该记得这个方法可以用于扩展已经填满的数组。

Employee[] a = new Employee[100]:
// array is full
a = Arrays.copyOf(a, 2 * a.length);

上面代码使得本来长为 100 的数组变为长 200。

下面展示将 Employee[ ] 数组转换为 Object[ ] 数组:

public static Object口 badCopyOf(Object[] a, int newLength) // not useful
{
	Object[] newArray = new Object[newlength]:
	System.arraycopy(a, 0, newArray, 0, Math.min(a.length, newLength));
    return newArray;
}

一个对象数组不能转换成雇员数组(Employee[ ])。 如果这样做, 则在运行时 Java 将会产生 ClassCastException 异常。
将一个 Employee[ ]临时地转换成 Object[ ] 数组, 然后再把它转换回来是可以的,但一 从开始就是 Object[ ] 的数组却永远不能转换成 Employe [ ] 数组。

为了编写这类通用的数组代码, 需要能够创建与原数组类型相同的新数组。为此, 需要 java.lang.reflect 包中 Array 类的一些方法。其中最关键的是 Array类中的静态方法 newlnstance,它能够构造新数组。在调用它时必须提供两个参数,一个是数组的元素类型,一个是数组的长度。

Object newArray = Array.newlnstance(componentType, newLength) ;

可以通过调用 Array.getLength(a) 获得数组的长度, 也可以通过 Array类的静态 getLength方法的返回值得到任意数组的长度。而要获得新数组元素类型,就需要进行以下工作:
1 ) 首先获得 a 数组的类对象。
2 ) 确认它是一个数组。
3 ) 使用 Class 类(只能定义表示数组的类对象)的 getComponentType 方法确定数组对应的类型。

为什么 getLength 是 Array 的方法,而 getComponentType 是 Class 的方法呢? 我们也不清楚。反射方法的分类有时确实显得有点古怪。下面是这段代码:

public static Object goodCopyOf(Object a, int newLength)
{
	Class cl = a.getClass();
    if (!cl.isArray()) return null ;
    Class componentType = cl .getComponentType0;
    int length = Array.getLength(a);
    Object newArray = Array.newlnstance(componentType, newLength):
    System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength)) ;
    return newArray;
}

请注意,这个 CopyOf 方法可以用来扩展任意类型的数组, 而不仅是对象数组。

int[] a = { 1,2, 3, 4, 5 };
a = (int[]) goodCopyOf(a, 10) ;

为了能够实现上述操作,应该将 goodCopyOf 的参数声明为 Object 类型,.而不要声明为对象型数组(Object[])。整型数组类型 int[] 可以被转换成 Object ,但不能转换成对象数组。
在这里插入图片描述

6.调用任意方法

为了能够看到方法指针的工作过程, 先回忆一下利用 Field 类的 get 方法查看对象域的过程。与之类似, 在 Method 类中有一个 invoke 方法, 它允许调用包装在当前 Method 对象中的方法。invoke 方法的签名是:

Object invoke(Object obj, Object... args)

第一个参数是隐式参数, 其余的对象提供了显式参数。
对于静态方法,第一个参数可以被忽略, 即可以将它设置为 null。
例如, 假设用 ml 代表 Employee 类的 getName 方法,下面这条语句显示了如何调用这个方法:

String n = (String) ml.invoke(harry)

如果返回类型是基本类型, invoke 方法会返回其包装器类型。 例如, 假设 m2 表示Employee 类的 getSalary 方法, 那么返回的对象实际上是一个 Double, 必须相应地完成类型转换。可以使用自动拆箱将它转换为一个 double:

double s = (Double) m2,invoke(harry);

如何得到 Method 对象呢? 当然, 可以通过调用 getDeclareMethods 方法, 然后对返回的 Method 对象数组进行查找, 直到发现想要的方法为止。 也可以通过调用 Class 类中的getMethod方法得到想要的方法。它与 getField 方法类似。getField 方法根据表示域名的字符串,返回一个 Field 对象。然而, 有可能存在若干个相同名字的方法, 因此要格外小心,以确保能够准确地得到想要的那个方法。有鉴于此, 还必须提供想要的方法的参数类型。getMethod 的签名是:

Method getMethod(String name, Class... parameterTypes)

例如, 下面说明了如何获得 Employee 类的 getName 方法和 raiseSalary 方法的方法指针。

Method ml = Employee.class.getMethod("getName");
Method m2 = Employee.class.getMethod("raiseSalary", double.class);

java.Iang.reflect.Method 1.1

•public Object invokeCObject implicitParameter,Object[] explicitParamenters)

调用这个对象所描述的方法, 传递给定参数,并返回方法的返回值。对于静态方法,把 null 作为隐式参数传递。 在使用包装器传递基本类型的值时, 基本类型的返回值必须是未包装的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值