【java基础】java反射机制

1、前言

 JAVA反射机制是在运行状态中,对于任意一个类,都可以知道这个类的全部属性,方法,泛型类型;对于任意一个对象,都可以调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。java

 JAVA反射(放射)机制:“程序运行时,容许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。可是JAVA有着一个很是突出的动态相关机制:Reflection,用在Java身上指的是咱们能够于运行时加载、探知、使用编译期间彻底未知的classes。换句话说,Java程序能够加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。python

 以上两句话是借用百度百科的描述,java反射机制使得咱们能够在运行的时候获取这个类的几乎全部的信息,及调用方法,但不能改变类的结构,因此,java反射机制使得java有点像Ruby,Python这种动态语言,但不是动态语言。

2、java类初始化

在java中,一个类要想使用必须通过类的加载,链接和初始化这3个操做,正常状况下这个3个步骤都有jvm来完成。c++

  • 类的加载:类的加载是指定将类的class文件读入内存,并为之生成一个java.lang.Class对象,程序使用的每一个类都会在内存中为之生成一个java.lang.Class对象。程序员

    • 类的加载由类的加载器类完成,jvm向咱们提供了一系列的java类加载器(系统类加载器),其中最经常使用的类加载器有以下四种:
    1. Bootstrap ClassLoader:根类加载器,由jvm自身提供,不是ClassLoader的子类,不是由java实现的,使用本地语言c++实现的,咱们没法在程序中拿到它。
    2. Extension ClassLoader:扩展类加载器
    3. System ClassLoader(Application ClassLoader):系统类加载器
    4. other ClassLoader:其余类加载器,如URLClassLoader

    除了系统向咱们提供的类加载器外,咱们还能够自定义咱们本身的类加载器,自定义类加载器用的很少spring

  • 类的链接:当类的加载完成后,系统为之在内存中生成了Class对象,咱们知道内存中保存的都是二进制数据,全部类的链接时将类的二进制数据放到jre(java runtime environment )中,这个过程分为以下三个阶段:
    1. 验证:对java类进行安全性验证,包括类是否有正确的内部结构以及和其余类的协调
    2. 准备:为类的静态Field分配内存,并初始化
    3. 解析:将类的二进制数据中的符号替换成直接引用
  • 类的初始化:这个阶段,jvm负责对类进行初始化,对Field进行初始化赋值,对Field初始化有以下两种方式:
    1. 声明变量的时候为变量直接赋值
    2. 在静态代码块(static{})中对Field初始化赋值bootstrap

      ps:动态代码块({})中的代码块在这个阶段不会本执行,它是在生成类的实例的时候执行,至关于构造方法中的语句,用的很少api

     用图画出类初始化过程以下:数组

    这里写图片描述

 这里对上面的这个图作个简要说明,当要初始化的类有父类时,先初始化其直接父类,初始化其直接父类的时候又会判断其直接父类是否有父类,而后依次递归,直到要初始化的类的全部的父类都被初始化后在初始化最初要初始化的类安全

ps:有初始化语句指的的成员变量直接赋值,及静态代码块等等,当建立某个类的实例,调用了某个类的静态方法,访问某个类或接口的Field或者为之赋值,该类的子类被初始化等等都会致使类初始化ruby

3、java类加载器

 前面说了类的加载器有不少,系统为咱们提供了四种经常使用的类加载器,下面咱们看看这四种经常使用的类加载器各自的分工及如何协调工做的

  • Bootstrap Class Loader

运行以下代码:

/**
 * 根类加载器bootstrapClassLoader,不是ClassLoader的子类,是由JVM自身实现的
 * @author lt
 *
 */
public class BootstrapTest {

    public static void main(String[] args) {
        // 获取根类加载器所加载的全部url数组
        URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        //输出根类加载器加载的所有的url
        for(int i = 0;i<urLs.length;i++){
            System.out.println(urLs[i].toExternalForm());
        }
    }

}

