Java面试基础——反射机制 红太狼 2022-11-21 04:27 141阅读 0赞 # 一、什么是反射 # 反射是Java的特征之一,是一种**间接操作目标对象的机制**。 核心是**JVM在运行的时候才动态加载类**,并且对于任意一个类,都能够知道这个类的所有属性和方法,调用方法/访问属性,不需要提前在编译期知道运行的对象是谁。 **它允许运行中的Java程序获取类的信息,并且可以操作类或对象内部属性**。 程序中对象的类型一般都是在编译期就确定下来的,而当我们的程序在运行时,可能需要动态的加载一些类,这些类因为之前用不到,所以没有加载到jvm,这时,使用Java反射机制可以在运行期动态的创建对象并调用其属性,它是在运行时根据需要才加载。 # 二、反射的原理 # 下图是类的正常加载过程,反射原理与class对象: Class对象的由来是将class文件读入内存,并为之创建一个Class对象。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2ppa2UxMTIzMQ_size_16_color_FFFFFF_t_70_pic_center] Java反射的原理:**java类的执行**需要经历以下过程, **编译**:.java文件编译后生成.class字节码文件 **加载**:类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部,并存储在运行时内存区的方法区,然后将其转换为一个与目标类型对应的java.lang.Class对象实例 **连接**:细分三步 验证:格式(class文件规范) 语义(final类是否有子类) 操作 准备:静态变量赋初值和内存空间,final修饰的内存空间直接赋原值,此处不是用户指定的初值。 解析:符号引用转化为直接引用,分配地址 **初始化**:有父类先初始化父类,然后初始化自己;将static修饰代码执行一遍,如果是静态变量,则用用户指定值覆盖原有初值;如果是代码块,则执行一遍操作。 **Java的反射就是利用上面第二步加载到jvm中的.class文件来进行操作的。.class文件中包含java类的所有信息,当你不知道某个类具体信息时,可以使用反射获取class,然后进行各种操作。** Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。总结说:**反射就是把java类中的各种成分映射成一个个的Java对象,并且可以进行操作。** # 三、反射的优缺点 # 1、**优点** 使用反射,我们就可以在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。 2、**缺点** (1)反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射; (2)反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。 # 四、反射的用途 # 1、反编译:.class–>.java 2、通过反射机制访问java对象的属性,方法,构造方法等 3、当我们在使用IDE比如Ecplise时,当我们输入一个对象或者类,并想调用他的属性和方法是,一按点号,编译器就会自动列出他的属性或者方法,这里就是用到反射。 4、反射最重要的用途就是开发各种通用框架。比如很多框架(Spring)都是配置化的(比如通过XML文件配置Bean),为了保证框架的通用性,他们可能需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象。 例如,在使用Strut2框架的开发过程中,我们一般会在struts.xml里去配置Action,比如 <action name="login" class="org.ScZyhSoft.test.action.SimpleLoginAction" method="execute"> <result>/shop/shop-index.jsp</result> <result name="error">login.jsp</result> </action> 比如我们请求login.action时,那么StrutsPrepareAndExecuteFilter就会去解析struts.xml文件,从action中查找出name为login的Action,并根据class属性创建SimpleLoginAction实例,并用Invoke方法来调用execute方法,这个过程离不开反射。配置文件与Action建立了一种映射关系,当View层发出请求时,请求会被StrutsPrepareAndExecuteFilter拦截,然后StrutsPrepareAndExecuteFilter会去动态地创建Action实例。 5、加载数据库驱动的,用到的也是反射。JDBC利用反射将数据库的表字段映射到java对象的getter/setter方法。 Class.forName("com.mysql.jdbc.Driver"); // 动态加载mysql驱动 6、Jackson,GSON,Boon等类库也是利用反射将JSON文件的属性映射到java对的象getter/setter方法。 # 五、反射机制常用的类 # Java.lang.Class; Java.lang.reflect.Constructor; Java.lang.reflect.Field; Java.lang.reflect.Method; Java.lang.reflect.Modifier; # 六、反射的基本使用 # ## 1、获得Class:主要有三种方法 ## (1)Object–>getClass (2)任何数据类型(包括基本的数据类型)都有一个“静态”的class属性 (3)通过class类的静态方法:forName(String className)(最常用) package fanshe; public class Fanshe { public static void main(String[] args) { //第一种方式获取Class对象 Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。 Class stuClass = stu1.getClass();//获取Class对象 System.out.println(stuClass.getName()); //第二种方式获取Class对象 Class stuClass2 = Student.class; System.out.println(stuClass == stuClass2);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个 //第三种方式获取Class对象 try { Class stuClass3 = Class.forName("fanshe.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名 System.out.println(stuClass3 == stuClass2);//判断三种方式是否获取的是同一个Class对象 } catch (ClassNotFoundException e) { e.printStackTrace(); } } } 注意,在运行期间,一个类,只有一个Class对象产生,所以打印结果都是true; 三种方式中,常用第三种,第一种对象都有了还要反射干什么,第二种需要导入类包,依赖太强,不导包就抛编译错误。一般都使用第三种,一个字符串可以传入也可以写在配置文件中等多种方法。 ## 2、判断是否为某个类的实例 ## 一般的,我们使用instanceof 关键字来判断是否为某个类的实例。同时我们也可以借助反射中Class对象的isInstance()方法来判断时候为某个类的实例,他是一个native方法。 public native boolean isInstance(Object obj); ## 3、创建实例:通过反射来生成对象主要有两种方法 ## (1)使用Class对象的newInstance()方法来创建Class对象对应类的实例。 Class<?> c = String.class; Object str = c.newInstance(); (2)先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建对象,这种方法可以用指定的构造器构造类的实例。 //获取String的Class对象 Class<?> str = String.class; //通过Class对象获取指定的Constructor构造器对象 Constructor constructor=c.getConstructor(String.class); //根据构造器创建实例: Object obj = constructor.newInstance(“hello reflection”); ## 4、通过反射获取构造方法 ## (1)批量获取的方法: public Constructor\[\] getConstructors():所有"公有的"构造方法 public Constructor\[\] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有) (2)单个获取的方法,并调用: public Constructor getConstructor(Class… parameterTypes):获取单个的"公有的"构造方法: public Constructor getDeclaredConstructor(Class… parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有; (3) 调用构造方法: Constructor–>newInstance(Object… initargs) newInstance是 Constructor类的方法(管理构造函数的类) api的解释为:newInstance(Object… initargs) ,使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。 它的返回值是T类型,所以newInstance是创建了一个构造方法的声明类的新实例对象,并为之调用。 # 七、反射机制的应用案例 # ## 1、需求 ## 写一个"框架",在不改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中的任意方法。 ## 2、实现 ## (1)配置文件 (2)反射机制 ## 3、步骤 ## (1)将需要创建的对象的全类名和需要执行的方法定义在配置文件中 (2)在程序中加载读取配置文件 (3)使用反射技术把类文件加载进内存 (4)创建对象 (5)执行方法 ## 4、代码实现 ## 4.1 需要的实体类 (1)Person类 public class Person { //无参方法 public void eat(){ System.out.println("eat..."); } } (2)Student类 public class Student { //无参方法 public void study(){ System.out.println("I am a Student"); } } 4.2 编写配置文件 className = zzuli.edu.cn.Person methodName = eat 4.3 实现框架 /** * 前提:不能改变该类的任何代码。可以创建任意类的对象,可以执行任意方法 * 即:拒绝硬编码 */ public class ReflectTest { public static void main(String[] args) throws Exception { //1.加载配置文件 //1.1创建Properties对象 Properties pro = new Properties(); //1.2加载配置文件 //1.2.1获取class目录下的配置文件(使用类加载器) ClassLoader classLoader = ReflectTest.class.getClassLoader(); InputStream inputStream = classLoader.getResourceAsStream("pro.properties"); pro.load(inputStream); //2.获取配置文件中定义的数据 String className = pro.getProperty("className"); String methodName = pro.getProperty("methodName"); //3.加载该类进内存 Class cls = Class.forName(className); //4.创建对象 Object obj = cls.newInstance(); //5.获取方法对象 Method method = cls.getMethod(methodName); //6.执行方法 method.invoke(obj); } } 4.4 运行结果 ![在这里插入图片描述][20210506231509133.png] 4.5 修改配置文件,再次运行 //将配置文件内的信息修改为Student类及类内的study方法 className = zzuli.edu.cn.Student methodName = study 运行结果 ![在这里插入图片描述][20210506231539200.png] **优点**:对于框架来说,已经对基础的代码进行了封装并提供相应的API。在框架的基础上进行软件开发,可以简化编码。 但如果我们使用传统的new形式来实例化,那么当类更改时我们就要修改Java代码,这是很繁琐的。修改Java代码以后我们还要进行重新编译、测试、发布等一系列的操作。 而如果我们仅仅只是修改配置文件,而不需要修改Java代码就简单的多。此外使用反射还能达到解耦的效果,如果我们使用的是new这种形式进行对象的实例化。此时如果在项目的某一个小模块中我们的一个实例类丢失了,那么在编译期间就会报错,会导致整个项目无法启动。 而对于反射创建对象Class.forName(“全类名”)这种形式,我们在编译期需要的仅仅只是一个字符串(全类名),在编译期不会报错,这样其他的模块就可以正常的运行,而不会因为一个模块的问题导致整个项目崩溃。 **原文链接** [Java基础篇:反射机制详解][Java] **参考文章** https://blog.csdn.net/Mr\_wxc/article/details/105812627\#comments\_16261277 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2ppa2UxMTIzMQ_size_16_color_FFFFFF_t_70_pic_center]: /images/20221120/c52c7da4614543f7886fb52e0102e97c.png [20210506231509133.png]: /images/20221120/d98ececc69df41d38b33e20a937a7dad.png [20210506231539200.png]: /images/20221120/0fe2e49c0c774a71a83ab944c88b63e6.png [Java]: https://blog.csdn.net/a745233700/article/details/82893076
还没有评论,来说两句吧...