反射(reflect)

目录

反射(reflect)

Java中的反射相关知识点

反射的基本操作步骤:

获取一个Class对象的反射API

探究一个Class对象的反射API

总结一下

使用探究到的信息的反射API


反射(reflect)

反射:在运行时探究和使用编译时未知的类

很明显在这句话中有两个时:运行时、编译时。根据我们的开发流程,都是先书写一个类,然后在代码里面产生它的对象,调用它的方法或属性,然后编译!编译结束以后,再运行,就能看到相应的效果---所以这是一个先编译后运行的流程。

但是反射这句话把整个过程给颠倒了,我们在是运行起来以后,才知道我们要产生哪个类的对象,调用它的哪个方法或属性。

举个例子,比如:IDEA这个软件就是用Java写的,它在运行起来以后可以探究到我们自己在它运行之后才编译好的Student类里面有哪些属性和行为,然后让我们通过联想的方式可以看到。

这就是Java语言提供的动态性效果。这里的动态性指的是“运行”起来以后,对应的“静态性”不是static而是编译后运行前。

编程语言分为:动态语言和静态语言。动态语言可以在程序运行起来之后,给它的类增加或修改或减少属性和行为。当然,Java作为静态语言做不到。 Java虽然不是动态语言,但是它具有一定的动态性。这个动态性就体现在“反射”上,虽然它没有办法在运行期给类增加/修改/删除成员内容,但是它可以探究和使用,而这就是反射提供的效果。

Java中的反射相关知识点

Java的工作流程:

1、先编写Java源文件;

2、然后编译Java原文件为class文件;

3、运行,而运行又分为3步:

3-1、加载

3-2、校验

3-3、解释执行

“反射”在这几个过程中和“加载”的关系很密切,所以我们要再深入“加载”来聊一聊。

1、加载谁? 2、谁加载? 3、加载成什么?

第一个问题:加载的是class文件。细节在于:一个Java类就会编译成一篇class文件,那么加载class文件就是在加载这个类,所以这个过程也叫做“类加载”;

第二个问题:在JVM当中,提供了一种叫做“类加载器”的东西,来完成加载这个动作。“类加载器”也叫做ClassLoader,它是JDK当中已经写好了的,提供给JVM用来做加载动作的。 Java在这里还专门设计了一个叫做“双亲委托模型”的东西。Java的类加载提供了三种加载器,如果再加上我们自定义的,那就是4种:

        BootstrapClassLoader、ExtClassLoader、ApplicationClassLoad、最后自定义的。

在加载的过程中: BootstrapClassLoader负责加载的是JDK中的那些最基本常用的类; ExtClassLoader负责加载的是jre/lib/ext这个包里面的常用类; ApplicationClassLoader是加载我们在应用层面自己写的类。

加载的方式是:向上询问,向下负责。

第三个问题:class文件是一篇字节码文件,那么到了内存当中,它是什么形式呢?它被加载成了一个对象。这个对象的类型是Class类型的,所以也被叫做Class对象。

Class对象是专门用来装一个类的内容信息的。一个类只需要一个Class对象。所以,在这句代码中其实产生了两个对象:

Student stu = new Student();

1、new出来的,交给stu去指向的,是Student的“实例对象”;表示一个学生对象,里面是该学生的数据和它可以执行的行为;

2、但是实例对象是运行期产生的,而在它之前需要先加载Student这个类,那么就会产生一个Class对象,在它当中装的是Student类的内容信息,包括:这个类的名字、父类是谁、接口有哪些、放在哪个包、有什么属性、构造、行为.....所以这个Class对象又被叫做“类模版对象”

3、类模版对象和实例对象的关系

3-1、必须先有模版对象,然后才能有实例对象;

3-2、模版对象只需要1个,实例对象可以根据要求产生无数个。

3-3、理论上,模版对象是JVM使用的,用来记载这个类的信息;而实例对象是我们的应用程序用的,用来装载数据和具体调用方法完成功能实现的。

反射在做什么事情呢?反射其实就是通过获取这个类的Class对象,我们就能够知道这个类的信息(探究),拿到这些信息以后,比如:拿到它的构造方法的信息,我们就可以产生实例对象了;拿到它的属性信息,我们就可以给属性进行赋值取值了;这就是所谓的“使用”。当然前提条件是获取它的Class对象。

反射的基本操作步骤:

  1. 获取一个Class对象

  2. 通过Class对象完成一个类的信息的探究。主要是探究3个东西:构造、属性和行为

  3. 探究到以后,就可以使用这些信息了。探究到构造就可以产生实例对象;探究到属性就可以对属性赋值取值;探究到方法就可以调用。

获取一个Class对象的反射API

方式1、类型名.class

