第12章反射

反射加载类与垃圾回收

1 类的加载机制:

类的加载→连接→ 初始化
java类文件通过类加载器加载到内存中. 类加载器包括底层
[类加载器↔扩展类加载器↔应用类加载器]
1 JVM运行底层加载器,该加载器加载java核心API
2 扩展类加载器:加载搜索 JAVA_HOME/jre/lib/ext目录,加载扩展API
3 应用类加载器:加载搜索CLASSPATH目录,加载我们要运行的类
javaAPI中还提供了一个对应的Class对象有一个ClassLoader抽象类,可以通过继承ClassLoader基类来创建自定义的类加载器

2 反射:

使用反射查看类的信息
在java程序中获得Class对象的方式有3种
第一种 知道类的名称时:在编译期不知道类名,但在运行期可以获得该类名的时候使用Class类的forName()静态方法可以获得Class对象例如: Class c=Class.forName(“全限定类名”);
Class c=Class.forName(“java.lang.System”);这是第一种知道类的名称时.

第二种 在编译时知道类的名称:如果在编译期知道类名的情况,可以调用该类的class属性来获得该类对象的Class对象 例如:Class c = 类名.Class;
Class c= Student.Class 这是第二种在编译时知道类的名称来获取信息

第三种 知道对象的时候:如果一个类的实例对象以及得到,则调用该对象的getClass()方法返回该对象所属类对应的Class对象。getClass()方法是java.lang.Object类的方法之一,所以所有对象都可以调用该方法. 例如:Class c= 对象名.getClass(); 这是知道对象的时候用对象名称调用,比如说一个类的实例new扭了一个Student对象 Class c=Student.getClass();

3垃圾回收:

在java中,当对象被创建后,就会拥有一块内存。在程序运行时,JVM会陆续创很多对象,如果所有对象都永久占用内存,那么系统内存有可能很快被消耗,最好引发内存空间不足必须采用某种方法及时回收哪些无用对象的内存,以保证内存可以被重复利用。finalize()方法对象的finalize()方法可以帮助我们完成一些释放对象所占用的资源等收尾工作但是垃圾回收器是否会执行finalize方法以及何时执行该方法,都是不确定的.

1
2
3
4
5
6
7
publicclassA
{
public void finalize()
{
System.out.println("A finalized");
}
}

总结:

使用反射查看信息有3种方法

第1种知道类的名称时
第2种在编译时知道类的名称
第3种知道对象的时候
垃圾回收没有实例指向对象的时候就被回收 ###

反射的工作原理

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
Java的反射机制的实现要借助于4个类:class,Constructor,Field,Method;其中class代表的是类对 象,Constructor-类的构造器对象,Field-类的属性对象,Method-类的方法对象。通过这四个对象我们可以粗略的看到一个类的各个组 成部分。
Reflection 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性。
reflection的工作机制,如下简单的例子
Java代码

1
2
3
4
5
6
7
8
9
10
import java.lang.reflect.Method;  
public class ReflectTest {
public static void main(String args[]) throws ClassNotFoundException {
Class c = Class.forName("java.util.ArrayList");
Method[] m = c.getDeclaredMethods();
for (Method method : m) {
System.out.println(method.toString());
}
}
}

这个程序使用 Class.forName 载入指定的类,然后调用 getDeclaredMethods 来获取这个类中定义了的方法列表。java.lang.reflect.Methods 是用来描述某个类中单个方法的一个类。

Java类反射中的主要方法:

构造函数:

Constructor getConstructor(Class[] params) – 获得使用特殊的参数类型的公共构造函数,
Constructor[] getConstructors() – 获得类的所有公共构造函数
Constructor getDeclaredConstructor(Class[] params) – 获得使用特定参数类型的构造函数(与接入级别无关)
Constructor[] getDeclaredConstructors() – 获得类的所有构造函数(与接入级别无关)

字段:

Field getField(String name) – 获得命名的公共字段
Field[] getFields() – 获得类的所有公共字段
Field getDeclaredField(String name) – 获得类声明的命名的字段
Field[] getDeclaredFields() – 获得类声明的所有字段

方法:

Method getMethod(String name, Class[] params) – 使用特定的参数类型,获得命名的公共方法
Method[] getMethods() – 获得类的所有公共方法
Method getDeclaredMethod(String name, Class[] params) – 使用特写的参数类型,获得类声明的命名的方法
Method[] getDeclaredMethods() – 获得类声明的所有方法
JDK1.5以后关于注解的:
Annotation[] getAnnotations() – 获得所有公共注解
Annotation[] getDeclaredAnnotations() – 获得声明的所有注解
AnnotationType getAnnotationType() – 获得注解的类型
用于 reflection 的类,如 Method,可以在 java.lang.relfect 包中找到。使用这些类的时候必须要遵循三个步骤:

获得你想操作的类的 java.lang.Class 对象。
调用诸如 getDeclaredMethods 的方法,以取得该类中定义的所有方法的列表。
使用 reflection API 来操作这些信息。

