JAVA反射学习之——基本学习

来源:互联网 发布:香港大学mba 知乎 编辑:程序博客网 时间:2024/06/05 18:36
JAVA反射学习之——基本学习

    反射通常用来做下在的事情:
    1. 动态加载类,获得类的信息(属性,方法,构造器)。
    2. 动态的构造对像。
    3. 动态的调用类和对像的方法。
    4. 动态的调用和处理属性(这于的处理是指修改)。
    5. 处理注解。

    首先,来看看什么是类的加载?
    程序在首次使用某个类时,如果该类还未被加载到内存中,JVM就会通过加载,连接,初始化三个步骤来对该类进行初始化,初始化的结果就是把类编译后的class文件加载到内存中,并为之创建一个java.lang.class对像,没错!是为class文件创建一个对像。关于JVM的一些特性后面再研究,今天先知道怎么用吧。

    一、获得Class对像的三种方式
    上面说了,class文件加载到内存,并为之创建一个对像,那怎么得到这个对像呢?
    方法一:通过Object超类的getClass方法获得Class对像
    方法二:调用类的class属性来获取该类对应的Class对像
     但上面两种方法都有它们自身的局限性,就是预先得知道了是哪个类,话说回来,你都知道了是哪个类了,为啥还要费事的得到类的class对像,然后再去操作它的属性和方法呢?    
 public static void main(String[] args)    {    Persion p1 = new Persion();     Persion p2 = new Persion();          //第一种方法得到类的Class文件对像    Class c1 = p1.getClass();    Class c2 = p2.getClass();        //第二种方法得到类的Class文件对像    Class c3 = Persion.class;        //通过打印结果表明,同一个类只有一个Class文件对像    //实际上是这样的,只要类被加载过一次,它就不会再被重新加载了    //所以事实上,c2和c3还是c1所指向的那个对像    System.out.println(p1 == p2); //false    System.out.println(c1 == c2); //true    System.out.println(c1 == c3); //true    }
   上面的两种方法我觉得知道就行了,并没有什么实用性吧,因为用到反射的地方,通常是为了使代码具有通用性,而不是针对某一个已知的特定类。

    方法三、使用Class类的forName(String clazzName)方法来动态加载类。
    简单的用法测试如下:
//第三种方法    Class c4 = Class.forName("反射.Persion");        //打印信息表明c4=c3    System.out.println(c4 == c3);
    值得说明的是,clazzName是一个带包名的完整的类名,不然运行会报错!

    查询JDK文档的Class类,具有如下描述:

    Class 类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。
    Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的”。

    JDK文档有两句简单的代码示例,通过以下代码简单测试
public static void printClassName(Object obj) {//通过反射获得类的全名(带包名)        System.out.println( obj.getClass().getName());                //获得包名        System.out.println( obj.getClass().getPackage());                //通过反射获得类的包内名称        System.out.println( obj.getClass().getSimpleName());    }    public static void main(String[] args)    {    printClassName(new Persion());    }
    上面Class类的三个方法都非常有用的。从JDK文档摘录如下:
    1. String getName() 
        以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。 
    2. Package getPackage()     获取此类的包。 
    3. String getSimpleName()    返回源代码中给出的底层类的简称。

    二、从Class中获取信息
    上面已经讲明了怎么来获得类的Class对像,那获得了Class对像用来干嘛呢?
    1. 通过Class对像获取构造方法并创建对像
    构造方法分有参和无参之分,又有public和private等修饰,所以就对应有好几个方法。    
//1.获取类的Class对像    Class clazz = Class.forName("反射.Persion");    //2.获取类的public无参构造方法Constructor cons = clazz.getConstructor();//3.通过类的无参构造方法返回实例Object obj = cons.newInstance();
    最基本的方法和步骤就是上面所写,getConstructor是获得无参构造方法,而且,必须是public的!
    在实际当中,用的最多的,其实是下面两个:
//获得所有的public构造方法    Constructor[] cons1 = clazz.getConstructors();    //获得所有的构造方法(不管有参无参,public还是private等 )    Constructor[] cons2 = clazz.getDeclaredConstructors();
    然而,上面三个获取构造方法的声明中都是指定了变参列表的,所以我们在获取的时候,可以指定参数列表类型,从而获得得与之相匹配的构造方法来创建对像。    
    典型代码如下所示:
Constructor cons = clazz.getConstructor(String.class,int.class,String.class);Object obj = cons.newInstance("刘德华",50,"香港");
    需要注意的是,如果想使用私有的构造,那就要用getDecleardConstructor方法了。
     Constructor类有一个方法是getName(),以字符串的形式返回获取构造方法的名称,这样的话,在一些工具类的编写中,不用知道具体的是什么类而编写个有通用性的代码,但要注意的是,还要获得构造方法的参数类型!下面的一个方法就是用于此作用的,后面学习到再进行研究。
    Class<?>[] getParameterTypes() 
    按照声明顺序返回一组 Class 对象,这些对象表示此 Constructor 对象所表示构造方法的形参类型。

    2. 获得成员属性
    属性也称字段,同样的,有getField获得一个成员属性,有getFields获得所有的public型成员属性,有getDeclaredFields获得所有的成员属性。而Field的getName方法是获得成员属性的类中的名子。
//获取所有的成员属性    Field[] fs = clazz.getDeclaredFields();        //打印成员属性的类中的名子    for(Field f: fs){    System.out.println(f.getName());    }
    那怎么获取和修改特定的属性呢?
//1.获取类的Class对像    Class clazz = Class.forName("反射.Persion");    //2.创建类的对像    Object obj = clazz.newInstance();        //3.获取特定的字段    Field name = clazz.getDeclaredField("name");    //4在操作字段前,首先要获得访问权限    name.setAccessible(true);    //5.使用set方法给特定的字段赋值    name.set(obj, "刘德华");    //6.同理肯定有get方法获得某个对像的字段值    System.out.println(name.get(obj));

    3. 获得成员方法并使用
    有了上面获取成员属性和构造器的知识,再看获取成员方法并使用的方法就so easy了。一个例子学会最基本的应用:    
//1.获取类的Class对像    Class clazz = Class.forName("反射.Persion");    //2.创建类的对像    Object obj = clazz.newInstance();        //3.获取所有的方法,并打印出方法名    Method[] ms = clazz.getDeclaredMethods();    for(Method m: ms){    System.out.println(m.getName());    }        //4.获取无参方法function1,并调用它    Method function1 = clazz.getDeclaredMethod("function1");    function1.invoke(obj);        //5.获取带参方法function2    Method function2= clazz.getDeclaredMethod("function2", String.class);    function2.invoke(obj, "Hello World"); 
    
     好了,上面就是反射的基本用法了,仅仅是使用而已,初步了解了反射能干什么。
    仅仅提供类名,就可以获得创建类的对像,获得类的属性和方法,通过反射创建的类,可以修改它的属性,也可以调用它的成员方法。反射的功能可谓强大。
    
    那既然JAVA语言提供了反射特性,那JVM本身是不是也用了反射呢?一个类不管有多少个对像,都只有一个Class文件,JVM本身是否也在用上面所述的那些方法去处理JAVA程序?
    Persion p1 = new Persion();
    Persion p2 = new Persion();
    上面第一行代码就把Persion类加载到内存中了,为Persion类的class文件创建了Class对像,那其实第二句的代码在编译或执行的时候,JVM只是调用了Class类的newInstance()方法来创建另一个对像而已,而不管它是Persion还是其它什么类,我是一个通用性的方法,正是在此原理的基础上,其它的框架也才用反射搭建起来?这是否就是反射的核心和本质?
    
   
0 0
原创粉丝点击