Java反射机制

一.Java反射概述

1.1 反射初步

既然有反射,那么我们先了解一下非反射,也就是正常创建对象的方式,即先要引入包,找到其中的类,然后通过new实例化,得到实例化对象。
反射就是反过来,根据实例化对象,使用getClass()方法,得到一个包.类的名称。一切都是反过来的,这就是反射。
那么反射到底是什么?
加载完类之后,在堆内存的方法区就产生了一个Class类型的对象。这个对象包含了完整的类的结构,我们可以通过这个对象就能看到类的结构。这个对象就像一面镜子能反射到类的结构,所以,我们形象的称之为反射。
通过一段代码来理解一下。
首先创建Person类

public class Person {

 private String name;
 public int age;

 @Override
 public String toString() {
     return "Person{" +
             "name='" + name + '\'' +
             ", age=" + age +
             '}';
 }

 public String getName() {
     return name;
 }

 public void setName(String name) {
     this.name = name;
 }

 public int getAge() {
     return age;
 }

 public void setAge(int age) {
     this.age = age;
 }

 public Person(String name, int age) {

     this.name = name;
     this.age = age;
 }

 private Person(String name) {
     this.name = name;
 }

 public Person() {
     System.out.println("Person()");
 }

 public void show(){
     System.out.println("你好,我是一个人");
 }

 private String showNation(String nation){
     System.out.println("我的国籍是:" + nation);
     return nation;
 }
}

反射之前,正常使用这个类,我们是怎么做的呢

public void test1() {

        // 1.创建Person类的对象
        Person p1 = new Person("Tom", 12);

        // 2.通过对象,调用其内部的属性、方法
        p1.age = 10;
        System.out.println(p1.toString());

        p1.show();

        //在Person类外部,不可以通过Person类的对象调用其内部私有结构。
        //比如:name、showNation()以及私有的构造器
 }

而现在通过反射,我们可以这样做

public void test2() throws Exception{
        Class clazz = Person.class;
        //1.通过反射,创建Person类的对象
        Constructor cons = clazz.getConstructor(String.class,int.class);
        Object obj = cons.newInstance("Tom", 12);
        Person p = (Person) obj;
        System.out.println(p.toString());
        //2.通过反射,调用对象指定的属性、方法
        //调用属性
        Field age = clazz.getDeclaredField("age");
        age.set(p,10);
        System.out.println(p.toString());

        //调用方法
        Method show = clazz.getDeclaredMethod("show");
        show.invoke(p);

        System.out.println("*******************************");

        //通过反射,可以调用Person类的私有结构的。比如:私有的构造器、方法、属性
        //调用私有的构造器
        Constructor cons1 = clazz.getDeclaredConstructor(String.class);
        cons1.setAccessible(true);
        Person p1 = (Person) cons1.newInstance("Jerry");
        System.out.println(p1);

        //调用私有的属性
        Field name = clazz.getDeclaredField("name");
        name.setAccessible(true);
        name.set(p1,"HanMeimei");
        System.out.println(p1);

        //调用私有的方法
        Method showNation = clazz.getDeclaredMethod("showNation", String.class);
        showNation.setAccessible(true);
        //相当于String nation   = p1.showNation("中国")
        String nation = (String) showNation.invoke(p1,"中国");
        System.out.println(nation);


    }

看出异同了吗?通过反射,拿到了Person的一个Class类型的对象,然后通过这个对象,就可以创建Person类的对象,调用Person类的属性,方法。更牛逼的是,还可以调用Person类的私有结构。
到这里,可能会有三个疑问,
1.既然有new了,反射会用在哪?
2.反射技术和面向对象的封装性矛盾吗?
3.Class是什么?
咱们一一解答
1. 编译的时候确定不了要new哪个类的对象,根据反射的特征动态性可以使用反射机制。举个例子,服务器运行了Java程序,前台传来/login,后台就创建login相关的对象,传/register,就创建register对象。这就是动态性,即不确定。
2. 不矛盾,封装性更多的是一种规范和建议,从程序员角度来说,封装性让代码更具可读性和设计感,一些方法和属性私有化说明我们不希望别人直接调用,又提供了一层其他方法来调用这些方法,提供对外接口。而反射是站在我们不知道对象的内部结构但是又不得已调用他的时候,比如spring框架,就利用了反射的知识,完全不矛盾,因为场景和意义不同。
3. 我们下节来看

1.2 Class是啥

Class是反射的源头,全称java.lang.Class
首先,我们要知道类的加载过程:
程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。
接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。
此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。换句话说,Class的实例就对应着一个运行时类。

从这个过程可以通俗理解Class就是描述类的类,类就是Class的对象,而实例化的对象又是类的对象。
加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
那么,如何通过不同方式获取Class的实例?

        //方式一:调用运行时类的属性:.class
        Class clazz1 = Person.class;
        System.out.println(clazz1);
        //方式二:通过运行时类的对象,调用getClass()
        Person p1 = new Person();
        Class clazz2 = p1.getClass();
        System.out.println(clazz2);

        //方式三(常用):调用Class的静态方法:forName(String classPath)
        Class clazz3 = Class.forName("com.wjs.java.Person");
        //也可以clazz3 = Class.forName("java.lang.String");
        System.out.println(clazz3);

        System.out.println(clazz1 == clazz2);//true
        System.out.println(clazz1 == clazz3);//true

        //方式四:使用类的加载器:ClassLoader  (了解)
        ClassLoader classLoader = ReflectionTest.class.getClassLoader();
        Class clazz4 = classLoader.loadClass("com.wjs.java.Person");
        System.out.println(clazz4);

        System.out.println(clazz1 == clazz4);


