一、快速入门
-
应用场景
通过配置文件,创建类,并调用方法
- 配置文件 ref.properties
className = reflection.Cat method = sleep
Properties properties = new Properties(); ClassPathResource classPathResource = new ClassPathResource("ref.properties"); InputStream inputStream = classPathResource.getInputStream(); properties.load(inputStream); String className = properties.getProperty("className"); String method = properties.getProperty("method"); Class<?> aClass = Class.forName(className); Cat cat = (Cat)aClass.newInstance(); Method method1 = aClass.getMethod(method); method1.invoke(cat);
-
步骤
-
读取配置文件
-
加载类
Class<?> aClass = Class.forName(className);
-
得到类对象
Cat cat = (Cat)aClass.newInstance();
-
得到类方法
在反射中,方法也可视为对象
Method method1 = aClass.getMethod(method);
-
执行类方法
方法对象.invoke(类对象)
method1.invoke(cat);
-
二、反射机制
反射相关的主要类:
在反射中,类的成员变量,方法,构造器 都视为 对象
-
java.lang.Class
表示一个类
Class 对象表示某个类加载后在堆中的对象
Class<?> aClass = Class.forName("com.ajun.Cat");//加载类(全限定名) Cat cat = (Cat)aClass.newInstance();//得到类对象
-
java.lang.reflect.Method
表示类的方法
Method method = aClass.getMethod("sleep");//获取方法对象 (方法名:sleep) method.invoke(cat); //执行方法 【方法对象.invoke(类对象)】
-
java.lang.reflect.Field
表示类的成员变量
//只可以得到public类型的变量 Field name = aClass.getField("name");//获取成员变量的对象 (成员变量:name) name.get(cat); //得到成员变量的值 【成员变量对象.get(类对象)】
-
java.lang.reflect.Constructor
表示类的构造器
Constructor constructor = aClass.getConstructor();//获取无参构造器对象 Constructor constructor1 = aClass.getConstructor(String.class);//获取有参构造器对象。需要传入的参数为:构造器参数类型calss,如String类型就是 String.class
三、反射优化
-
反射优点
可以动态地创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架就失去了底层支撑
-
反射缺点
反射基本上是解释执行,对速度有影响
-
优化
关闭安全检查开关(优化效果有限)
Method、Field、Constructor 都实现了 AccessibleObject 接口,有 setAccessible(boolean flag) 方法
setAccessible(true):关闭安全检查;默认是打开的。可以对私有属性、方法等实施爆破(突破私有限制),配合getDeclaredxxx()使用
四、class类分析
-
Class 也是类,继承自 Object。是一种特殊的类,Class 类的实例对象对应着 其它类。
Class<?> aClass = Class.forName(“com.ajun.Cat”);
aClass 就是 Class 类的一个实例对象;对应着 Cat 类。
-
Class 类的实例对象不是 new 出来的,是由系统创建的
aClass 是通过 Class.forName(“”) 得到的
-
对于某个类(如:Cat)的 Class 类对象,在内存中只有一份,因为类只加载一次
-
每个类的实例都会记得自已是由哪个 Class 所生成
-
通过 Class 的 API 可以完整地得到一个类的结构
Method、Field、Constructor等
-
Class 对象是存放在堆里的
-
类的字节码二进制数据,是放在方法区的 (元空间)
五、Class 类常用方法
-
forName()
返回指定类名 className 的 Class 对象
static Class<?> forName(String className)
String classAllPath = "com.ajun.Cat"; Class<?> cls = Class.forName(classAllPath); System.out.println(cls);//class com.ajun.Cat cls对象所属类型 System.out.println(cls.getClass());//class java.lang.Class cls的运行类型
-
getClass()
获取运行时对象的类
继承自Object对象的方法
-
getPackage()
返回对象所在的包。类型为 Package
Package getPackage()
-
getName()
返回名称 (字符串)
String getName()
System.out.println(cls.getPackage());//package com.ajun 包(package) System.out.println(cls.getPackage().getName());//com.ajun 包名(字符串) System.out.println(cls.getName());//com.ajun.Cat 全限定类名(字符串)
-
newInstance()
创建对象实例
Cat cat = (Cat)cls.newInstance(); System.out.println(cat);//cat.toString()
-
getField()
获取类字段属性 (public型)
Field getField(String name)
Field name = cls.getField("name");//字段属性 (Field表示字段类) System.out.println(name);//public java.lang.String com.ajun.Cat.name System.out.println(name.get(cat));//得到字段值 【字段属性对象.get(类实例)】
- 给属性赋值
name.set(cat,"喵喵");//【字段属性对象.set(类实例,新属性值)】 System.out.println(name.get(cat));//喵喵
-
getFields()
获取类的所有字段属性 (public型)
Field[] getFields()
Field[] fields = cls.getFields(); for (Field field : fields) { System.out.println(field.getName()); }
-
getMethod()
获取类方法
Method getMethod(String name, Class<?>... parameterTypes)
Method sleep = cls.getMethod("sleep");//获取方法 (Method表示方法的类) System.out.println(sleep);//public void com.ajun.Cat.sleep() sleep.invoke(cat);//执行类方法 【方法类对象.invoke(类实例)】
六、获取class的六种方式
1、Class.forName()
String classAllPath = "com.ajun.Cat";
Class<?> cls = Class.forName(classAllPath);
多用于配置文件,读取类全路径,加载类
可以在编译阶段使用
2、类.class
Class<Cat> catClass = Cat.class;
多用于参数传递
可以在加载阶段使用
3、对象.getClass()
Cat cat1 = new Cat("abc");
Class aClass = cat1.getClass();
有实例对象
可以在运行阶段使用
4、类加载器对象.loadClass()
String classAllPath1 = "com.ajun.Cat";
ClassLoader classLoader = cat.getClass().getClassLoader();
Class<?> aClass1 = classLoader.loadClass(classAllPath1);
5、基本数据类型.class
Class<Integer> integerClass = int.class;
Class<Boolean> booleanClass = boolean.class;
Class<Character> characterClass = char.class;
6、基本数据类型包装类.TYPE
Class<Integer> type = Integer.TYPE;
Class<Boolean> type1 = Boolean.TYPE;
Class<Character> type2 = Character.TYPE;
七、哪些类有class对象
八、类加载
1、分类
2、加载时机
加载时机 | 加载类型 |
---|---|
创建对象时(new) | 静态加载 |
当子类被加载时,父类也加载 | 静态加载 |
调用类中的静态成员时加载 | 静态加载 |
反射加载 | 动态加载 |
3、类加载步骤
3.1、概述
加载阶段 和 链接阶段 都是由JVM完成的,程序员无法干预,而初始化阶段程序员可以干预(程序员可以定义静态成员)
3.2、加载阶段
将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成。
3.3、连接阶段
将类的二进制数据合并到JRE中
1、验证
2、准备
//属性-成员变量-字段 三个叫法一样
/**
* n1 是实例属性,不是静态变量,和类没有关系,因此在准备阶段,是不会分配内存
* n2 是静态变量,分配内存,赋默认值 0,而不是20
* n3 是常量在编译期间,则会直接被编译器优化,然后给分配了内存并赋了初值30,在准备阶段赋与给定值
*/
public int n1 = 10; //实例变量
public static int n2 = 20; //静态变量
public static final int n3 = 30; //常量
3、解析
- 符号引用:即一个字符串,这个字符串给出了能够唯一性识别一个方法,一个变量,一个类的相关信息
- 直接引用:可以理解为一个内存地址,或者一个偏移量。比如类方法,类变量的直接引用是指向方法区的指针。在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用
如:调用方法hello(),这个方法的地址是1234567,那么hello就是符号引用,1234567就是直接引用。
3.4、初始化阶段
JVM负责对类进行初始化。这里主要是指静态成员
clinit:class init
加载类的安全机制
这是设计模式中,用静态内部类实现单例模式的原理
九、获取类的结构信息
1、Class 类
Class类方法 | 返回 | 说明 |
---|---|---|
getName() | String | 获取全类名 |
getSimpleName() | String | 获取简单类名 |
getField() | Field | 获取某个public属性(含父类) |
getFields() | Field[] | 获取所有public属性(含父类) |
getDeclaredField() | Field | 获取本类的某个属性 (四种类型都可以) (public、protected、private、默认) |
getDeclaredFields() | Field[] | 获取所有本类属性(四种类型) |
getMethod() | Method | 获取某个public方法(含父类) |
getMethods() | Methods[] | 获取所有public方法(含父类) |
getDeclaredMethod() | Method | 获取本类的某个方法 (四种类型都可以) |
getDeclaredMethods() | Methods[] | 获取所有本类方法(四种类型) |
getConstructor() | Constructor | 获取本类的某个public构造器 |
getConstructors() | Constructor[] | 获取本类的所有public构造器 |
getDeclaredConstructors() | Constructor[] | 获取本类的所有构造器(四种类型) |
getPackage() | Package | 获取包信息 |
getSuperClass() | Class | 获取父类信息 |
getInterfaces() | Class[] | 获取接口信息 |
getAnnotations() | Annotation[] | 获取注解信息 |
2、Field 类
3、Method 类
4、Constructor 类
十、创建对象
创建之前需要先获得类的Class对象
- 实体类
package com.ajun;
/**
* @author ajun
* Date 2021/6/26
* @version 1.0
*/
@SuppressWarnings({"all"})
public class User {
public String name = "ajun";
private int age = 30;
//无参构造器
public User() {
}
//有参构造器
public User(String name) {
this.name = name;
}
//private构造器
private User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Class<?> aClass = Class.forName("com.ajun.User");//先得到类的Class对象
1、调用 public 无参构造器
Object u1 = aClass.newInstance();
2、调用 public 有参构造器
//先获得构造器对象
Constructor<?> constructor = aClass.getConstructor(String.class);
//调用构造器对象的 newInstance() 方法,并赋值
Object u2 = constructor.newInstance("aaa");
3、调用非 public 构造器
无参有参类似于1、2
//先获得构造器对象。通过 getDeclaredConstructor() 可以得到任何类型的构造器,包括私有的(private)
Constructor<?> constructor1 = aClass.getDeclaredConstructor(String.class, int.class);
//对构造器实施爆破(突破私有限制)
constructor1.setAccessible(true);
//调用构造器对象的 newInstance() 方法,并赋值
Object u3 = constructor1.newInstance("bbb", 50);
十一、操作类中属性
- Class 对象
Class<?> aClass = Class.forName("com.ajun.User");//先得到类的Class对象
1、public 属性
//得到 public 属性类
Field name = aClass.getField("name");
//得到 User 类的 name 属性值,参数为 User 对象
String nameValue = name.get(u1);
//设置 name 值。参数一:User对象;参数二:name 值
name.set(u1,"adafafaf");
如果属性是 static 的,在设置时类对象可以是 null
name.set(null,“aafafl”);
2、非 public 属性
//先获得属性对象。通过 getDeclaredField() 可以得到任何类型的属性,包括私有的(private)
Field age = aClass.getDeclaredField("age");
//对属性对象实施爆破(突破私有限制)
age.setAccessible(true);
age.set(u1,18);
int ageValue = age.get(u1);
十二、获取方法及使用
- Class 对象
Class<?> aClass = Class.forName("com.ajun.User");//先得到类的Class对象
1、public 方法
//public 静态
public static String m1(String str){
return "方法1"+str;
}
//获得方法类。参数一:方法名称;参数二:方法参数类型
Method m1 = aClass.getMethod("m1",String.class);
//执行方法。参数一:类对象;参数二:方法实参
m1.invoke(u1,"测试");
//静态方法的类对象可以为 null
m1.invoke(null,"测试");
2、非 public 方法
//私有
private String m2(){
return "方法2";
}
//先获得方法对象。通过 getDeclaredMethod() 可以得到任何类型的方法,包括私有的(private)
Method m2 = aClass.getDeclaredMethod("m2");
//对方法对象实施爆破(突破私有限制)
m2.setAccessible(true);
//执行方法
m2.invoke(u1);