注意 : 所有的类型(包括void)都有Class对象; 基本数据类型可以通过自己.class,也能通过自己的包装类.TYPE 获取到该基本类型的Class对象; 不管用哪种方式(包括后面的方式二和方式三)获取Class对象,同一类型的只有一个Class对象。

示例:

        //1、可以根据类型名的标识符获取Class对象
        //   所有的Java类型都可以通过这种方式获取它的Class对象
        Class stuClass = StudentBean.class;//自定义的Java类的Class对象
        Class strClass = String.class;
        Class interClass = Serializable.class;//接口的Class对象
        Class intArrayClass = int[].class;
        Class stuArrayClass = StudentBean[].class;
        Class intClass = int.class;//基本数据类型的Class对象
        Class intClass2 = Integer.TYPE;//也是基本数据类型int的Class对象
        /*
            在JDK5之前,由于Java要体现基本数据类型只能装数据,而不能有任何"."
            出来的内容。所以,在语法设计的时候不允许用"int"点出任何内容。那么要
            获取int的Class的对象,就只能通过int对应的包装类来实现。
            但是包装类.class得到的是包装类的Class对象;所以又给每个包装类设计
            了一个TYPE,用于专门获得对应的基本数据类型的Class对象。
            所以:Integer.class和Integer.TYPE得到的是不同的Class对象。
         */
        //System.out.println(intClass2 == intClass);//结果为false
        Class voidClass = void.class;//甚至连void也有Class对象。

方式2、实例对象.getClass()

注意 :getClass()来自于Object,所以只有Objet的子类才可以通过这种方式获取;接口、基本数据类型、void都不能通过这种方式获取它们的Class对象。

示例:

        //2、通过实例对象的getClass()方法获取到Class对象
       
        StudentBean stu = new StudentBean("zhang3",18);//StudentBean的实例对象
        Class stuClass1 = stu.getClass();
        Class stuClass = StudentBean.class
        // System.out.println(stuClass == stuClass1);//结果为true
        Class strClass1 = "hello".getClass();
        Class strClass2 = "world".getClass();
        int[] intArray = {1,3,5,7,9};
        Class intArrayClass1 = intArray.getClass();

方式3、Class.forName(类型的字符串名称)

注意 :这种方式是需要做异常处理的,因为有可能这个字符串对应的类型找不到,所以forName方法抛出了一个编译时异常ClassNotFoundException。 这种方式只能获取类、接口、数组的Class对象,不支持基本数据类型和void。 其中数组的字符串类名有古怪:

Student[] --> "[Lcom.lovo.bean.Student"

Serializable[] --> "[Ljava.io.Serializable"

byte[] "[B"

short[] "[S"

int[] "[I"

long[] "[J" -- 特殊

float[] "[F"

double[] "[D"

char[] "[C"

boolean[] "[Z" -- 特殊