控制台输出:

file:/D:/Program%20Files/Java/jre7/lib/resources.jar
file:/D:/Program%20Files/Java/jre7/lib/rt.jar
file:/D:/Program%20Files/Java/jre7/lib/sunrsasign.jar
file:/D:/Program%20Files/Java/jre7/lib/jsse.jar
file:/D:/Program%20Files/Java/jre7/lib/jce.jar
file:/D:/Program%20Files/Java/jre7/lib/charsets.jar
file:/D:/Program%20Files/Java/jre7/lib/jfr.jar
file:/D:/Program%20Files/Java/jre7/classes

咱们能够看到根类加载器所要加载的全部url

  • Extension ClassLoader(扩展类加载器):

     负责加载JRE的扩展目录(%JAVA_HOME%/jre/lib/ext和有java.ext.dirs系统属性指定的目录)中jar包的类

  • Application ClassLoader:

     系统类加载器,负责加载程序员本身写的类

  • 其余类加载器:

     加载本身写的类,如:URLClassLoader既能够加载本地的类也能够加载远程的类

类加载器之间的协调工做:

这里写图片描述

 说明:

  1. jvm加载一个类的时候,先由BootstrapClassLoader加载扩展类加载器,在由扩展类加载加载类或者加载系统类加载器,系统类加载器在加载类或者用户类加载器
  2. 由于除了根类加载器不是java写的外,其余类加载器都是用java写的,且都是ClassLoader的子类
  3. 加载器的层次结构向咱们展现了四种加载器之间的父子关系,这种父子关系不是指类之间的继承,而是类加载器实例之间的关系,上图左图中向咱们展现了用户类加载器持有系统类加载器的实例的引用,系统类加载器持有扩展类加载器的实例的引用,扩展类加载中拿不到根类加载器的引用,因此不知道扩展类加载器中是否有根类加载器的引用
自定义类加载器:

 自定义类加载器很是简单,前面说了java类加载器中除了根类加载器不是ClassLoader外,其余类加载器都是ClassLoader的子类,ClassLoader中有大量的protected的方法,毫无疑问,咱们能够经过继承ClassLoader来扩展类加载器,从而实现咱们本身的类加载器。

  1. loadClass(String name,boolean resolve):ClassLoader的入口, 使用指定的二进制名称来加载类。
  2. findClass(String name):经过指定的二进制名称查找类

一般咱们若是重写loadClass方法时使自定义ClassLoader变得困难,咱们还得本身实现该类的父类委托,缓冲机制两种策略,而loadClass的执行过程以下:

  1. findLoader(String)来检查该类是否已经加载,若是加载了则直接返回该类的Class对象,不然返回Null
  2. 调用父类加载器的loadClass()方法,若是父类加载器为Null,则使用根类加载器
  3. 调用findClass(String)方法查找该类

咱们能够看到前面两步是实现父类的一些逻辑,其实这里两步实现了父类委托和缓冲机制策略,而咱们只须要从新findClass(String)方法来实现咱们本身的逻辑便可,这样使自定义类加载器就简单多了

4、java反射机制

 前面说了类的加载器,下面开始真正学习java反射机制,java反射机制能够时咱们在运行时刻获取类的信息,如:类的成员变量类型,值,方法的信息及调用方法,获取泛型类型,获取注解等等。

java反射的相关api都在java.lang.reflect包下:学习api最好的方法仍是看官网文档,由于官方文档最权威。下面咱们经过一个测试类来学习反射的最经常使用的知识。

// 经过Class的静态方法forName加载类,该方法会初始化类
Class clazz = Class.forName("Common");
// 经过反射生成该类的实例,调用public的无参构造方法,反射生成类的实例,该类必须得有一个public的无参方法
Object newInstance = clazz.newInstance();
  • 获取类的构造方法
