示例引出反射
请看下面的问题
- 根据配置文件 re.properties 指定信息,创建Cat对象并调用方法hi。配置文件的信息如下:
ini
classfullpath=com.reflect_.Cat
method=hi
使用现有技术,你能做的吗?
- 这样的需求在学习框架时特别多,即通过外部文件配置,在不修改源码情况下,来控制程序,也符合设计模式的ocp原则(开闭原则:不修改源码,扩容功能)
使用反射解决上面问题
java
public class Start_ {
public static void main(String[] args) throws Exception {
//1.使用Properties 类,可以读写配置文件
Properties properties = new Properties();
properties.load(new FileInputStream( "src\\re.properties"));
String classfullpath = properties.get("classfullpath").toString();//"com.reflect_.Cat"
String methodName = properties.get( "method" ).toString();// "hi"
System.out.println("classfullpath=" + classfullpath);
System.out.println( "methodName=" + methodName);
//2.创建对象,传统的方法,行不通=>反射机制
// new classfullpath();
//3.使用反射机制解决
// (1) 获取class类型的对象(有一个类,类名叫Class。通过forName方法可以获取classfullpath路径类的加载的对象)
Class<?> cls = Class.forName(classfullpath);
// (2) 获取com.reflect_.Cat 的对象实例
// cls.getDeclaredConstructor()获取无参构造器
// cls.getDeclaredConstructors()获取所有无参构造器,返回是一个数组
Object o = cls.getDeclaredConstructor().newInstance();
System.out.println(o.getClass());
//(3)获取方法对象
Method method = cls.getMethod(methodName);
//(4) 通过反射来调用hi方法
method.invoke(o);
}
}
反射机制
反射机制允许程序在执行期借助于ReflectionAPI取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到。
加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为:反射。

反射原理