注意,万事万物皆对象,Class实例可以是Object,接口,int,void,数组等等,甚至还可以是Class的。

有了Class对象就可以创建运行时类的对象(*)

public void test1() throws IllegalAccessException, InstantiationException {

        Class<Person> clazz = Person.class;
        /*
        newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参 的构造器。

        要想此方法正常的创建运行时类的对象,要求:
        1.运行时类必须提供空参的构造器
        2.空参的构造器的访问权限得够。通常,设置为public。


        在javabean中要求提供一个public的空参构造器。原因:
        1.便于通过反射,创建运行时类的对象
        2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器

         */
        Person obj = clazz.newInstance();
        System.out.println(obj);

    }

1.3获取运行时类的完整结构

我们通过Class clazz = Person.class获得了运行时类,可以通过方法获得类的完整结构。Java提供以下方法是供使用。

         //获取属性结构
        //getFields():获取当前运行时类及其父类中声明为public访问权限的属性
        Field[] fields = clazz.getFields();
        for(Field f : fields){
            System.out.println(f);
        }
        System.out.println();

        //getDeclaredFields():获取当前运行时类中声明的所有属性。(不包含父类中声明的属性)
        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field f : declaredFields){
            System.out.println(f);
        }
        
        //获取方法结构
        //getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
        Method[] methods = clazz.getMethods();
        for(Method m : methods){
            System.out.println(m);
        }
       
        //getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for(Method m : declaredMethods){
            System.out.println(m);
        }
        
         //获取构造器
         //getConstructors():获取当前运行时类中声明为public的构造器
        Constructor[] constructors = clazz.getConstructors();
        for(Constructor c : constructors){
            System.out.println(c);
        }

        
        //getDeclaredConstructors():获取当前运行时类中声明的所有的构造器
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
        for(Constructor c : declaredConstructors){
            System.out.println(c);
        }
        
        //获得运行时类的父类
         Class superclass = clazz.getSuperclass();
         
         //获取运行时类实现的接口
         Class[] interfaces = clazz.getInterfaces();
         
         //获取运行时类所在的包
         Package pack = clazz.getPackage();
         
         //获取运行时类声明的注解
         Annotation[] annotations = clazz.getAnnotations();

1.4 调用运行时类的结构

如何操作运行时类中的指定属性

public void testField1() throws Exception {
        Class clazz = Person.class;

        //创建运行时类的对象
        Person p = (Person) clazz.newInstance();

        //1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
        Field name = clazz.getDeclaredField("name");

        //2.保证当前属性是可访问的
        name.setAccessible(true);
        //3.获取、设置指定对象的此属性值
        name.set(p,"Tom");

        System.out.println(name.get(p));
    }

如何操作运行时类中的指定的方法

public void testMethod() throws Exception {

        Class clazz = Person.class;

        //创建运行时类的对象
        Person p = (Person) clazz.newInstance();

        /*
        1.获取指定的某个方法
        getDeclaredMethod():参数1 :指明获取的方法的名称  参数2:指明获取的方法的形参列表
         */
        Method show = clazz.getDeclaredMethod("show", String.class);
        //2.保证当前方法是可访问的
        show.setAccessible(true);

        /*
        3. 调用方法的invoke():参数1:方法的调用者  参数2:给方法形参赋值的实参
        invoke()的返回值即为对应类中调用的方法的返回值。
         */
        //String nation = p.show("CHN");
        Object returnValue = show.invoke(p,"CHN"); 
        System.out.println(returnValue);

        System.out.println("*************如何调用静态方法*****************");

        // private static void showDesc()

        Method showDesc = clazz.getDeclaredMethod("showDesc");
        showDesc.setAccessible(true);
        //如果调用的运行时类中的方法没有返回值,则此invoke()返回null
        // Object returnVal = showDesc.invoke(null);
        Object returnVal = showDesc.invoke(Person.class);
        System.out.println(returnVal);//null

    }

如何调用运行时类中的指定的构造器

public void testConstructor() throws Exception {
        Class clazz = Person.class;

        //private Person(String name)
        /*
        1.获取指定的构造器
        getDeclaredConstructor():参数:指明构造器的参数列表
         */

        Constructor constructor = clazz.getDeclaredConstructor(String.class);

        //2.保证此构造器是可访问的
        constructor.setAccessible(true);

        //3.调用此构造器创建运行时类的对象
        Person per = (Person) constructor.newInstance("Tom");
        System.out.println(per);

    }

二. 反射的应用---动态代理模式

详情请见后续计划的Java设计模式大全之动态代理篇。

posted @ 2020-11-14 21:03  钻石喵  阅读(80)  评论(0)    收藏  举报