public static void testConstructor(Class clazz){
        System.out.println("-----------构造方法测试-----------");
        // 获取因此的构造方法
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
        for(int i=0;i<declaredConstructors.length;i++){
            int index = i+1; // 由于i从0开始
            String str = "第"+index+"个构造方法权限修饰符为";
            if(declaredConstructors[i].getModifiers() == 1){ // 这里的常量值能够查看api文档,博客下面也会帖子各类修饰符对应的数字
                str+="public";
            }
            if(declaredConstructors[i].getModifiers() == 2){ // 这里的常量值能够查看api文档,博客下面也会帖子各类修饰符对应的数字
                str+="private";
            }
            if(declaredConstructors[i].getModifiers() == 4){ // 这里的常量值能够查看api文档,博客下面也会帖子各类修饰符对应的数字
                str+="protected";
            }
            str+="名称为:"+declaredConstructors[i].getName();
            System.out.println(str);
        }
    }
  • 获取类方法的相关信息
/**
     * 这个方法经过反射获取了方法的相关信息,能够看到咱们获得了方法
     * 的所有信息
     * 这里字符串拼接比较多,因为是测试,因此就没用StringBuffer拼接
     * @param clazz
     */
    public static void testCommonMethodInfo(Class clazz){
        System.out.println("-----------普通方法测试-----------");
        Method[] methods = clazz.getMethods();
        System.out.println(methods.length);
        for(int i=0;i<methods.length;i++){
            int dexI = i+1;
            String str = "第"+dexI+"个方法形式为:";
            
            // 获取方法权限修饰符
            int modifiers = methods[i].getModifiers();
            if(modifiers == 1){ // 这里的常量值能够查看api文档,博客下面也会帖子各类修饰符对应的数字
                str+="public";
            }
            if(modifiers == 2){ // 这里的常量值能够查看api文档,博客下面也会帖子各类修饰符对应的数字
                str+="private";
            }
            if(modifiers == 4){ // 这里的常量值能够查看api文档,博客下面也会帖子各类修饰符对应的数字
                str+="protected";
            }
            
            String returnType = methods[i].getReturnType().getSimpleName(); // 获取方法返回类型
            str+=" "+returnType+" "; 
            
            String name = methods[i].getName(); // 获取方法名称
            str+= name;
            str+="(";
            
            // 获取方法参数的类型
            Class[] parameterTypes = methods[i].getParameterTypes();
            for(int j=0;i<parameterTypes.length;i++){
                str+=parameterTypes[j]+" ";
            }
            str+=")";
            System.out.println(str);
        }
    }
  • 调用类的方法
/**
     * 经过反射调用方法
     * @param clazz
     * @throws SecurityException 
     * @throws NoSuchMethodException 
     * @throws InvocationTargetException 
     * @throws IllegalArgumentException 
     * @throws IllegalAccessException 
     */
    public static void testMethodInvoke(Class clazz,Object newInstance) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
        System.out.println("-----------测试方法的调用-----------");
        /**
         * 因为咱们知道类中有这种类型的方法,因此咱们直接指定要获取的方法类型
         * 若是不知道咱们要调用的 方法的类型,咱们能够获取方法的参数类型等等全部信息
         * 而后匹配到咱们须要调用的方法,这里作个说明,咱们要调用的方法名是知道的
         */
        Method sum = clazz.getMethod("sum",int.class,int.class);
        System.out.println(sum);
        sum.invoke(newInstance, 1,1);
        Method setS = clazz.getMethod("setS",String.class);
        System.out.println(setS);
        setS.invoke(newInstance, "赋值啦");
    }
  • 测试类的属性Field
