Java反射从零到精通笔记
一、通过需求引出反射
二、反射机制图
三、反射的优点和缺点
1. 反射机制优化
/**
* @author 86176
* 反射专题:可以看到反射用时比传统方法用时长,因为反射基本是解释执行
* 关闭访问检查机制可以减短一些运行时长
*/
public class Reflection2 {
public static void main (String[] args) throws Exception {
m1();
m2();
m3();
}
static void m1(){
long start = System.currentTimeMillis();
Cat cat = new Cat();
for (int i=0;i<900000000;i++){
cat.hi();
}
long end = System.currentTimeMillis();
System.out.println("传统方法用时:"+(end-start));
}
static void m2() throws Exception {
long start = System.currentTimeMillis();
Class aClass = Class.forName("onlineClass.reflection.Cat");
Method hi = aClass.getMethod("hi");
Object cat = aClass.newInstance();
for (int i=0;i<900000000;i++){
hi.invoke(cat);
}
long end = System.currentTimeMillis();
System.out.println("反射方法用时:"+(end-start));
}
static void m3() throws Exception {
long start = System.currentTimeMillis();
Class aClass = Class.forName("onlineClass.reflection.Cat");
Method hi = aClass.getMethod("hi");
hi.setAccessible(true);
Object cat = aClass.newInstance();
for (int i=0;i<900000000;i++){
hi.invoke(cat);
}
long end = System.currentTimeMillis();
System.out.println("反射方法用时,(关闭检查机制:"+(end-start));
}
}
四、Class类分析
1. class类的类图
2. Class类的常用方法
每个类会生成一个class字节码文件,称之为元数据
五、获取class类对象的六种方式
六种方式可以根据阶段来理解
- 编码阶段获取 :Class.forName()
- 运行阶段获取(已有实例对象):对象.getClass()
- Class类阶段 :类.class
- 类加载器得到Class对象:ClassLoader.loadClass(classPath)
- java基本类型得到(例int,double):类.class
- 基本数据类型对应包装类:类.TYPE
String classAllPath = "onlineClass.reflection.Cat";
//第一种:通过Class.forName静态方法获得(已知类的全路径下使用,框架用得比较多
Class cls1 = Class.forName(classAllPath);
//第二种:类.class获得
Class cls2 = Cat.class;
//第三种:已经实例化对象的情况下,得到class
Cat cat = new Cat();
Class cls3 = cat.getClass();
//第四种:通过类加载器得到
Cat cat1 = new Cat();
ClassLoader classLoader = cat1.getClass().getClassLoader();
Class cls4 = classLoader.loadClass(classAllPath);
//第五种:java基本数据类型获得
Class<Integer> cls5 = int.class;
//第六种:基本数据类型对应包装类,可以通过类.TYPE获得
Class<Integer> cls6 = Integer.TYPE;
System.out.println(cls1.hashCode());
System.out.println(cls2.hashCode());
System.out.println(cls3.hashCode());
System.out.println(cls4.hashCode());
System.out.println(cls5.hashCode());
System.out.println(cls6.hashCode());
//可以发现cla1,cls2,cls3,cls4的hashCode都是一样的,
// 原因是内存里面,不论怎么处理在堆中一个类只能有一个类对象
/*
运行结果:
460141958
460141958
460141958
460141958
1163157884
1163157884
*/
六、有哪些类型有Class对象
七、动态和静态加载
实例代码:
- 静态加载:如上图所示,其实程序不一定会用到Dog类(若key为2就用不到),但是在编译阶段直接报错,所以是静态加载
- 动态加载:反射就是动态加载,如果没有到需要用到该方法的时候就不会报错
八、类加载流程图
九、类加载的五个阶段
/**
* 类加载的执行顺序:
* 1.加载:把class字节码文件装载到内存中,并且生成一个class对象
* 2.连接:
* 2.1.验证:验证字节码文件是否符合要求,开头有魔数cafebabe,并且不危及虚拟机安全
* 2.2.准备:JVM会在准备阶段把静态变量分配内存并初始化,如0,null,false等(此时num=0)
* 2.3.解析:JVM将常量池中的符号应用替换成直接引用的过程
* 3.初始化:到此阶段真正开始执行类中定义的java代码,此过程是执行<clinit()>方法的过程
* clinit方法会合并静态代码块和静态变量,并执行,所以num=300并没有执行
*
*/
1.加载阶段
2.连接阶段
2.1.连接阶段-验证
2.2.连接阶段-准备
2.3.连接阶段-解析
3.初始化
这里讲到,就是因为第3点,每个类在堆中才只有独一份
/**
* @author huochai
* @date 2022/10/8 21:03
* P13:类加载的顺序
*/
public class ClassLoad03 {
public static void main(String[] args) {
System.out.println(B.num);
/**
* 类加载,执行顺序:
* 1.加载:把class字节码文件装载到内存中,并且生成一个class对象
* 2.连接:
* 2.1.验证:验证字节码文件是否符合要求,开头有魔数cafebabe,并且不危及虚拟机安全
* 2.2.准备:JVM会在准备阶段把静态变量分配内存并初始化,如0,null,false等(此时num=0)
* 2.3.解析:JVM将常量池中的符号应用替换成直接引用的过程
* 3.初始化:到此阶段真正开始执行类中定义的java代码,此过程是执行<clinit()>方法的过程
* clinit方法会合并静态代码块和静态变量,并执行,所以num=300并没有执行
*
*/
}
}
class B{
static {
System.out.println("B类被加载");
num = 300;
}
static int num = 100;
public B(){
System.out.println("B()构造方法被执行");
}
}
十一、反射获取类的结构信息
十二、反射爆破创建实例
/**
* @author huochai
* @date 2022/10/8 22:40
* P16:反射爆破创建实例对象
* 例1:通过无参public构造方法实例化对象
* 例2:通过有参Public构造方法实例化对象
* 例3:通过有参非public构造方法实例化对象
* 本集重点:setAccessible()关闭安全性检查,俗称爆破
*/
public class ReflectionCreateInstance {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
/**
* 例1:直接用newInstance创建
*/
Class<?> cls1 = Class.forName("onlineClass.reflection.User");
Object o = cls1.newInstance();
System.out.println(o);
/**
* 例2:先获取有参构造器再通过构造器的newInstance方法实例化对象
*/
Class<?> cls2 = Class.forName("onlineClass.reflection.User");
Constructor<?> constructor = cls2.getConstructor(String.class);
Object huochai = constructor.newInstance("huochai");
System.out.println(huochai);
/**
* 例3:和例2的步骤类似,先获取类对象的构造方法(这一步要使用getDeclaredConstructor方法,因为获取的是私有构造方法)
* 然后再通过构造方法的newInstance方法实例化,
* 不过在这之前要先把安全性检查关闭,这样私有构造方法就也可以实例化对象
*/
Class<?> cls3 = Class.forName("onlineClass.reflection.User");
Constructor<?> declaredConstructor = cls3.getDeclaredConstructor(String.class, int.class);
//设置爆破开启(安全性检查关闭)
declaredConstructor.setAccessible(true);
Object huochai1 = declaredConstructor.newInstance("huochai", 99999);
System.out.println(huochai1);
}
}
class User{
String name;
int balance ;
public User(){
}
public User(String name){
this.name = name;
}
private User(String name,int balance){
this.name = name;
this.balance = balance;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", balance=" + balance +
'}';
}
}
十三、反射爆破操作属性
/**
* @author huochai
* @date 2022/10/9 18:36
* P17:访问对象中的属性
*
*/
public class ReflectionAccessProperty {
public static void main(String[] args) throws Exception {
Class<?> aClass = Class.forName("onlineClass.reflection.Person");
//方法1:public类型的属性直接通过get获得
Object o = aClass.newInstance();
Field name = aClass.getField("name");
name.set(o,"huochai");
System.out.println(name.get(o));
//方法2:非public类型的属性通过getDeclaredField获得
Field age = aClass.getDeclaredField("age");
System.out.println(age.get(o));
//方法3:static修饰的属性可写为get(null),因为静态属性和具体实例对象无关,只和类加载有关
Field age2 = aClass.getDeclaredField("age");
System.out.println(age2.get(null));
//方法4:private修饰的属性需要关闭安全检查机制(setAccessible)才可获取
Field money = aClass.getDeclaredField("money");
money.setAccessible(true);
System.out.println(money.get(o));
}
}
class Person{
public String name;
static int age;
private int money;
}
十四、反射爆破操作方法
/**
* @author huochai
* @date 2022/10/10 15:37
*/
public class ReflectionAccessMethod {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
//1.如用private修饰则需要getDeclaredMethod得到全部方法
//2.如用private修饰则需要关闭安全检查setAccessible(true)
//3.如是静态方法则可以不写具体实例对象
Class<?> aClass = Class.forName("onlineClass.reflection.Person");
Method say = aClass.getDeclaredMethod("say", String.class, int.class, int.class);
say.setAccessible(true);
Object o = aClass.newInstance();
String str = (String) say.invoke(null,"huochai",1,23);
System.out.println(str);
}
}
十五、反射课后练习
课后练习1:
/**
* @author huochai
* @date 2022/10/10 16:06
* 创建PrivateTest的类,利用Class类得到私有的name属性,
* 修改私有的name属性值,并调用getName()的方法打印name属性值
*/
public class HomeWork1 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<PrivateTest> privateTestClass = PrivateTest.class;
PrivateTest privateTest = privateTestClass.newInstance();
Field name = privateTestClass.getDeclaredField("name");
name.setAccessible(true);
name.set(privateTest,"haha");
Method getName = privateTestClass.getMethod("getName");
System.out.println(getName.invoke(privateTest));
}
}
class PrivateTest{
private String name = "hellokitty";
public String getName(){
return name;
}
}
课后练习2:
/**
* @author huochai
* @date 2022/10/10 16:13
* 1. 利用Class类的forName方法得到File类的class对象
* 2. 在控制台打印File类的所有构造器
* 3. 通过newInstance的方法创建File对象,并创建E:\myNew.txt
*/
public class HomeWork2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> fileCls = Class.forName("java.io.File");
Constructor<?>[] declaredConstructors = fileCls.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
Constructor<?> constructor = fileCls.getConstructor(String.class);
Object o = constructor.newInstance("C:\\temp\\myNew.txt");
Method createNewFile = fileCls.getMethod("createNewFile");
createNewFile.invoke(o);
System.out.println("文件创建成功!");
}
}