这张图清晰地展示了Java类从编写到运行的完整生命周期以及反射机制的底层内存模型,整体分为编译、类加载和运行时三个阶段。
- 首先在编译阶段 ,
Cat.java源码经过javac编译后,生成包含字段、方法描述符以及常量池(如"招财猫")的二进制字节码文件Cat.class。 - 接着进入类加载阶段 ,类加载器(
ClassLoader)通过loadClass方法按需(懒加载)读取字节码,将真正的类元数据(如字节码指令、方法结构)存入方法区(元空间),同时在堆内存中生成一个代表该类的Class对象作为访问入口,该对象内部持有指向方法区元数据的指针。 - 最后在Runtime运行时阶段 ,当我们实例化
Cat对象时,会在堆中分配内存,该实例的对象头指针会指向堆中的Class对象(即通过getClass()获取的镜像),而实例中的name字段则指向堆内存里的字符串常量池中的"招财猫"。
反射的作用
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时得到任意一个类所具有的成员变量和方法
在运行时调用任意一个对象的成员变量和方法
生成动态代理
反射相关的主要类
java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象java.lang.reflect.Method:代表类的方法, 只能取public修饰的成员java.lang.reflect.Field:代表类的成员变量, 只能取public修饰的成员java.lang.reflect.Constructor:代表类的构造方法
java
public class ReflecTion_ {
public static void main(String[] args) throws Exception {
//1.使用Properties 类,可以读写配置文件
Properties properties = new Properties();
properties.load(new FileInputStream( "src\\re.properties"));
String classfullpath = properties.get("classfullpath").toString();//"com.reflect_.Cat"
String methodName = properties.get( "method" ).toString();// "hi
Class cls = Class.forName(classfullpath);
// 通过无参构造器创建对象
Object o = cls.getConstructor().newInstance();
System.out.println(o.getClass());
// 方法对象
Method method = cls.getMethod(methodName);
method.invoke(o);//执行方法
// 获取对象
Field name = cls.getField("name"); //注意只能取public修饰的成员
System.out.println(name.get(o));//获取对象
// 获取构造器对象
Constructor csc = cls.getConstructor(); //无参构造器
System.out.println(csc);
// 有参构造器 String.class这个是String class对象
Constructor csc1 = cls.getConstructor(String.class);
System.out.println(csc1);
}
}
优点和缺点
-
优点:可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑。
-
缺点:使用反射基本是解释执行,对执行速度有影响.
Class类&类对象
JVM 在运行时为每个加载的类在堆中生成唯一的 Class 对象,对象实例通过对象头中的指针记住自己的 Class。反射的核心,就是利用字符串(Class.forName())或 .class 语法获取这个 Class 对象,然后通过它动态地获取类的结构信息,并在必要时通过 setAccessible(true) 突破访问控制,动态地创建实例、调用方法或修改属性。
-
运行时的类型标识(RTTI) : Java 是多态语言。当你调用
animal.sound()时,JVM怎么知道该调猫叫还是狗叫?靠的就是对象的"类型标识"。每个对象在内存里都有一张"身份证",记录自己属于哪个类。虚拟机利用运行时类型信息选择相应的方法执行。 -
Class类不需要通过new创建对象,而是JVM创建对象的。你不能写new Class(),因为Class的构造器是私有的。它是由 JVM 在加载类时自动在堆中生成的。 -
无论是
new Cat()还是反射constructor.newInstance(),创建实例时,JVM 会先确保这个类已经被ClassLoader加载过了(如果没有,先加载),然后分配内存、调用构造器 -
一个类在内存中只有一份
Class对象,是因为 JVM 的双亲委派模型会缓存加载过的类。 -
每个类的实例都会记得自己是由哪个
Class对象所生成 ,因为JVM 会在对象头(Object Header)里存一个指针,指向堆中的这个 Class 对象。所以你可以通过obj.getClass()顺藤摸瓜找到它。 -
JDK 8以后,Class对象本身存放在堆中,真正的类元数据(字节码指令、字段描述、方法代码等)存放在方法区(元空间 Metaspace) 。堆里的Class对象内部有一个指针,指向方法区里的元数据。 -
反射的核心就在这里。因为
Class对象持有这些元数据,所以你可以调用getDeclaredFields()、getDeclaredMethods()等方法,像剥洋葱一样拿到类的所有细节。
类的实例之所以能进行反射操作(或者动态绑定),是因为对象头里有一个指针(Klass Pointer)指向堆中的 Class 对象;而这个 Class 对象内部又有一个指针指向方法区里的元数据。这样 JVM 就能顺着实例 -> Class 对象 -> 元数据这条链路,找到字段的偏移量和方法的字节码,从而操作属性或调用方法。
一个通俗的类比:
- 实例对象(cat):你本人。
- 对象头指针:你随身带的身份证。
- Class 对象(堆中):派出所的户籍档案柜(只记录你属于哪个街道,具体信息在系统里)。
- 方法区元数据:公安局的系统数据库(真正存着你的姓名、年龄、指纹)。
如果你只是想知道自己叫什么(直接访问 cat.name),你摸摸脑袋就回忆起来了,不需要去查档案。
但如果你要办业务,工作人员(反射)必须拿你的身份证(对象头),去档案柜(Class 对象),再查系统数据库(元数据),才能确认你的所有信息并办理业务。
Class类实际上是一个泛型类。例如,Employee.class的类型是Class<Employee>。没有说明这个问题的原因是:它将已经抽象的概念更加复杂化了。在大多数实际问题中,可以忽略类型参数,而使用原始的Class类。这样 employeeClass.newInstance() 出来的对象直接就是 Employee 类型,不用强转。
class 常用方法
java
public class Class_ {
public static void main(String[] args) throws Exception {
//获取到Car类对应的 CLass对象
Class<?> cls = Class.forName("com.reflect_.Cat");
System.out.println(cls);//显示cls对象,是哪个类的Class对象
System.out.println(cls.getClass());//输出cls运行类型java.lang.Class
//得到包名
System.out.println(cls.getPackage().getName());//包名
//得到全类名
System.out.println(cls.getName());
//通过cls创建对象实例
Cat cat = (Cat)cls.getConstructor().newInstance();
System.out.println(cat);
//只能获取本类及父类 public 字段
Field name = cls.getField("name");
System.out.println(name.get(cat));
//获取本类及父类 public 方法,getMethods也一样
// getDeclaredMethods本类所有方法,不含父类。
Method method = cls.getMethod("hi");
method.invoke(o);//执行方法
//通过反射设置值
name.set(cat, "大脸猫");
System.out.println(name.get(cat));
//获取所有的属性,只能获取本类及父类 public 字段
// getDeclaredFields() 本类所有字段,不含父类。
Field[] fields = cls.getFields();
for (Field field : fields) {
System.out.println(field.get(cat));
}
}
}
获取Class对象的方式
-
前提: 已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法
forName()获取,forName默认会初始化类,可能抛出ClassNotFoundException实例:
Class cls1 =Class.forName( "java.lang.Cat");应用场景: 多用于配着文件,读取类全路径,加载类,一般处于类编译后想要获取Class对象可以使用这种方式。
-
前提: 若已知具体的类,通过类的class 获取,该方式最为安全可靠,程序性能最高
实例:
Class cls2 = Cat.class;应用场景: 多用于参数传递,比如通过反射得到对应构造器对象。一般在类加载阶段,想要获取Class对象可以使用这种方式。
-
前提: 已知某个类的实例,调用该实例的
getClass()方法获取Class对象实例:
Class class=对象.getClass();应用场景: 通过创建好的对象,获取
Class对象。一般处于运行阶段,想要获取Class对象可以使用这种方式。 -
其他方式:
ClassLoader cl =对象.getClass().getClassLoader();loadClass只加载,不初始化Class clazz4 = cl.loadClass("类的全类名"); -
基本数据(
int, char,boolean,float,double,byte,long,short)按如下方式得到Class类对象Class cls =基本数据类型.class -
基本数据类型对应的包装类,可以通过.TYPE得到Class类对象
Class cls =包装类.TYPE
JAVA
public class GetClass_ {
public static void main(String[] args) throws ClassNotFoundException {
//1.通过反射获取类
Class<?> cls1 = Class.forName("com.reflect_.Cat");
System.out.println(cls1);
//2.通过类名.class
Class<Cat> cls2 = Cat.class;
System.out.println(cls2);
//3.通过对象.getClass()
Cat cat = new Cat();
Class cls3 = cat.getClass();
System.out.println(cls3);
//4.通过类加载器获取类的class对象
//(1).先获取类的加载器 cat
ClassLoader classLoader = cat.getClass().getClassLoader();
//(2).通过类加载器获取Class对象
Class<?> cls4 = classLoader.loadClass("com.reflect_.Cat");
System.out.println(cls4);
}
}
哪些类型有class类的对象
-
外部类,成员内部类,静态内部类,局部内部类,匿名内部类
-
interface
-
数组
-
enum
-
annotation
-
基本数据类型
-
void
java
public class AllTypeClass {
public static void main(String[] args) {
Class<String> cls1 = String.class;//外部类
System.out.println(cls1);
Class<Serializable> cls2 = Serializable.class;//接口
System.out.println(cls2);
Class<Integer[]> cls3 = Integer[].class;//数组
System.out.println(cls3);
Class<float[][]> cls4 = float[][].class;//二维数组
System.out.println(cls4);
Class<Deprecated> cls5 = Deprecated.class;//注解//枚举
System.out.println(cls5);
Class<Thread.State> cls6 = Thread.State.class;
System.out.println(cls6);
Class<Long> cls7 = long.class;//基本数据类型
System.out.println(cls7);
Class<Void> cls8 = void.class; //void数据类型
System.out.println(cls8);
Class<Class> cls9 = Class.class;
System.out.println(cls9);
}
}
类加载
反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载。
-
静态加载:编译时就需要加载相关的类,如果没有则报错,依赖性太强。
javapublic static void main(string[] args){ Scanner scanner = new Scanner(System.in); System.out.println("请输入key"); String key = scanner.next(); switch(key) { case "1" : Dog dog = new Dog();//静态加载 break; case "2": System.out.println( "ok " ); break; default: System.out.println( "do nothing.." ); } }如上述代码中Dog类需要在编译的时候就需要加载进去,如果发现没有这个类就会报错。
-
动态/延时加载: 运行时加载需要的类,如果运行时不用该类,即使不存在该类,也不会报错, 降低了依赖性。
javapublic static void main(string[] args){ Scanner scanner = new Scanner(System.in); System.out.println("请输入key"); String key = scanner.next(); switch(key) { case "1" : System.out.println( "1" ); break; case "2": //使用反射动态加载,运行时只有执行到这段代码,才会加载Person类 Class cls = Class.forName( "Person");//运行时加载Person类 Object o = cls.newInstance(); Method m = cls.getMethod( "hi" );m.invoke(o); System.out.println( "ok " ); break; default: System.out.println( "do nothing.." ); } }
类加载时机:
- 当创建对象时(new) 属于静态加载
- 当子类被加载时,父类也加载 属于静态加载
- 调用类中的静态成员时 属于静态加载
- 通过反射 属于动态加载
类加载流程图

在加载(Loading)阶段:
JVM 将字节码从不同数据源(class文件、jar包、网络)转化为二进制字节流加载到内存。具体分三步:
- 通过全限定名获取二进制字节流。
- 将字节流代表的静态存储结构转化为方法区的运行时数据结构(元数据)。
- 在堆内存 中生成一个代表该类的
java.lang.Class对象,作为方法区数据的访问入口。
在连接(Linking)阶段进行的操作:
-
验证: 确保Class文件字节流安全且符合规范(文件格式、元数据、字节码、符号引用验证)
-
准备: 为静态变量分配内存并设置默认初始值 (0、null、false)。如果是编译期常量 (
static final且编译时值确定),则直接赋值。实例变量不在此阶段分配内存。javaclass A { //n1 是实例属性,不是静态变量,因此在准备阶段,是不会分配内存 public int n1 = 10; //n2是静态变量,分配内存 n2是默认初始化0,而不是20 public static int n2 = 20; //n3是static final是常量,他和静态变量不一样,因为一旦赋值就不变,所以在准备阶段会直接赋值。 public static final int n3 = 50; } -
解析:将常量池内的符号引用替换为直接引用(内存地址)。
将类的二进制数据合并到 JVM 的运行时数据区中(即方法区和堆)
**初始化阶段:**执行类构造器 <clinit>() 方法,按源码顺序收集静态变量的真实赋值动作和静态代码块,真正赋予程序员设定的初始值。
获取类的结构信息
类的结构信息
java
package reflect_;
import org.junit.Test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectUtil_ {
public static void main(String[] args) {
}
@Test
public void api01() throws ClassNotFoundException {
Class<?> cls = Class.forName("reflect_.Dog");
//getName:获取全类名
System.out.println(cls.getName());
//getSimpleName:获取简单类名
System.out.println(cls.getSimpleName());
//getFields:获取所有public修饰的属性,包含本类以及父类的
Field[] fields = cls.getFields();
for (Field field : fields) {
System.out.println("public修饰的属性"+field);
}
//getDeclaredFields:获取本类中所有属性.
Field[] declaredFields = cls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("本类中所有属性"+declaredField);
}
//getMethods:获取所有public修饰的方法,包含本类以及父类的
Method[] methods = cls.getMethods();
for (Method method : methods) {
System.out.println("public修饰的方法"+method);
}
//getDeclaredMethods:获取本类中所有方法
Method[] declaredMethods = cls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("本类中所有方法"+declaredMethod);
}
//getConstructors:获取所有public修饰的构造器.只包含本类
Constructor<?>[] constructors = cls.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("public修饰的构造器,只包含本类的"+constructor);
}
//getDeclaredconstructors:获取本类中所有构造器.
Constructor<?>[] declaredConstructors = cls.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("本类中所有构造器"+declaredConstructor);
}
//getPackage:以Package形式返回包信息.
System.out.println(cls.getPackage());
//getsuperClass:以Class形式返回父类信息
System.out.println(cls.getSuperclass());
//getInterfaces:以class[]形式返回接口信息
Class<?>[] interfaces = cls.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println("接口信息"+anInterface);
}
//getAnnotations:以Annotation[]形式返回注解信息,注意只返回RUNTIME保留的注解,例如@Override 这种 SOURCE 注解拿不到
Annotation[] annotations = cls.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("注解信息"+annotation);
}
}
}
interface A1{}
interface A2{}
class Animal {
public String hobby;
public Animal() {}
public Animal(String hobby) {
this.hobby = hobby;
}
public void a1(){}
public void a2(){}
}
@Deprecated
class Dog extends Animal implements A1,A2{
public String name;
protected int age;
String job;
private double salary;
public Dog(){}
public Dog(String name) {
this.name = name;
}
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
private Dog(int age) {
this.age = age;
}
public void m1(){}
protected void m2(){}
}
类的属性信息
-
getModifiers: 以int形式返回修饰符,建议用Modifier.toString(field.getModifiers()), 这里说明下:默认修饰符是 0,
public是 1,private是 2,protected是 4,static是 8,final是 16组合使用例如:
public static final = public(1) + static (8) + final(16) =25还有别的修饰符:
synchronized=32、volatile=64、transient=128、native=256、interface=512、abstract=1024等 -
getType:以Class形式返回类型 -
getName:返回属性名
java
@Test
public void api02() throws ClassNotFoundException {
Class<?> cls = Class.forName("reflect_.Dog");
Field[] declaredFields = cls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("本类中所有属性:"+declaredField+"; 属性修饰符的值是:"+Modifier.toString(declaredField.getModifiers())
+"; 属性的类型是:"+declaredField.getType());
}
}
类的方法信息
getModifiers: 以int形式返回修饰符,建议用Modifier.toString(field.getModifiers()), 和类的属性的一样。getReturnType:以Class形式获取返回类型getName:返回方法名getParameterTypes:以Class\[\]返回参数类型数组
JAVA
@Test
public void api03() throws ClassNotFoundException {
Class<?> cls = Class.forName("reflect_.Dog");
//getDeclaredMethods:获取本类中所有方法
Method[] declaredMethods = cls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("本类中所有方法"+declaredMethod+"; 方法修饰符的值是:"+Modifier.toString(declaredMethod.getModifiers())+
"; 方法的返回类型是:"+declaredMethod.getReturnType());
System.out.println("该方法的参数个数是:"+declaredMethod.getParameterCount());
Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println("该方法的参数类型是"+parameterType);
}
}
}
类的构造器信息
getModifiers:以int形式返回修饰符getName:返回构造器名(全类名)getParameterTypes:以Class\[\]返回参数类型数组
反射爆破
通过反射创建对象
-
Class类相关方法newInstance: 调用类中的无参构造器,获取对应类的对象。getConstructor(Class...clazz):根据参数列表,获取对应的public构造器对象getDeclaredConstructor(Class...clazz):根据参数列表,获取对应的所有构造器对象 -
Constructor类相关方法setAccessible:暴破, 使用反射可以访问private构造器/属性/方法。newlnstance(Object...obj): 调用构造器
java
package reflect_;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ReflectCreateInstance {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
//1 先获取Person类的class对象,通过无参构造器创建对象
Class<?> cls = Class.forName("reflect_.Person");
Object o = cls.getConstructor().newInstance();
System.out.println(o);
//2 通过public修饰的的有参构造器创建实例
Constructor<?> cst1 = cls.getConstructor(String.class);
//这里面传递的参数就和Person(String name)一样
System.out.println(cst1.newInstance("小明"));
//3 通过非public的无参构造器创建实例
//3.1 得到构造器对象
Constructor<?> dcst1 = cls.getDeclaredConstructor(String.class, int.class);
Constructor<?> dcst2 = cls.getDeclaredConstructor(String.class, int.class, String.class, double.class);
//3.2 非protected和无修饰的可以直接newInstance
System.out.println(dcst1.newInstance("小花", 18));
System.out.println(dcst2.newInstance("小李", 22, "业务员", 3000));
//3.3 private修饰的需要爆破后才能newInstance
Constructor<?> dcst3 = cls.getDeclaredConstructor(String.class, int.class, String.class);
dcst3.setAccessible(true); //在 JDK 9+ 模块化下可能失败
System.out.println(dcst3.newInstance("小龙", 20, "程序员"));
}
}
class Person{
public String name;
protected int age;
String job;
private double salary;
public Person() {}
public Person(String name) {
this.name = name;
}
protected Person(String name, int age) {
this.name = name;
this.age = age;
}
private Person(String name, int age, String job) {
this.name = name;
this.age = age;
this.job = job;
}
Person(String name, int age, String job, double salary) {
this.name = name;
this.age = age;
this.job = job;
this.salary = salary;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", job='" + job + '\'' +
", salary=" + salary +
'}';
}
}
通过反射操作属性
-
根据属性名获取
Field对象Field f = clazz对象.getDeclaredField(属性名); -
暴破:
f.setAccessible(true);//f 是Field -
属性的操作
f.set(o,值);f.get(o); -
注意:如果是静态属性,则set和get中的参数o,也可以写成null, 因为静态程序不需要对象。
通过反射操作方法
-
根据方法名和参数列表获取Method方法对象:
Method m =clazz.getDeclaredMethod(方法名,XX.class);多个参数后边继续跟xx.class之间用逗号隔开 -
获取对象:
Object o = clazz.newlnstance(); -
暴破:
m.setAccessible(true); -
访问:
Object returnValue = m.invoke(o, 实参列表); -
注意:如果是静态方法,则invoke的参数o,也可以写成null!