【java基础(三十一)】反射(一)

反射类(reflection library)提供了一个非常丰富且精心设计的工具类,以便编写能够动态操作Java代码的程序。特别是在设计或运行中添加新类时,能够快速地应用开发工具动态地查询添加类的能力。

能够分析类能力的程序称为反射(reflective)。反射机制的功能极其强大,反射可以用来:

  • 在运行时分析类的能力。
  • 在运行时查看对象,如,编写一个toString方法提供所有类使用。
  • 实现通用的数组操作代码。
  • 利用Method对象。

反射是一种功能强大且复杂的机制。使用它的主要人员是工具构造者,而不是应用程序员。如果仅对设计应用程序感兴趣,而对构造工具不感兴趣,可以不看这个博文。以后再返回来学习了解。

Class类

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

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

Employee e;
...
Class cl = e.getClass();

如同用一个Employee对象表示一个特定的雇员属性一样,一个Class对象将表示一个特定类的属性。最常用的Class方法是getName。这个方法返回类的名字。例如,

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

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

Employee Harry Hacker

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

Manager Harray Hacker

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

Random generator = new Random();
Class cl = generator.getClass();
String name = cl.getName(); // 输出:java.util.Random

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

String className = "java.util.Random";
Class cl = Class.forName(className);

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

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

Class cl1 = Random.class;
Class cl2 = int.class;
Class cl3 = Double[].class;

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

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

  • Double[].class.getName()返回[LJava.lang.Double;
  • int[].class.getName()放回[I

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

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

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

e.getClass().newInstance();

创建了一个与e具有相同类型的实例。newInstance方法调用默认的构造器(没有参数的构造器)初始化新创建的对象。如果这个类没有默认的构造器,就会抛出一个异常。

将forName于newInstance配合起来使用,可以根据存储在字符串中的类名创建一个对象。

String s = "java.util.Random";
Object m = Class.forName(s).newInstance();

捕获异常

以后我们详细在说明异常处理机制,现在我们先了解一下平时遇到的一些方法如何抛出异常。

当程序运行过程中发生错误时,就会“抛出异常”。抛出异常比终止程序要灵活很多,这是因为可以提供一个“捕获”异常的处理器(handler)对异常情况进行处理。

如果没有提供处理器,程序就会终止,并在控制台上打印出一条信息,其中给出了异常的类型。前面我们应该遇到过很多。

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

并不是所有的错误都可以避免的。如果竭尽全力还是发生了异常,编译器就要求提供一个处理器。Class.forName方法就是一个抛出已检查异常的例子,我们来看一种最简单的处理器:

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

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

如:

try {
	String name = ...;
	Class cl = Class.forName(name);
	do something with cl;
} catch (Exception e) {
	e.printStrackTrace();
}

如果类名不存在,则将跳过try块中剩余代码,程序直接进入catch字句。如果try块中没有抛出任何异常,那么会跳过catch字句的处理器代码。

对于已检查异常,只需要提供一个异常处理器。可以很容易地发现会抛出已检查异常的方法。如果调用了一个抛出已检查异常的方法,而又没有提供处理器,编译器就会给出错误报告。

捐赠

若你感觉读到这篇文章对你有启发,能引起你的思考。请不要吝啬你的钱包,你的任何打赏或者捐赠都是对我莫大的鼓励。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章