反射的安全性:

在处理反射时安全性是一个较复杂的问题。反射经常由框架型代码使用,由于这一点,我们可能希望框架能够全面接入代码,无需考虑常规的接入限制。但是,在其它情况下,不受控制的接入会带来严重的安全性风险,例如当代码在不值得信任的代码共享的环境中运行时。由于这些互相矛盾的需求,Java编程语言定义一种多级别方法来处理反射的安全性。基本模式是对反射实施与应用于源代码接入相同的限制:
从任意位置到类公共组件的接入
类自身外部无任何到私有组件的接入
受保护和打包(缺省接入)组件的有限接入
不过至少有些时候,围绕这些限制还有一种简单的方法。我们可以在我们所写的类中,扩展一个普通的基本类 java.lang.reflect.AccessibleObject 类。这个类定义了一种setAccessible方法,使我们能够启动或关闭对这些类中其中一个类的实例的接入检测。唯一的问题在于如果使用了安全性管理 器,它将检测正在关闭接入检测的代码是否许可了这样做。如果未许可,安全性管理器抛出一个例外。
下面是一段程序:
Java代码

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String args[]) throws ClassNotFoundException {  
try {
String test = "";
Class clas = test.getClass();
Field field = clas.getDeclaredField("count");
// field.setAccessible(true);
System.out.println(field.get("test"));
} catch(Exception e) {
System.out.println("Exception");
e.printStackTrace(System.out);
}
}

如果我们编译这一程序时,不使用任何特定参数直接运行,它将在field .get(“test”)调用中抛出一个IllegalAccessException异常。如果我们不注释 field.setAccessible(true)代码行,那么重新编译并重新运行该代码,它将编译成功。最后,如果我们在命令行添加了JVM参数 -Djava.security.manager以实现安全性管理器,它仍然将不能通过编译,除非我们定义了ReflectSecurity类的许可权 限。

反射的性能:

写了个简单的测试程序,如下:
Java代码

import java.lang.reflect.Method;  

public class ReflectTest {  
    String name;  
    int age;  
    public static void main(String args[])  throws Exception {  

        Object o2 = javaCreate();  

        ReflectTest my = new ReflectTest();  

        long a = System.currentTimeMillis();  
        long b = System.currentTimeMillis();  
        a = System.currentTimeMillis();  
        for (int i = 0; i < 5000; i++) {  
            javaCreate();  
        }  
        b = System.currentTimeMillis();  
        System.out.println("javaCreate : " + (b - a));  

        a = System.currentTimeMillis();  
        for (int i = 0; i < 5000; i++) {  
            manualCreate();  
        }  
        b = System.currentTimeMillis();  
        System.out.println("manualCreate : " + (b - a));  

        a = System.currentTimeMillis();  
        for (int i = 0; i < 5000; i++) {  
            javaSet(o2);  
        }  
        b = System.currentTimeMillis();  
        System.out.println("javaSet : " + (b - a));  

        a = System.currentTimeMillis();  
        for (int i = 0; i < 5000; i++) {  
            manualSet(my);  
        }  
        b = System.currentTimeMillis();  
        System.out.println("manualSet : " + (b - a));  

        a = System.currentTimeMillis();  
        for (int i = 0; i < 5000; i++) {  
            javaGet(o2);  
        }  
        b = System.currentTimeMillis();  
        System.out.println("javaGet : " + (b - a));  

        a = System.currentTimeMillis();  
        for (int i = 0; i < 5000; i++) {  
             manualGet(my);  
        }  
        b = System.currentTimeMillis();  
        System.out.println("manualGet : " + (b - a));  
    }  

    // ===============下面是 java自身的直接反射的方法  
    public static Object javaCreate()  throws Exception {  
        Object ob = ReflectTest.class.newInstance();  
        return ob;  
    }  
    public static void javaSet(Object ob)  throws Exception {  
        Method m = ReflectTest.class.getDeclaredMethod("setName",  
                new Class[] { String.class });  
        m.invoke(ob, new Object[] { "旺旺旺" });  
    }  
    public static void javaGet(Object ob) throws Exception {  
        Method m = ReflectTest.class.getDeclaredMethod("getName", new Class[0]);  
        m.invoke(ob, new Object[0]);  
    }  
    // ===============下面是 手动的创建对象  
    public static ReflectTest manualCreate() {  
        ReflectTest my = new ReflectTest();  
        return my;  
    }  

    public static void manualSet(ReflectTest my) {  
        my.setName("旺旺旺");  
    }  
    public static void manualGet(ReflectTest my) {  
        my.getName();  
    }  
    public int getAge() {  
        return age;  
    }  
    public void setAge(int age) {  
        this.age = age;  
    }  
    public String getName() {  
        return name;  
    }  
    public void setName(String name) {  
        this.name = name;  
    }  
}

运行结果: 写道
javaCreate : 22
manualCreate : 0
javaSet : 40
manualSet : 0
javaGet : 9
manualGet : 0

由此可见,反射对性能的影响。

总结:

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