/**
     * 测试类的Field
     * @param clazz
     * @throws IllegalAccessException 
     * @throws IllegalArgumentException 
     */
    public static void testField(Class clazz,Object obj) throws IllegalArgumentException{
        System.out.println("-----------测试类的Field-----------");
        // 获取因此的Field,包括private修饰的,但不能直接获取和直接改变private修饰的值
        Field[] fields = clazz.getDeclaredFields();
        for(int i=0;i<fields.length;i++){
            int dexI = i+1;
            System.out.print("第"+dexI+"个name:"+fields[i].getName()+"  ");
            Class type = fields[i].getType();
            String typeStr = type.getName();
            try{
                if(typeStr.equals("int")){
                    System.out.println("value:"+fields[i].getInt(obj));
                }else if(typeStr.equals("java.lang.String")){
                    // 字符串形式经过get(Object obj)方法取得,若是该Field的权限为private,则 获取值的时候会报java.lang.IllegalAccessException
                    System.out.println("value:"+fields[i].get(obj));
                }
            }catch(IllegalAccessException ex){
                System.out.println("不能获取private修饰的属性值");
            }
        }
    }
  • 测试注解
/**
     * 测试注解(类上的注解),属性方法上的注解分别经过
     * Field对象和Method对象的getAnnotations()方法能够获得
     * 和这里是同样的
     * @param clazz
     */
    public static void testAnnotation(Class clazz){
        System.out.println("-----------测试注解-----------");
        Annotation[] annotations = clazz.getAnnotations();
        for(int i=0;i<annotations.length;i++){
            System.out.println(annotations[i]);
        }
    }
  • 测试泛型
/**
     * 泛型测试
     * @throws NoSuchFieldException
     */
    private static void genericTest() throws NoSuchFieldException {
        System.out.println("-----------获泛型测试-----------");
        Class<ReflectTest> clazz  = ReflectTest.class;
        Field f = clazz.getDeclaredField("map");
        // 直接使用getType()只对普通类型有效,并不能获得有泛型的类型
        
        Class<?> type = f.getType(); 
        // 下面的代码能够看到只输出了java.util.Map
        System.out.println(" map 的类型为:"+type);
        // 获取Field实例f的泛型类型
        Type genericType = f.getGenericType();
        // 若是genericType是ParameterizedType对象
        if(genericType instanceof ParameterizedType){
            ParameterizedType parameterizedType = (ParameterizedType) genericType;
            // 获取原始类型
            Type rawType = parameterizedType.getRawType();
            System.out.println("原始类型是:"+rawType);
            // 取得泛型类型的泛型参数
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            for(int i=0;i<actualTypeArguments.length;i++){
                System.out.println("第"+i+"个泛型类型为:"+actualTypeArguments[i]);
            }
        }else{
            System.out.println("泛型类型获取出错");
        }
    }
  • 获取泛型的实际类型
/**
     * 获取泛型T的实际类型
     */
    public static void getTType(){
        System.out.println("-----------获取泛型T的实际类型-----------");
        Dog dog = new DogImpl();
        // 注意,下面这种写法不能获取到T的实际类型,由于范式要在编译的时候就指定类型,在运行时候指定类型是获取不到真实类型的
//      Dog<Cat> dog = new Dog<Cat>();
    }
修饰符常量对应的值:

这里写图片描述

 因为注释说了很清楚了,因此这里就不过多介绍了,以上这些方法都是参考了api提供的一些方法本身写的一些测试,其实都只是一个简单方法的使用,但这些方法是反射中最基本最经常使用的方法,api方法不少,因此咱们学习api的时候最好时刻查询文档,查看文档是个好习惯。

 总结,java反射机制是java的一个很是重要的一个知识点,其实向spring,struts等等一些知名的框架没有一个没有使用反射,因此学好java反射是提高咱们技术必不可少的,咱们应该掌握java反射机制。这篇文章与其说是java反射机制的讲解,实际上是我本身学习java反射的学习总结,由于学习java也很久了,回顾总结一下所写的知识点,因此文章内容有说错的部分,欢迎指出。

测试代码:http://download.csdn.net/detail/ydxlt/9310157

相关文章
相关标签/搜索