为了明白为啥有反射这个东西,得先从JVM如何获取.class文件开始!
类的加载三步骤:
- 1、加载:从硬盘或网络中读取一个.class文件数据到JVM内存中
- 2、连接三小步:
- 1)校验:其中一项看字节码的数据是否以“魔数cafe”以及当前的JVM运行的JDK版本是否可以运行该字节码的数据。例如:JDK1.8可以运行用JDK1.7编译的字节码,反过来就不可以
- 2)准备:给成员变量(类变量/静态变量)赋默认值;把常量(final)等值在方法去的常量池中准备好
- 3)解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程,可以理解为把类中的对应的类型名换成该类型的Class对象的地址
- 3、初始化类初始化,类初始化由两部分组成
- 1)静态变量的显示初始化代码,赋值代码
- 2)静态代码块
- 3)注:1)和2)的顺序是从上往下
不说了,我要甩图啦!来了老弟,类加载与初始化↓注意事项
- 1、当一个类初始化时,发现它的父类如果没有初始化,会先初始化父类
- 2、每一个类只会初始化一次,并且类初始化的过程是线程安全的
- 3、哪些操作导致类的初始化?这句话的意思:类的加载不一定就会发生类初始化(可能只会发生加载和连接这两步)
- 1)main方法所在的类在加载时,直接就先初始化
- 2)new一个类的对象,一定会先完成类的初始化
- 3)调用该类的静态变量(final的常量除外)和静态方法
- 4)使用java.lang.reflect包的方法对类进行反射调用
- 5)当初始化一个类,如果其父类没有被初始化,则先会初始化他的父类
- 4、哪些情况不会导致类初始化?
- 1)引用静态常量(final)不会触发此类的初始化(因为常量在连接-准备的时候就已经在常量池中了)
- 2)当访问一个静态域时,只有真正声明这个域的类才会被初始化,换句话来说:通过子类访问父类的静态域时,只会初始化父类,不会初始化子类
- 3)通过数组定义类引用,不会触发此类的初始化,如:Student[] stus = new Student[3];时Student类并不会被初始化(因为Student[]是一个数组类型并不是一个Student类型,只有它的元素才是Student类型)
- 5、那我们现在都知道了类的加载有三步骤生成对应的Class对象:加载、连接、类初始化。那new一个实例对象时会执行实例初始化,也有三步骤:
- 1)非静态域的显示赋值
- 2)非静态代码块的执行
- 3)构造器的执行
- 4)其中1)和2)是按顺序执行的,3)一定是最后才被执行!
- 5)记得不要把类初始化和实例初始化弄混,不行倒回去再瞪瞪emmm
- 6、注:JVM这样做的目的就是为了尽可能的做到“懒”加载,需要用才载入内存,不需要的不载!
类加载的结果
- 结论:无论如何操作,最终类加载的结果都是为了:在方法区中有一个唯一的Class对象来代表一个类型,每一种类型都有且只有一个Class对象
- 1、java.lang.Class
- 1)Class类的实例表示正在运行的Java引用程序中的类和接口
- 2)枚举是一种类,注释(注解)是一种接口
- 3)每个数组属于被映射为Class对象的一个类,所有具有相同元素类型和维度的数组都共享该Class对象。也就是说数组也有对应的Class对象
- 4)八大基本类型和关键字void也有对应的Class对象
- 2、如何在程序中获取对应的Class对象
- 1)类型名.class (注:八大基本类型和void,只能通过这种方式)
- 2)实例对象.getClass()(注:只能用于引用数据类型)
- 3、Class.forName("完全限定名称")(注:数组也可以通过该方式得到对应的Class对象)
- 4、类加载器对象.loadClass("完全限定名称")
四种类加载器
- 1、引导类加载器(Bootstrap Classloader):又称为根类加载器
- 1)它负责加载Java的核心库(JAVA_HOME/jre/lib/rt.jar等或sun.boot.class.path路径下的内容),是用原生代码(C/C++)来实现的,并不继承自java.lang.ClassLoader,所以通过Java代码获取引导类加载器对象将会得到null
- 2、扩展类加载器(Extension ClassLoader)
- 1)它由sun.misc.Launcher$ExtClassLoader实现,是java.lang.ClassLoader的子类,负责加载Java的扩展库(JAVA_HOME/jre/lib/ext/*.jar或java.ext.dirs路径下的内容)
- 3、应用程序类加载器(Application Classloader)
- 1)它是由sun.misc.Launcher$AppClassLoader实现,是java.lang.ClassLoader的子类,负责加载Java应用程序类路径(classpath、java.class.path)下的内容
- 4、自定义类加载器
- 1)开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需要,例如对字节码进行加密来避免class文件被反编译,或者加载特殊目录下的字节码数据
- 2)例如:tomacat加载特定目录下的类
- 注:Java的类加载过程是一个双亲(parent)委托模式加载的,4,3,2,1加载器是组合关系,如4认3为parent加载器,但不是继承关系。例如:当“应用程序类加载器”接到一个加载任务时:
- 1)先搜索内存中是否已经加载过了,如果加载过了,就可以找到对应的Class对象,那么就不加载了
- 2)如果没有找到,把这个任务先提交给“parent”,“父”加载器接到任务时,也是重复1)和2)操作
- 3)直到传给了根加载器,如果根加载器可以加载,就完成了,如果不能加载,往回传,依次每个加载器尝试在自己负责的路径下搜索,如果找到就直接返回Class对象,如果一直回传到“应用程序类加载器”,还是没有找到,就会报ClassNotFoundException
别说了,先上一波图,有点脑路↓
- 再注:为啥要怎么干,传来传去的emmm
- 1)目的是为了安全,防止你写一个和核心类库一样的类,每一个加载器负责自己的路径下的东西!
类加载器的作用
- 1)目的是为了安全,防止你写一个和核心类库一样的类,每一个加载器负责自己的路径下的东西!
- 1、最主要的作用就是用来加载类的
- 2、还可以起到辅助的作用:可以用类加载器来加载“类路径下”的资源文件,如在JavaSE阶段的:src下的文件-->bin目录下
- 3、ClassLoader
- 1)静态方法,ClassLoader.getSystemResourceAsStream("文件名");但是这个只适用于JavaSE阶段,因为它用应用程序类加载器去加载的,注:在Web阶段用这个方法是有问题的,因为Web阶段的类路径在WEB-INF/classes下,必须由Web自定义的类加载器去加载
- 2)非静态方法,类加载器对象.getResourceAsStream("文件名");该方法适用于Web阶段!
好了,来啦!反射
别说了,上来先来一段定义,反射:在运行时可以获取到类的信息,对类进行操作,而且该类可能是在编译时完全未知的类型。反射使得Java具有动态语言的特征,注:我们在正常的操作是 从类->对象(new);现在有了反射就是从 该类的Class对象->操作类的所有成员!
- 类==》对象:先写类==》用类创建对象->通过对象操作部分公开成员
- 提前笔记:反射又很多种,例如:tomcat,hibernate,spring,mybatis等各种框架都运用了反射机制(也不是很明白,可能学到了才懂吧!emmm)。
- 反射在开发的应用有如下几个方面
- 1)在运行期间,动态的获取某个类的详细信息
- 2)在运行期间,动态的创建任意类型的对象,只要这个类可以创建对象
- 3) 在运行期间,动态的为对象的属性赋值
- 4) 在运行期间,动态的调用任意对象的任意方法
- 5)在运行期间,动态获取注解的信息(Web框架经常使用!)
- 反射创建对象两种方式
- 1)Class对象.newInstance();注:只支持有无参构造的
- 2)通过Class对象获取到该类的构造器,然后 获取的构造器对象.newInstance(Ojbect ... initargs);注:都支持!
不写了,脑壳疼(不抄了emmm)