(run-time type identification ,RTTI)
Class对象是RTTI的核心,每个类都有一个class对象。每当编写并且编译一个新类,就会产生一个Class对象(被保存在同名的.class文件当中)
Class.forName("classname"),如果对象没有加载就加载对象(这将会触发类的静态初始化)
Class.newInstance()用来产生一个对象。如
Class m = Class.forName("classname");//1
Object o = m.newInstance();//2
java也提供" 类字面常量" 的机制生成对象的引用。像这样:
A.class
对于基本类型,boolean.class === Boolean.TYPE , char.class ===Character.TYP
void.class ===Void.TYPE,等等
那么也可以用Class m = char.class; //或者 Class m = <aclass><aclass>.class
Object o = m.newInstance();
((Char)o).××
instanceof 关键字用于检查对象是不是某个特定类型的实例。这用于类型转换前做检测。如:
if ( x instanceof Dog )
((Dog)x).bark();
</aclass>除了 instanceof 关键字以外,还可以使用 Class.isInstance() 方法,两者功能相同。
<aclass>
instanceof的替代方案是: x.getClass == Y.class 或者x.getClass.equals( Y.class)
Class对象的getInterfaces()获得接口,getSurperClass 或者获得超类。
下面看一个thinking in java中的例子:
-
1
.
//: ToyTest.java
- 2
.
// Testing class Class
- 3
.
interface
HasBatteries {}
- 4
.
interface
Waterproof {}
- 5
.
interface
ShootsThings {}
- 6
.
class
Toy {
- 7
.
// Comment out the following default
- 8
.
// constructor to see
- 9
.
// NoSuchMethodError from (*1*)
- 10
. Toy() {}
- 11
. Toy(
int
i) {}
- 12
. }
- 13
.
class
FancyToy
extends
Toy
- 14
.
implements
HasBatteries,
- 15
. Waterproof, ShootsThings {
- 16
. FancyToy() {
super
(
1
); }
- 17
. }
- 18
.
public
class
ToyTest {
- 19
.
public
static
void
main(String[] args) {
- 20
. Class c =
null
;
- 21
.
try
{
- 22
. c = Class.forName(
"thinking.java.example_c11.FancyToy"
);
- //此处的FancyToy类名要把包名包含全,不然编译器找不到
- 23
. }
catch
(ClassNotFoundException e) {}
- 24
. printInfo(c);
- 25
. Class[] faces = c.getInterfaces();
- 26
.
for
(
int
i =
0
; i < faces.length; i++)
- 27
. printInfo(faces[i]);
- 28
. Class cy = c.getSuperclass();
- 29
. Object o =
null
;
- 30
.
try
{
- 31
.
// Requires default constructor:
- 32
. o = cy.newInstance();
// (*1*)
- 33
. }
catch
(InstantiationException e) {}
- 34
.
catch
(IllegalAccessException e) {}
- 35
. printInfo(o.getClass());
- 36
. }
- 37
.
static
void
printInfo(Class cc) {
- 38
. System.out.println(
- 39
.
"Class name: "
+ cc.getName() +
- 40
.
" is interface? ["
+
- 41
. cc.isInterface() +
"]"
);
- 42
. }
- 43
. }
///
从中可以看出,class FancyToy 相当复杂,因为它从Toy 中继承,并实现了HasBatteries,Waterproof 以
及ShootsThings 的接口。在main()中创建了一个Class 句柄,并用位于相应try 块内的forName()初始化成
FancyToy。
Class.getInterfaces 方法会返回Class 对象的一个数组,用于表示包含在Class 对象内的接口。
若有一个Class 对象,也可以用getSuperclass()查询该对象的直接基础类是什么。当然,这种做会返回一
个Class 句柄,可用它作进一步的查询。这意味着在运行期的时候,完全有机会调查到对象的完整层次结构。
若从表面看,Class 的newInstance()方法似乎是克隆(clone())一个对象的另一种手段。但两者是有区别
的。利用newInstance(),我们可在没有现成对象供“克隆”的情况下新建一个对象。就象上面的程序演示
的那样,当时没有Toy 对象,只有cy——即y 的Class 对象的一个句柄。利用它可以实现“虚拟构建器”。
换言之,我们表达:“尽管我不知道你的准确类型是什么,但请你无论如何都正确地创建自己。”在上述例
子中,cy 只是一个Class 句柄,编译期间并不知道进一步的类型信息。一旦新建了一个实例后,可以得到
Object 句柄。但那个句柄指向一个Toy 对象。当然,如果要将除Object 能够接收的其他任何消息发出去,
首先必须进行一些调查研究,再进行造型。除此以外,用newInstance()创建的类必须有一个默认构建器。
没有办法用newInstance()创建拥有非默认构建器的对象,所以在Java 1.0 中可能存在一些限制。然而,
Java 1.1 的“反射”API(下一节讨论)却允许我们动态地使用类里的任何构建器。
程序中的最后一个方法是printInfo(),它取得一个Class 句柄,通过getName()获得它的名字,并用
interface()调查它是不是一个接口。
该程序的输出如下:
Class name: FancyToy is interface? [false]
Class name: HasBatteries is interface? [true]
Class name: Waterproof is interface? [true]
Class name: ShootsThings is interface? [true]
Class name: Toy is interface? [false]
反射
RTTI 会帮助我们调查出对象的准确类型,但却有一个限制:类型必须是在编译期间已知的,否则就不能用RTTI 调查它,进而无法展开下一步的工作。换言之,编译器必须明确知道RTTI 要处理的所有类。
假若得到的是一个不在自己程序空间内的对象的句柄,假设我们从磁盘或者网络获得一系列字节,而且被告知那些字节代表一个类。由于编译器在编译代码时并不知道那个类的情况,所以怎样才能顺利地使用这个类呢?
“反射”提供了一种特殊的机制,可以侦测可用的方法,并产生方法名,在Java 1.1 中,Class
类(本章前面已有详细论述)得到了扩展,可以支持“反射”的概念。针对Field,Method 以及Constructor
类(每个都实现了Memberinterface——成员接口),它们都新增了一个库:java.lang.reflect。这些类型的对象都是JVM
在运行期创建的,用于代表未知类里对应的成员。这样便可用构建器创建新对象,用get()和set()方法读取和修改与Field
对象关联的字段,以及用invoke()方法调用与Method
对象关联的方法。此外,我们可调用方法getFields(),getMethods(),getConstructors(),分别返回用于表示字段、
方法以及构建器的对象数组(在联机文档中,还可找到与Class
类有关的更多的资料)。因此,匿名对象的类信息可在运行期被完整的揭露出来,而在编译期间不需要知道任何东西。大家要认识的很重要的一点是“反射”并没有
什么神奇的地方。通过“反射”同一个未知类型的对象打交道时,JVM 只是简单地检查那个对象,并调查它从属于哪个特定的类(就象以前的RTTI
那样)。但在这之后,在我们做其他任何事情之前,Class 对象必须载入。因此,用于那种特定类型的.class 文件必须能由JVM
调用(要么在本地机器内,要么可以通过网络取得)。所以RTTI 和“反射”之间唯一的区别就是对RTTI
来说,编译器会在编译期打开和检查.class 文件。换句话说,我们可以用“普通”方式调用一个对象的所有方法;但对“反射”来说,.class
文件在编译期间是不可使用的,而是由运行期环境打开和检查。
- //: ShowMethods.java
- // Using Java 1.1 reflection to show all the
- // methods of a class, even if the methods are
- // defined in the base class.
- import
java.lang.reflect.*;
- public
class
ShowMethods {
- static
final
String usage =
- "usage: /n"
+
- "ShowMethods qualified.class.name/n"
+
- "To show all methods in class or: /n"
+
- "ShowMethods qualified.class.name word/n"
+
- "To search for methods involving 'word'"
;
- public
static
void
main(String[] args) {
- if
(args.length <
1
) {
- System.out.println(usage);
- System.exit(0
);
- }
- try
{
- Class c = Class.forName(args[0
]);
- Method[] m = c.getMethods();
- Constructor[] ctor = c.getConstructors();
- if
(args.length ==
1
) {
- for
(
int
i =
0
; i < m.length; i++)
- System.out.println(m[i].toString());
- for
(
int
i =
0
; i < ctor.length; i++)
- System.out.println(ctor[i].toString());
- }
- else
{
- for
(
int
i =
0
; i < m.length; i++)
- if
(m[i].toString()
- .indexOf(args[1
])!= -
1
)
- System.out.println(m[i].toString());
- for
(
int
i =
0
; i < ctor.length; i++)
- if
(ctor[i].toString()
- .indexOf(args[1
])!= -
1
)
- System.out.println(ctor[i].toString());
- }
- } catch
(ClassNotFoundException e) {
- System.out.println("No such class: "
+ e);
- }
- }
- } ///
Class 方法getMethods()和getConstructors()可以分别返回Method 和Constructor 的一个数组。每个类都
提供了进一步的方法,可解析出它们所代表的方法的名字、参数以及返回值。但也可以象这样一样只使用
toString(),生成一个含有完整方法签名的字串。代码剩余的部分只是用于提取命令行信息,判断特定的签
名是否与我们的目标字串相符(使用indexOf()),并打印出结果。
这里便用到了“反射”技术,因为由Class.forName()产生的结果不能在编译期间获知,所以所有方法签名
信息都会在运行期间提取。若研究一下联机文档中关于“反射”(Reflection)的那部分文字,就会发现它
已提供了足够多的支持,可对一个编译期完全未知的对象进行实际的设置以及发出方法调用。同样地,这也
属于几乎完全不用我们操心的一个步骤——Java 自己会利用这种支持,所以程序设计环境能够控制Java
Beans——但它无论如何都是非常有趣的。
反射方法调用:
这样写(假设方法的参数是String[],如main(String[] args)):
Class yourClass = Class.forName( "YourClass ");//假设你要动态加载的类为YourClass
Class[] parameterTypes = new Class[1];//这里你要调用的方法只有一个参数
parameterTypes[0] = String[].class;//这个参数的类型是String型的/////应该是String[]
Method method = yourClass.getMethod( "main ", parameterTypes);//这里假设你的类为YourClass,而要调用的方法是main
Object[] args = new Object[1];//假设你要传入两个参数////应该是一个
String[] argments = new String[2];//假设你要传入两个参数
argments[0] = "OK ";
argments[1] = "NO ";
args[0] = argments;
method.invoke(yourClass.newInstance(), args);//调用方法