CRUD工程师---番外篇---反射
![](https://img2020.cnblogs.com/blog/1898491/202006/1898491-20200608224531172-122897639.png)
1、优点:使用反射,我们就可以在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
2、缺点:(1)反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
(2)反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
反射的作用:
1、反编译:.class-->.java
2、通过反射机制访问java对象的属性,方法,构造方法等
3、当我们在使用IDE,比如Ecplise时,当我们输入一个对象或者类,并想调用他的属性和方法是,一按点号,编译器就会自动列出他的属性或者方法,这里就是用到反射。
4、反射最重要的用途就是开发各种通用框架。比如很多框架(Spring)都是配置化的(比如通过XML文件配置Bean),为了保证框架的通用性,他们可能需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象。
(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(); } }
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是创建了一个构造方法的声明类的新实例对象,并为之调用。
![](https://img2020.cnblogs.com/blog/1898491/202006/1898491-20200608224722750-1441890647.png)
Class的生命周期就是指一个class文件(字节码)从载入到卸载的全过程。
当一个类被装载、连接、初始化后,它的生命周期就開始了,当代表该类的Class对象不再被引用、即已经不可触及的时候,Class对象的生命周期结束。那么该类的方法区内的数据也会被卸载,从而结束该类的生命周期。
一个类的生命周期取决于它Class对象的生命周期,经历载入、连接、初始化、使用、和卸载五个阶段。
装载 Loading
查找Class的二进制文件(.class),把类的信息载入到JVM的方法区中。对其进行部分检验(类文件的魔数。文件长度,是否有父类等);在堆区中实例化一个java.lang.Class对象,作为方法区中这个类的信息的入口。
载入方式有多种:A. 在classPath下找对应class文件;B. 从jar文件里读取;C. 从网络中获取;D. 实时生成。如设计模式中的动态代理模式;E. 从非class文件里获取,这些文件在jvm中执行之前也会被转换为可识别的字节码文件。
一般来说载入和连接是同步的,但有时候也会交叉进行,可是两者的開始和结束是顺序的。
链接 Linking
将相应的字节码文件读入到JVM中(当中解析步骤是能够选择的)
a) 验证
检查加载的class文件数据的合法性:字节码的格式,变量与方法是否反复、数据类型是否有效、继承与实现是否合法,接入属性是否正确(public ,private的问题),检查 final class 有没有被继承。检查静态变量的正确性等等。
b) 准备
给类的静态变量分配存储空间:赋default值(基本类型为0。引用为null)。静态常量(如final static int a = 100)。但不会对其进行初始化,不会运行不论什么 Java 代码(static块)。
c) 解析:
将符号引用转成直接引用。完毕内存结构的布局。
比如我们要调用Collections.toString()。java.util.Collections.toString()就是符号引用,而直接引用就是这种方法在方法区中的内存地址。
解析就是把类、接口、方法、成员变量的符号引用转换成内存地址,以供调用。
在连接阶段完毕后,再依据使用的情况(直接引用or被动引用)来决定是否类进行初始化。
初始化 Initializing
对静态变量赋assign值。运行静态代码块。 【类的初始化顺序能够參考我的《类的初始化&实例化顺序》】
在Java中类的引用分为直接引用和被动引用,仅仅有直接引用。会触发类的初始化:
a) new实例化对象;b) 使用类的(很量)静态变量 / 方法;c) 通过反射运行前三种情况。d) 子类被初始化;e) 作为程序入口。调用main(也是调用静态方法的一种)。
其它使用类的方式都叫被动引用,如:
a) 定义类数组。b) 引用类的静态常量;c) 引用父类的静态域,仅仅会触发父类的初始化,而不会触发子类的初始化。
从上面能看出。初始化的原则是,仅仅初始化要用到的。
在主动引用中,实例化(new/main)、使用静态域都是被用到了;而子类依赖了父类。所以也会触发初始化。在被动引用中,a中的类型仅仅是用于编译器的校验,而b中的静态常量是在链接中的准备阶段已经完毕了,所以两者都用不上去初始化。另外c中仅仅用到了父类的静态域,所以就不是必需触发子类。
console:
Init parent
能看出上面仅仅初始化了父类。那假设我们把HELLO属性放在子类SubLoaderLazy中,子类也会被初始化。
console:
Init parent
Init sub
实例化 NewInstance
类的初始化完毕后。我们就能够创建对象实例了。
相比初始化仅仅运行类的静态域(静态变量/代码块)。类的实例化是运行类的实例域(非静态变量/代码块)和构造函数。而且在堆区创建一个类的实例对象。
(在实例化时,假设没有指定构造函数,JVM会自己主动构造一个无參构造函数。
)
类的实例化,一般在使用new创建对象。或者Class.newInstance()时运行。
回收卸载 GC
Class作为JVM中的一个特殊对象,也会被GC回收卸载。
Class的卸载就是清空方法区中Class的信息和堆区中的java.lang.Class对象。这时Class的声明周期就结束了。
SUN的原话:“class or interface may be unloaded if andonly if its class loader is unreachable. Classesloaded by the BootstrapClassLoadermay not be unloaded”。
Class被回收要满足下面三个条件:
a) No Instance:该类全部的实例都已经被GC;
b) No ClassLoader:载入该类的ClassLoader实例已经被GC;
c) No Reference:该类的java.lang.Class对象没有被引用。(XXX.class, 静态变量/方法)
另外。有些Class是不会被回收的:
1、依据JVM和JLS的规范,由Bootstrap类载入器载入的Class在整个执行期间是不会被卸载的。
2、被Extension类载入器 和System类载入器载入的Class在执行期间不太可能被卸载,由于这些实例基本上在整个执行期间总能直接或者间接的訪问的到,其达到unreachable的可能性极小。
3、开发人员自己定义的类载入器载入的Class仅仅有在非常easy的上下文环境中才干被卸载,略微复杂点的应用场景中,非常难有符合上面3个条件的Class。
综合以上三点,一个已经载入的Class被卸载的几率非常小。并且卸载时间也是不可控的。并且Class占用的内存空间不大。一般不用考虑。