一、反射
(一)虚拟机类加载机制
1.1虚拟机类加载机制概述
虚拟机把描述类的数据从class(字节码)文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可 以被java虚拟机直接使用的java类型----Class类型(也就是引用类型类名),这就是虚拟机的类加载机制.
通俗: 类加载就是把磁盘的字节码文件数据变为内存的Class类型对象, Class对象唯一;
1.2类加载的过程
类加载过程
当程序要使用某个类时,如果该类还未被加载到内存中,系统会通过加载,连接,初始化三步来实现对这个类的加 载.
1)加载
就是指将class文件读入内存,并为之创建一个Class对象(类对象). 任何类被使用时系统都会建立一个Class对象 例如: Person ---> Person.class Student --> Student.class
2)连接
1.验证是否有正确的内部结构,并和其他类协调一致
2.准备负责为类的静态成员分配内存,并设置默认初始化值
3.解析将类的二进制数据中的符号引用替换为直接引用
比如: String str = "abc"; str就是一个符号引用, 存储是"abc"在方法区常量池的地址. 0x100 System.out.println(str); System.out.println(0x100);
3)初始化 就是我们以前讲过的初始化步骤,例如: 静态变量赋显示初始化的值.
1.3类的加载时机
总结:类被使用时才会被加载, 类的字节码文件加载到方法区中的时机.
1.创建类的实例
2.类的静态成员使用
3.类的静态方法调用
4.使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
5.初始化某个类的子类
6.直接使用java.exe命令来运行某个主类
注意: 一个类无论使用多少次,字节码文件只会加载到方法区一次
package com.offcn.demo01;
public class Student {
static {
System.out.println("Student类完成加载了");
}
public static String color = "红色";
public static void eat(){
System.out.println("静态方法");
}
}
package com.offcn.demo01;
public class SmallStudent extends Student {
}
package com.offcn.demo01;
public class Demo01 {
public static void main(String[] args) throws Exception {
//1、实例化对象
//Student s = new Student();
//2、调用静态变量
//System.out.println(Student.color);
//3、调用静态方法
//Student.eat();
//4、利用反射
//Class.forName("com.offcn.demo01.Student");
//5、实例化其子类对象
SmallStudent ss = new SmallStudent();
}
}
(二)类加载器(了解)
2.1类加载器的概述
类加载器是负责加载类的一种对象,将class文件(硬盘)加载到内存中,并为之生成对java.lang.Class 对象.
比如: Person字节码文件 ----> 类的加载器把磁盘上字节码文加载到内存中 ---> Person的类对象
Class:就是每个类的类对象所属的类型. Student.class Person.class Dog.class
Class 类的实例表示正在运行的 Java 应用程序中的类和接口.
Person类名, new Person() new Person(10) new Person("李四") 属于Person对象
每一个类加载到方法区,形成一个字节码文件对象,称之为该类的类对象.这个类对象就是Class类型一个对象.
2.2类加载器的分类
1.Bootstrap ClassLoader 引导(根)类加载器, 使用C++实现的,在java中获取不到.
也被称为根类加载器,负责Java核心类的加载,比如System,String等.
2.ExtClassLoader 扩展类加载器
负责JRE的扩展目录中jar包的加载,在JDK中JRE的lib目录下ext目录.
3.Application ClassLoader 系统类加载器
负责在JVM启动时加载来自java命令的class文件[自定义的类],以及classpath环境变量所指定 的 jar包和类路径[第三方的类].
4.自定义类加载器(很少做)
开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需 求.
5.类加载器之间的继承关系
-Bootstrap ClassLoader
-Extension ClassLoader
-Application ClassLoader
2.3双亲委派机制(背)
1.双亲委派机制是指当一个类加载器收到一个类加载请求时,该类加载器首先会把请求委派给父类加载器.每个类加载器都是如此,只有在父类加载器在自己的搜索范围内找不到指定类时,子类加载器才会尝试自己去加载.
2.双亲委派模型工作过程:
这都在程序的执行期间需要完成的
1)当Application ClassLoader 收到一个类加载请求时,他首先不会自己去尝试加载这个类,而是先判断自己是否加载过该类,没有加载过,将这个请求委派给父类加载器Extension ClassLoader去完成.
2)当Extension ClassLoader收到一个类加载请求时,他首先也不会自己去尝试加载这个类,而是先判断自己是否加载过该类,没有加载过,将请求委派给父类加载器Bootstrap ClassLoader去完成.
3)如果Bootstrap ClassLoader,先判断自己是否加载过该类,加载过就不在加载了;如果没有加载,找到就自己加载了, 找到就加载,找不到了,就会让Extension ClassLoader尝试加载.
4)如果Extension ClassLoader也加载失败,就会使用Application ClassLoader加载,如果加载成功就直接用.
5)如果Application ClassLoader也加载失败,就会使用自定义加载器去尝试加载.
6)如果均加载失败,就会抛出ClassNotFoundException(这个类没有找到异常)异常.
3.例子:
当一个Hello.class这样的文件要被加载时.AppClassLoader先收到请求,先判断自己有没有加载过这个类,有就不再加载了;没有把请求转给ExtClassLoader加载,先判断自己有没有加载过这个类,有就不再加载了;没有把请求转给BootClassLoader加载,先判断自己有没有加载过这个类,有就不再加载了;没有就在自己能加载的范围内找寻该类,找到就加载,找不到就把请求丢ExtClassLoader,ExtClassLoader在记载范围内搜寻有没有该类,有就加载;没由就把请求丢给AppClassLoader加载,AppClassLoader在记载范围内搜寻有没有该类,有就加载,没有就抛出异常:ClassNotFoundException;
4.作用
①防止加载同一个.class。通过委托去询问上级是否已经加载过该.class,如果加载过了,则不需要重新加载。保证了数据安全。
②保证核心类.class不被篡改。比如咱们自定义一个类String,从上到下加载,只会用自己核心类库中的String,而不会用自定义的String.
2.4ClassLoader类
1.ClassLoader 叫做类加载器.虚拟机设计团队把类加载阶段,加载字节文件,这个动作放到java虚拟机外 部去实现,以便让应用程序自己决定去如何获取所需要的类,实现这个动作的模块称之为“类加载器”. ClassLoader: 类加载器是负责加载类的对象,除了把字节码文件加载到方法,还会创建这个类的类对象.
2.ClassLoader的方法: public static ClassLoader getSystemClassLoader():返回用于委派的系统类加载器 public ClassLoader getParent():返回父类加载器进行委派
3.代码示例:
package com.offcn.demo02;
public class Person {
}
package com.offcn.demo02;
public class Dog {
}
package com.offcn.demo02;
import sun.security.ec.SunEC;
public class Demo02 {
public static void main(String[] args) {
ClassLoader cl1 = ClassLoader.getSystemClassLoader();
System.out.println(cl1);
ClassLoader cl2 = cl1.getParent();
System.out.println(cl2);
ClassLoader cl3 = cl2.getParent();
System.out.println(cl3);
System.out.println("=============================");
Class c1 = Dog.class;
System.out.println(c1.getClassLoader());
Class c2 = String.class;
System.out.println(c2.getClassLoader());
Class c3 = SunEC.class;
System.out.println(c3.getClassLoader());
}
}
3.2获取Class类的对象
1.Class类: Class类型的实例(对象)表示正在运行的java应用程序的类或者接口(表示的就是类和接口字节码文件加载到方法区之后形成的对象).
2.Class类的对象: 想获取和操作类中的内容,首先要获取类的字节码对象(Class类对象),每一个正在运行的类,都有对应的字节码对象,获取了类的字节码对象,就可以使用这个对象的所有方法,这些方法都定义在Class类型中.
3.Class没有公共构造方法。Class对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的
4.三种获取Class类对象的方式:
1)类名.class属性
2)对象名.getClass()方法
3)Class.forName(全类名)方法 (这种方式称之为反射的方式)
5、代码示例:
package com.offcn.demo03;
public class Person {
public String name;
public int age;
private String gender;
public Person(){
}
public Person(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
private Person(String name, int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println("吃东西");
}
public int sum(int a, int b) {
return a+b;
}
private void sleep(){
System.out.println("睡觉");
}
public void drink(String name) {
System.out.println("喝了"+name+"饮品");
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
'}';
}
}
package com.offcn.demo03;
public class Demo01 {
public static void main(String[] args) throws Exception {
Class c1 = Person.class;
System.out.println(c1);
Person p = new Person();
Class c2 = p.getClass();
System.out.println(c2);
Class c3 = Class.forName("com.offcn.demo03.Person");
System.out.println(c3);
System.out.println(c1==c2);
System.out.println(c2==c3);
}
}
总结:
1.前两种获取类对象的方式,都属于硬编码(把代码写死了),后期程序拓展比较麻烦.
2.第三种比较灵活,因为字符串来源比较多,可以键盘录入,可以通过文件读取,还可以通过网络传输等等,有利 于代码的拓展. 而且第三种也是反射的方式的在获取,推荐使用第三种.
3.3反射获取构造方法并使用
1.Class类获取构造方法对象:
方法分类:
Constructor[] getConstructors():返回所有公共(public)造方法对象的数组
Constructor[] getDeclaredConstructors():返回所有构造方法对象的数组
Constructor getConstructor(Class... parameterTypes):返回单个公共(public)构造方法对象
Constructor getDeclaredConstructor(Class... parameterTypes):返回单个构造方法对象
注意:
getConstructor(Class... parameterTypes)
getDeclaredConstructor(Class...parameterTypes)两方法的参数列表为可变参数
这个两个方法只能获取某一个构造方法对象, 参数都是Class类型数据,且都是可变参数.
参数Class...用来描述的是要获取的构造方法中,参数列表的类对象.
每一种数据类型都有类对象, 引用数类有,基本数据也有(基本数据类型名.class)
举例说明参数:
public Person(){} 获取方式: getConstructor();
public Person(String name){} 获取方式: getConstructor(String.class);
public Person(int age){} 获取方式: getConstructor(int.class);
public Person(String name, int age) 获取方式: getConstructor(String.class,int.class);
public Person(int age, String name) 获取方式: getConstructor(int.class,String.class);
public Person(String name, String gender){}获取方式:getConstructor(String.class, String.class);
2.Constructor类型:
1)表示构造方法类型,这个类的每个对象,都是一个确定的,具体的构造方法
2)构造方法对象应该具有的功能: 获取构造方法各种信息(构造方法修饰符、构造方法名称、构造方法的参数列表、构造方法的注解),最基本的一个功能就是,创建对象.
3) Constructor类用于创建对象的方法:
T newInstance(Object... initargs) 根据指定的构造方法创建对象,参数为所运行构造方法需
要的实际参数.
如果参数类型是基本类型, 传递实参可以直接传递基本类型数据即可,因为会自动装箱.
package com.offcn.demo04;
import java.lang.reflect.Constructor;
public class GetConstructor {
public static void main(String[] args) throws Exception{
Class c = Class.forName("com.offcn.demo03.Person");
Constructor[] con1 = c.getConstructors();
System.out.println(con1.length);
for (Constructor con : con1) {
System.out.println(con);
}
System.out.println("======================================");
Constructor[] con2 = c.getDeclaredConstructors();
for (Constructor con : con2) {
System.out.println(con);
}
System.out.println("======================================");
Constructor c1 = c.getConstructor();
System.out.println(c1);
Constructor c2 = c.getConstructor(String.class,int.class,String.class);
System.out.println(c2);
System.out.println("======================================");
Constructor c3 = c.getDeclaredConstructor();
System.out.println(c3);
Constructor c4 = c.getDeclaredConstructor(String.class,int.class);
System.out.println(c4);
}
}
3.4反射获取成员变量并使用
1.Class类获取成员变量对象:
方法分类:
Field[] getFields():返回所有公共成员变量对象的数组
Field[] getDeclaredFields():返回所有成员变量对象的数组
Field getField(String name):返回单个公共成员变量对象
Field getDeclaredField(String name):返回单个成员变量对象
参数说明:写的是成员变量的名字.
public int age; 获取方式: getField("age");
public String name; 获取方式: getField("name");
public String gender; 获取方式: getField("gender");
2.Field类型:
1.表示一个成员变量类型,每个对象都是一个具体的成员变量
2.作用: 获取成员变量的各种信息(修饰符、注解、名称),做各种数据类型的转换最核心的功能就是属性赋值和取出属性中存储的值.
3.Field类用于给成员变量赋值的方法:
参数1: 给这个属性所在类创建的对象 例如:name属性属于Person, p = new Person() p2 =
new Person()
参数1: 属性值
set(Object obj, Object value): 用于给obj对象的,该成员变量,赋value值
例如: f.set(p, "rose"); 表示给p的f成员变量,赋值为rose
4.Field类获取成员变量值的方法:
参数1:属性所在类创建的对象
get(Object obj): 用于获取obj对象的指定成员变量值
f.get(p), 表示获取p的f成员变量的中的值
package com.offcn.demo04;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.jar.JarOutputStream;
public class Demo02 {
public static void main(String[] args) throws Exception{
Class c = Class.forName("com.offcn.demo03.Person");
Constructor con1 = c.getConstructor();
Object o = c.newInstance();
System.out.println(o);
Field name = c.getField("name");
Field age = c.getField("age");
name.set(o,"tom");
age.set(o,18);
System.out.println(o);
System.out.println("===========================================");
System.out.println(name.get(o));
System.out.println(age.get(o));
}
public static void test()throws Exception{
Class c = Class.forName("com.offcn.demo03.Person");
Field[] fs1 = c.getFields();
for (Field f : fs1) {
System.out.println(f);
}
System.out.println("=============================================");
Field[] fs2 = c.getDeclaredFields();
for (Field f : fs2) {
System.out.println(f);
}
System.out.println("=============================================");
Field name = c.getField("name");
System.out.println(name);
Field age = c.getField("age");
System.out.println(age);
System.out.println("=============================================");
Field gender = c.getDeclaredField("gender");
System.out.println(gender);
}
}
3.5获取类中的成员方法并执行
1.Class类获取成员方法对象:
方法分类
Method[] getMethods():返回所有公共成员方法对象的数组,包括继承的
Method[] getDeclaredMethods():返回所有成员方法对象的数组,不包括继承的
Method getMethod(String methodName, Class...parameterTypes):返回单个公共成员方法
对象
Method getDeclaredMethod(String methodName, Class...parameterTypes):返回单个成员方法对象
参数说明:
参数1:要获取方法对象的方法名
参数2:要获取方法对象的方法参数列表中参数的参数类型类对象
public void eat(){} 获取方式: getMethod("eat")
public int sum(int a,int b)获取方式:getMethod("sum", int.class, int.class)
private void sleep()获取方式:getDelaredMethod("sleep")
public void drink(String name);获取方式:getMethod("drink", String.class)
2.Method类型:
1)表示成员方法的类型,该类型的每个对象,都是一个具体的成员方法
2)成员方法对象具有的功能: 获取成员方法信息(注解,修饰符,方法名,参数类型等等),核心功能:运行方法
3.Method类用于执行方法的功能:
p.sum(10,20) -->结果30
参数1: 方法所属的对象 相当于: p
参数2: 调用方法是传入的实参 相当于:10和20
返回值类型: 方法对象代表方法功能执行完之后的返回值 相当于:30 返回返回值类型是void,invoke返回值就是null
Object invoke(Object obj, Object...values):调用obj对象的成员方法,参数是values,返回值是
Object类型.
m.invoke(p, 10, 20) 表示调用p对象m方法,传入的实参是10和20,结果30.
package com.offcn.demo04;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class Demo03 {
public static void main(String[] args) throws Exception{
Class c = Class.forName("com.offcn.demo03.Person");
Constructor con = c.getConstructor();
Object o = c.newInstance();
Method eat = c.getMethod("eat");
eat.invoke(o);
Method sum = c.getMethod("sum",int.class,int.class);
System.out.println(sum.invoke(o,10,20));
Method drink = c.getMethod("drink",String.class);
drink.invoke(o,"啤酒");
}
public static void test()throws Exception{
Class c = Class.forName("com.offcn.demo03.Person");
Method[] mt1 = c.getMethods();
for (Method m : mt1) {
System.out.println(m);
}
System.out.println("==========================================");
Method[] mt2 = c.getDeclaredMethods();
for (Method m : mt2) {
System.out.println(m);
}
System.out.println("==========================================");
Method eat = c.getMethod("eat");
System.out.println(eat);
Method sum = c.getMethod("sum", int.class, int.class);
System.out.println(sum);
Method drink = c.getMethod("drink", String.class);
System.out.println(drink);
}
}
3.6暴力反射(用来操作private修饰的内容)
1.通过Class类中:
getDeclaredXXX方法: 可以获取类中的所有声明的成员(属性、方法、构造),私有的成员也可以获取到.但是私有成员进行访问使用时,会因为权限问题导致失败,因此就需要暴力反射解决访问私有的问题.
学习暴力反射的目的就是为了操作private修饰的内容,如果操作的是public修饰不需要暴力反射.
2.修改该对象私有内容的访问权限:
AccessibleObject类是Field,Method和Constructor对象的基类. 它提供了在使用它时反射对象
将其标记为抑制默认Java语言访问控制检查的功能.
setAccessible(boolean flag): 参数为true表示忽略掉java语言的语法检测. 参数为false表示使用java语言语法检测.
3.一旦设定当前对象可以访问,私有的成员也可以被访问,被修改.
package com.offcn.demo04;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Demo04 {
public static void main(String[] args)throws Exception {
Class c = Class.forName("com.offcn.demo03.Person");
Constructor con1 = c.getDeclaredConstructor(String.class,int.class);
con1.setAccessible(true);
Object o = con1.newInstance("tom",18);
System.out.println(o);
System.out.println(con1.isAccessible());
Constructor con2 = c.getConstructor();
Object o1 = con2.newInstance();
Field f = c.getDeclaredField("gender");
f.setAccessible(true);
f.set(o1,"man");
System.out.println(f.isAccessible());
System.out.println(o1);
Method sleep = c.getDeclaredMethod("sleep");
sleep.setAccessible(true);
sleep.invoke(o1);
System.out.println(sleep.isAccessible());
}
}