第 16章
反射
– 类别载入与检视
– 使用反射生成与操作对象简介 Class与类别载入
真正需要使用一个类别时才会加以加载
java.lang.Class对象代表了 Java应用程序在运行时所加载的类别或接口实例
可以透过 Object的 getClass()方法来取得每一个对象对应的 Class对象,或者是透过
"class"常量( Classliteral)
简介 Class与类别载入
String name = "caterpillar";
Class stringClass = name.getClass();
System.out.println("类别名称," +
stringClass.getName());
System.out.println("是否为接口," +
stringClass.isInterface());
System.out.println("是否为基本型态," +
stringClass.isPrimitive());
System.out.println("是否为数组对象," +
stringClass.isArray());
System.out.println("父类别名称," +
stringClass.getSuperclass().getName());
Class stringClass = String.class;
简介 Class与类别载入
所谓「真正需要」通常指的是要使用指定的类别生成对象
例如使用 Class.forName()加载类别,或是使用 ClassLoader的 loadClass()载入类别
public class TestClass {
static {
System.out.println("类别被载入 ");
}
}
TestClass test = null;
System.out.println("宣告 TestClass参考名称 ");
test = new TestClass();
System.out.println("生成 TestClass实例 ");
简介 Class与类别载入
Class的讯息是在编译时期就被加入至,class档案中
执行时期 JVM在使用某类别时,会先检查对应的 Class对象是否已经加载
如果没有加载,则会寻找对应的,class档案并载入简介 Class与类别载入
一个类别在 JVM中只会有一个 Class实例
每个类别的实例都会记得自己是由哪个
Class实例所生成
可使用 getClass()或,class来取得 Class实例简介 Class与类别载入
数组是一个对象,也有其对应的 Class实例
System.out.println(boolean.class);
System.out.println(void.class);
int[] iarr = new int[10];
System.out.println(iarr.getClass().toString());
double[] darr = new double[10];
System.out.println(darr.getClass().toString());
boolean
void
class [I
class [D
从 Class中获取信息
Class对象表示所加载的类别,取得 Class
对象之后,您就可以取得与类别相关联的信息
– 套件的对应型态是 java.lang.Package
– 建构方法的对应型态是
java.lang.reflect.Constructor
– 方法成员的对应型态是
java.lang.reflect.Method
– 数据成员的对应型态是 java.lang.reflect.Field
从 Class中获取信息
try {
Class c = Class.forName(args[0]);
Package p = c.getPackage();
System.out.println(p.getName());
}
catch(ArrayIndexOutOfBoundsException e) {
System.out.println("没有指定类别 ");
}
catch(ClassNotFoundException e) {
System.out.println("找不到指定类别 ");
}
从 Class中获取信息
Class c = Class.forName(args[0]);
//取得套件代表对象
Package p = c.getPackage();
System.out.printf("package %s;%n",p.getName());
//取得型态修饰,像是 class,interface
int m = c.getModifiers();
System.out.print(Modifier.toString(m) + " ");
//如果是接口
if(Modifier.isInterface(m)) {
System.out.print("interface ");
}
else {
System.out.print("class ");
}
System.out.println(c.getName() + " {");
从 Class中获取信息
//取得宣告的数据成员代表对象
Field[] fields = c.getDeclaredFields();
for(Field field,fields) {
//显示权限修饰,像是 public,protected,private
System.out.print("\t" +
Modifier.toString(field.getModifiers()));
//显示型态名称
System.out.print(" " +
field.getType().getName() + " ");
//显示数据成员名称
System.out.println(field.getName() + ";");
}
从 Class中获取信息
//取得宣告的建构方法代表对象
Constructor[] constructors =
c.getDeclaredConstructors();
for(Constructor constructor,constructors) {
//显示权限修饰,像是 public,protected,private
System.out.print("\t" +
Modifier.toString(
constructor.getModifiers()));
//显示建构方法名称
System.out.println(" " +
constructor.getName() + "();");
}
从 Class中获取信息
//取得宣告的方法成员代表对象
Method[] methods = c.getDeclaredMethods();
for(Method method,methods) {
//显示权限修饰,像是 public,protected,private
System.out.print("\t" +
Modifier.toString(
method.getModifiers()));
//显示返回值型态名称
System.out.print(" " +
method.getReturnType().getName() + " ");
//显示方法名称
System.out.println(method.getName() + "();");
}
简介类别加载器
Bootstrap Loader通常由 C撰写而成
Extended Loader是由 Java所撰写而成,实对应 sun.misc.Launcher$ExtClassLoader
( Launcher中的内部类别)
System Loader是由 Java撰写而成,实际对应于 sun.misc,Launcher$AppClassLoader
( Launcher中的内部类别)
简介类别加载器简介类别加载器
BootstrapLoader会搜寻系统参数
sun.boot.class.path中指定位置的类别
预设是 JRE所在目录的 classes下之,class档案,或 lib目录下,jar档案中(例如 rt.jar)的类别
System.getProperty("sun.boot.class.path")
显示 sun.boot.class.path中指定的路径简介类别加载器
Extended Loader
( sun.misc.Launcher$ExtClassLoader)
是由 Java撰写而成,会搜寻系统参数
java.ext.dirs中指定位置的类别
预设是 JRE目录下的 lib\ext\classes目录下的,class档案,或 lib\ext目录下的,jar档案中
(例如 rt.jar)的类别
System.getProperty("java.ext.dirs")陈述来显示 java.ext.dirs中指定的路径简介类别加载器
System Loader
( sun.misc.Launcher$AppClassLoader)
是由 Java撰写而成,会搜寻系统参数
java.class.path中指定位置的类别,也就是
Classpath所指定的路径
默认是目前工作路径下的,class档案
System.getProperty("java.class.path")陈述来显示 java.class.path中指定的路径简介类别加载器
ootstrapLoader会在 JVM启动之后产生,之后它会载 入 ExtendedLoader并将其 parent设为
Bootstrap Loader,然後 BootstrapLoader再载入
SystemLoader并将其 parent设定为
ExtClassLoader
每个类别加载器会先将加载类别的任务交由其
parent,如果 parent找不到,才由自己负责载入
载入类别时,会 以 Bootstrap Loader→Extended
Loader→SystemLoader 的顺序来寻找类别
– 都找不到,就会丢出 NoClassDefFoundError
简介类别加载器
类别加载器在 Java中是以
java.lang.ClassLoader型态存在
每一个类别被载入后,都会有一个 Class的实例来代表,而每个 Class的实例都会记得自己是由哪个 ClassLoader载入
可以由 Class的 getClassLoader()取得载入该类别的 ClassLoader,而从 ClassLoader
的 getParent()方法可以取得自己的 parent
简介类别加载器简介类别加载器
//建立 SomeClass实例
SomeClass some = new SomeClass();
//取得 SomeClass的 Class实例
Class c = some.getClass();
//取得 ClassLoader
ClassLoader loader = c.getClassLoader();
System.out.println(loader);
//取得父 ClassLoader
System.out.println(loader.getParent());
//再取得父 ClassLoader
System.out.println(loader.getParent().getParent());
简介类别加载器
取得 ClassLoader的实例之后,您可以使用它的 loadClass()方法来加载类别
使用 loadClass()方法加载类别时,不会执行静态区块
静态区块的执行会等到真正使用类别来建立实例时简介类别加载器
try {
System.out.println("载入 TestClass2");
ClassLoader loader = ForNameDemoV3.class.getClassLoader();
Class c = loader.loadClass("onlyfun.caterpillar.TestClass2");
System.out.println("使用 TestClass2宣告参考名称 ");
TestClass2 test = null;
System.out.println("使用 TestClass2建立对象 ");
test = new TestClass2();
}
catch(ClassNotFoundException e) {
System.out.println("找不到指定的类别 ");
}
使用自己的 ClassLoader
可以在使用 java启动程序时,使用以下的指令来指定 ExtClassLoader的搜寻路径
可以在使用 java启动程序时,使用 -
classpath或 -cp来指定 AppClassLoader的搜寻路径,也就是设 定 Classpath
java -Djava.ext.dirs=c:\workspace\ YourClass
java -classpath c:\workspace\ YourClass
使用自己的 ClassLoader
ExtClassLoader与 AppClassLoader在程序启动后会在虚拟机中存在一份
在程序运行过程中就无法再改变它的搜寻路径,如果在程序运行过程中
打算动态决定从其它的路径加载类别,就要产生新的类别加载器使用自己的 ClassLoader
可以使用 URLClassLoader来产生新的类别加载器
搜寻 SomeClass类别时,会一路往上委托至 BootstrapLoader先开始搜寻,接着是
ExtClassLoader,AppClassLoader,如果都找不到,才使用新建的 ClassLoader搜寻
URL url = new URL("file:/d:/workspace/");
ClassLoader urlClassLoader =
new URLClassLoader(new URL[] {url});
Class c = urlClassLoader.loadClass("SomeClass");
使用自己的 ClassLoader
每次寻找类别时都是委托 parent开始寻找
除非有人可以侵入您的计算机,置换掉标準 Java SEAPI与您自己安装的延伸套件,
否则是不可能藉由撰写自己的类别加载器来载入恶意类别使用自己的 ClassLoader
使用自己的 ClassLoader
由同一个 ClassLoader载入的类别档案,会只有一份 Class实例
如果同一个类别档案是由两个不同的 ClassLoader
载入,则会有两份不同的 Class实例
如果有两个不同的 ClassLoader搜寻同一个类别,
而在 parent的 AppClassLoader搜寻路径中就可以找到指定类别的话,则 Class实例就只会有一个
如果父 ClassLoader找不到,而是由各自的
ClassLoader搜寻到,则 Class的实例会有两份使用自己的 ClassLoader
//测试路径
String classPath = args[0];
//测试类别
String className = args[1];
URL url1 = new URL(classPath);
//建立 ClassLoader
ClassLoader loader1 =
new URLClassLoader(new URL[] {url1});
//加载指定类别
Class c1 = loader1.loadClass(className);
//显示类别描述
System.out.println(c1);
URL url2 = new URL(classPath);
ClassLoader loader2 =
new URLClassLoader(new URL[] {url2});
Class c2 = loader2.loadClass(className);
System.out.println(c2);
System.out.println("c1与 c1为同一实例? "
+ (c1 == c2));
生成物件
使用 Class的 newInstance()方法来实例化一个对象
Class c = Class.forName(args[0]);
List list = (List) c.newInstance();
for(int i = 0; i < 5; i++) {
list.add("element " + i);
}
for(Object o,list.toArray()) {
System.out.println(o);
}
生成物件
如果您要在动态加载及生成对象时指定对象的初始化自变量
– 要先指定参数型态
– 取得 Constructor物件
– 使用 Constructor的 newInstance()并指定参数的接受值生成物件
Class c = Class.forName(args[0]);
//指定参数型态
Class[] params = new Class[2];
//第一个参数是 String
params[0] = String.class;
//第二个参数是 int
params[1] = Integer.TYPE;
//取得对应参数列的建构方法
Constructor constructor =
c.getConstructor(params);
//指定自变量内容
Object[] argObjs = new Object[2];
argObjs[0] = "caterpillar";
argObjs[1] = new Integer(90);
//给定自变量并实例化
Object obj = constructor.newInstance(argObjs);
呼叫方法
方法的对象代表是 java.lang.reflect.Method
的实例
使用 invoke()方法来动态呼叫指定的方法呼叫方法
Class c = Class.forName(args[0]);
//使用无参数建构方法建立对象
Object targetObj = c.newInstance();
//设定参数型态
Class[] param1 = {String.class};
//根据参数型态取回方法对象
Method setNameMethod = c.getMethod("setName",param1);
//设定自变量值
Object[] argObjs1 = {"caterpillar"};
//给定自变量呼叫指定对象之方法
setNameMethod.invoke(targetObj,argObjs1);
Class[] param2 = {Integer.TYPE};
Method setScoreMethod =
c.getMethod("setScore",param2);
Object[] argObjs2 = {new Integer(90)};
setScoreMethod.invoke(targetObj,argObjs2);
//显示对象描述
System.out.println(targetObj);
呼叫方法
一个存取私有方法的例子
Method privateMethod =
c.getDeclaredMethod("somePrivateMethod",new Class[0]);
privateMethod.setAccessible(true);
privateMethod.invoke(targetObj,argObjs);
修改成员值
Class c = Class.forName(args[0]);
Object targetObj = c.newInstance();
Field testInt = c.getField("testInt");
testInt.setInt(targetObj,99);
Field testString = c.getField("testString");
testString.set(targetObj,"caterpillar");
System.out.println(targetObj);
Field privateField = c.getDeclaredField("privateField");
privateField.setAccessible(true);
privateField.setInt(targetObj,99);
再看数组对象
System.out.println("short数组类别," + sArr.getClass());
System.out.println("int数组类别," + iArr.getClass());
System.out.println("long数组类别," + lArr.getClass());
System.out.println("float数组类别," + fArr.getClass());
System.out.println("double数组类别," + dArr.getClass());
System.out.println("byte数组类别," + bArr.getClass());
System.out.println("boolean数组类别," + zArr.getClass());
System.out.println("String数组类别," + strArr.getClass());
short数组类别,class [S
int数组类别,class [I
long数组类别,class [J
float数组类别,class [F
double数组类别,class [D
byte数组类别,class [B
boolean数组类别,class [Z
String数组类别,class [Ljava.lang.String;
再看数组对象
使用反射机制动态生成数组
Class c = String.class;
Object objArr = Array.newInstance(c,5);
for(int i = 0; i < 5; i++) {
Array.set(objArr,i,i+"");
}
for(int i = 0; i < 5; i++) {
System.out.print(Array.get(objArr,i) + " ");
}
System.out.println();
String[] strs = (String[]) objArr;
for(String s,strs) {
System.out.print(s + " ");
}
再看数组对象
Class c = String.class;
//打算建立一个 3*4数组
int[] dim = new int[]{3,4};
Object objArr = Array.newInstance(c,dim);
for(int i = 0; i < 3; i++) {
Object row = Array.get(objArr,i);
for(int j = 0; j < 4; j++) {
Array.set(row,j,"" + (i+1)*(j+1));
}
}
for(int i = 0; i < 3; i++) {
Object row = Array.get(objArr,i);
for(int j = 0; j < 4; j++) {
System.out.print(Array.get(row,j) + " ");
}
System.out.println();
}
Proxy类别
java.lang.reflect.Proxy类别,可协助您实现动态代理功能