示例:

        //3、通过字符串形式的类型名获取Class对象
        try {
            Class stuClass3 = Class.forName("com.project.bean.StudentBean");
            Class strClass4 = Class.forName("java.lang.String");
            Class interClass2 = Class.forName("java.io.Serializable");
            Class intArrayClass2 = Class.forName("[I");
            Class intClass1 = Class.forName("void");
            int[] intArray = {1,2,3,4,5};
            Class intArrayClass1 = intArray.getClass;
            System.out.println(intArrayClass2 == intArrayClass1);//结果为true
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

最后,我们需要对比一下这三种方式: 方式1 是可以获取所有的Java类型; 方式2 只能获取Object的子类类型; 方式3 可以获取所有的引用数据类型。

除了方式3,其他两种方式都没有“动态性”。

示例:

        String str = new Scanner(System.in).next();
        try {
            /*
                Class.forName在编译的时候并不知道要获取谁的Class对象;
                JVM也不知道要创建谁的Class对象。
                而是运行起来以后,根据传入的str的值,才能确定要加载谁,
                然后把谁的Class对象交给c。
                这才是有动态性的反射代码。
             */
            Class c = Class.forName(str);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

探究一个Class对象的反射API

探究类的基本信息:


        String className = stuClass.getName();//得到类的全名--类的限定名
        System.out.println(className);
        className = stuClass.getSimpleName();//得到类的简单名
        System.out.println(className);
        Class superClass = stuClass.getSuperclass();//得到父类的Class对象
        String superName = superClass.getName();
        System.out.println("父类名:" + superName);
        Class[] interClasses = stuClass.getInterfaces();//得到这个类实现的接口的Class对象
        System.out.println("接口名:");
        for(Class interC : interClasses){
            System.out.println(interC.getName());
        }
        Package aPackage = stuClass.getPackage();//得到类所在的包信息
        System.out.println("所在包:" + aPackage.getName());

探究构造方法

getConstructors() -- 获取所有的公共构造

getDeclaredConstructors() -- 获取所有声明的构造

这两个方法都是返回的Constructor[]。

getConstructor(参数的class对象) -- 获取指定的公共构造 getDeclaredConstructor(参数的class对象) -- 获取指定的声明构造

示例:


        Constructor[] allPublicCons = stuClass.getConstructors();//获取所有的公共构造
        Constructor[] allCons = stuClass.getDeclaredConstructors();//获取所有的构造
        Constructor publicCon = stuClass.getConstructor(String.class,int.class);//获取指定的某一    个公共构造
        Constructor theCon = stuClass.getDeclaredConstructor(String.class);//获取某个指定的声明构造与访问修饰符无关

        for(Constructor con : allCons){
            String conName = con.getName();//构造方法的名字是"类的限定名"
            int modNum = con.getModifiers();//构造方法的修饰符
            String modStr = Modifier.toString(modNum);
            Parameter[] params = con.getParameters();//构造方法的参数
            String strParams = "";
            for(int i = 0; i < params.length; i++){
                String parmStr = params[i].getType().getName();//得到参数的类型
                String parmName = params[i].getName();
                strParams += parmStr + " " + parmName;
                if(i != params.length - 1){
                    strParams +=",";
                }
            }
            System.out.println(modStr + " " + conName + "(" + strParams + ")");
        }

探究属性

getFields() -- 获取所有的公共属性

getDeclaredFields() -- 获取所有声明的属性

getField(属性名) -- 获取某个指定的公共属性

getDeclaredField(属性名) -- 获取某个指定的申明属性

示例:

        Field[] allPublicFields = stuClass.getFields();//获取所有的公共属性
        Field[] allFields = stuClass.getDeclaredFields();//获取所有的申明属性
        Field publicF = stuClass.getField("id");//获取指定的公共属性
        Field theF = stuClass.getDeclaredField("CLASSNAME");//获取某一个指定的申明属性

探究方法

getMethods() -- 获取所有的公共方法

getDeclaredMethods() -- 获取所有声明的方法

getMethod(方法名,参数的class对象) -- 获取某个指定的公共方法

getDeclaredMethod(方法名,参数的class对象) -- 获取某个指定的声明方法

示例:

        Method theM = stuClass.getDeclaredMethod("setName",String.class);

总结一下

这里的探究告诉了我们,在一个类的Class对象里面封装好了这个类的所有信息(只要你在类的代码里面声明的东西,都可以获取到)。 当然对我们最有意义的,最常用的,就是获取构造、属性和方法的那3组12个get。不要死记,其实就是4个单词和单复数的区别。

使用探究到的信息的反射API

探究到构造,就要产生实例对象

Constructor对象有一个叫做newInstance的方法,通过这个方法可以产生实例对象。

注意

1、获取到的Constructor对象的参数要和newInstance方法中传递的参数匹配!!!--- 这个其实就是形参和实参的匹配关系。

2、由于我们通过getDeclaredConstructor是可以找到这个类所有的构造方法,但是调用newInstance的时候还是要看该构造的访问修饰符。如果调用处与定义的时候规定的访问权限不匹配,仍然不能产生对象成功。会报:IllegalAccessException。

示例:

//先将StudentBean中的公共无参构造访问修饰符设置为private
Class stuClass = StudentBean.class;
//Constructor stuCon1 = stuClass.getDeclaredConstructor();//获取私有无参构造
//StudentBean stu1 = (StudentBean) stuCon1.newInstance();//此处调用这个构造,必然会报异常

Constructor stuCon2 = stuClass.getConstructor(String.class, int.class);
StudentBean stu2 = (StudentBean) stuCon2.newInstance("zhang3",28);//1、尊重访问修饰符
                                                                  //2、要与获取到的构造匹配参数

探究到属性,就要给这个属性赋值取值

Field对象身上有一个get()/set()的方法,可以给这个属性进行取值/赋值。当然仍然有前提条件,要遵守这个属性定义的访问修饰符。

示例:

        //普通属性
        Field f = stuClass.getField("id");
        int stuID = (int)f.get(stu2);//取值
        f.set(stu2,18);//赋值
        //静态属性
        Field f2 = stuClass.getField("CLASSNAME");
        f2.set(null,"公共一班");

探究到方法,就要调用这个方法

Method对象身上有一个invoke()的方法,可以调用这个method。当然仍然有前提条件,要遵守这个方法定义的访问修饰符。

示例:

        Method m = stuClass.getMethod("setAge", int.class);
        m.invoke(stu2,23);

        Method m2 = stuClass.getMethod("getName");
        String returnName = (String)m2.invoke(stu2);
        System.out.println(returnName);

总结一下

Constructor的newInstance、Method的invoke、Field的get/set。在操作的时候要注意以下几点: 1、调用处的位置要和目标定义的访问修饰符保持一致;

2、Method和Field的操作要注意和实例对象的绑定关系。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值