反射
反射的主要类
-
java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的Class类对象,通过这个类,可以得到类信息,在堆中的Class对象实际上包含了,类的所有信息,也就是把这个类加载到了堆中,然后我们可以通过对应方法得到这个Class类的实例
//(1) 加载一个class类,返回一个Class类型的对象,Class就是一个类,类名就是Class Class aClass = Class.forName(classfullpath); //(2) 通过aClass对象,得到加载的类 com.hsp.Cat 的对象实例 Object o = aClass.newInstance(); //(3) 通过 aClass 得到加载的类 com.hsp.Cat 的 methodName"hi" 的方法对象 // 即: 在反射中,可以把方法视为对象(万物皆对象,什么都可以是对象)
-
java.lang.reflect.Method:代表类的方法
Method method = aClass.getMethod(methodName); //通过 method 这个Method(方法对象) 来实现调用Cat也就是加载类下的方法 method.invoke(o); // 传统方法调用对象的方法为,对象.方法() ,而反射机制则是 方法.invoke(对象)
-
java.lang.reflect.Field:代表类的成员变量(属性),Field对象表示某个类的,对应成员变量(属性) Field.get(对象), 可以获得对应属性
//java.lang.reflect.Field: 代表类的成员变量(属性),Field对象表示某个类的,对应成员变量(属性) //getField不能得到私有的属性 Field name = aClass.getField("name"); System.out.println(name.get(o));
-
java.lang.reflect.Constructor:代表类的构造方法
// ()括号中可以指定构造器参数类型,返回对应构造器 Constructor constructor = aClass.getConstructor(); //返回无参构造器 System.out.println(constructor); Constructor constructor1 = aClass.getConstructor(String.class);//这里传入的String.class就是String的Class对象 System.out.println(constructor1);
类加载的三个阶段
-
代码/编译阶段
在这个阶段,java会编译源码文件,形成.class的字节码文件,其中包含类的信息
-
Class类阶段(加载阶段)
在这个阶段会把类对象加载到堆中,在运行阶段调用了new 关键字创建对象时,就会先加载类的Class类对象,然后再创建对象实例,在Class类对象中我们的属性、方法、构造器都是一个个对象,如:属性是Field对象,构造器是 Constructor对象,成员方法是 Method对象
在普通的过程中我们new 一个对象后的到对象实例,这个实例和堆中加载的对象是有关联的,也就是该对象知道它是属于哪个Class类对象
在反射机制中我们得到的“Class”对象,其中的方法与加载在堆中这个对象名是相同的,但无法直接调用,而是需要使用 “对象.方法名”的方式,来获得对应的,属性、方法、构造器等的对象来,调用类中的成员
个人理解:
-
在调用new 和Class.forName时都会通过ClassLoader.loadClass()加载一个Class类对象,但只会加载一次,如果先new以后,ClassforName就不会再调用ClassLoader.loadClass()
-
在这个Class类实例中有,创建类的成员信息,并且被做成了一个个对象,可以通过对应方法来获取实例
-
Class类对象只会被系统创建,无法通过new来创建
-
-
Runtime运行阶段
此阶段主要运行我们写的程序
-
Runtime运行阶段
这个就是我们运行时的代码,会根据我们写的代码,来运行相关的程序
反射机制的优点和缺点
优点:可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑
缺点:使用反射基本是解释执行,对执行速度有影响
动态加载和静态加载
-
静态加载:编译时加载相关类,如果没有则报错,依赖性太强
-
动态加载:运行时加载需要的类,如果运行时不用该类,即使不存在该类,则不报错,降低了依赖性
-
类加载的时机
-
当创建对象时(new) //静态加载
-
当子类被加载时 //静态加载
-
调用类中的静态成员时 //静态加载
-
通过反射 //动态加载
-
-
类加载的流程图
-
java源码通过javac编译成字节码文件
com.Cat.java --》com.Cat.class
字节码文件中存放着类的各种信息,比如访问修饰符,方法代码,方法名,变量名等
-
字节码文件通过java运行
-
类加载:
-
加载(Loading) :将类的class文件读入内存,并为之创建一个java.lang.Class对象,此过程有类加载器完成
-
连接(Linking): //将类的二进制数据合并到JRE中
-
验证 (verification) 对文件进行安全校验
-
准备(Preparation) 完成默认初始化,如静态变量的默认初始化,分配内存并进行默认初始化(对应数据类型的默认初始值,如0、null、false),这些变量所使用的内训都将在方法区中进行分配
-
实例变量不会分配内存
-
静态变量如果不是final修饰,会分配成默认的初始值
-
如果用final修饰,那么再分配过后会无法改变,所以final的静态变量值就是直接定义的值
-
-
解析(Resolution) 将符号引用转换为直接引用,符号引用主要是占位
-
-
初始化(initialization) 完成显示初始化,JVM负责对类进行初始化,主要指静态成员
-
此阶段才真正开始执行类中定义的java程序代码,此阶段实质性<clinit>()方法的过程
-
<clinit>方法是有编译器按语句在源文件中出现的顺序,一次自动手机类中所有静态变量的赋值动作和静态代码块中的语句,惊醒合并
-
虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确的加锁、同步,如果多个线程同时初始化一个类,那么只会有一个线程取执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕
-
-
-
以上两个过程是有jvm虚拟机来控制的
-
类加载后,内存中:
-
方法区:存在类的字节码二进制数据,其中包括了,定义的方法,访问权限等描述信息
-
堆区:类的class对象,两者之间还有引用的关系,这里面是一些数据结构,是程序访问的入口
-
Class的常用方法
- 1 getName():返回String形式的该类的名称。
- 2 newInstance():根据某个Class对象产生其对应类的实例,它调用的是此类的默认构造方法(没有默认无参构造器会报错)
-
3 getClassLoader():返回该Class对象对应的类的类加载器。
-
4 getSuperClass():返回某子类所对应的直接父类所对应的Class对象
-
5 isArray():判定此Class对象所对应的是否是一个数组对象
-
6 getComponentType() :如果当前类表示一个数组,则返回表示该数组组件的 Class 对象,否则返回 null。
-
7 getConstructor(Class[]) :返回当前 Class 对象表示的类的指定的公有构造子对象。
-
8 getConstructors() :返回当前 Class 对象表示的类的所有公有构造子对象数组。
-
9 getDeclaredConstructor(Class[]) :返回当前 Class 对象表示的类的指定已说明的一个构造子对象。
-
10 getDeclaredConstructors() :返回当前 Class 对象表示的类的所有已说明的构造子对象数组。
-
11 getDeclaredField(String) :返回当前 Class 对象表示的类或接口的指定已说明的一个域对象。
-
12 getDeclaredFields() :返回当前 Class 对象表示的类或接口的所有已说明的域对象数组。
-
13 getDeclaredMethod(String, Class[]) :返回当前 Class 对象表示的类或接口的指定已说明的一个方法对象。
-
14 getDeclaredMethods() :返回 Class 对象表示的类或接口的所有已说明的方法数组。
-
15 getField(String) :返回当前 Class 对象表示的类或接口的指定的公有成员域对象。
-
16 getFields() :返回当前 Class 对象表示的类或接口的所有可访问的公有域对象数组。
-
17 getInterfaces() :返回当前对象表示的类或接口实现的接口。
-
18 getMethod(String, Class[]) :返回当前 Class 对象表示的类或接口的指定的公有成员方法对象。
-
19 getMethods() :返回当前 Class 对象表示的类或接口的所有公有成员方法对象数组,包括已声明的和从父类继承的方法。
-
20 isInstance(Object) :此方法是 Java 语言 instanceof 操作的动态等价方法。
-
21 isInterface() :判定指定的 Class 对象是否表示一个接口类型
-
22 isPrimitive() :判定指定的 Class 对象是否表示一个 Java 的基类型。
-
23 newInstance() :创建类的新实例
通过反射获取类的结构信息
Field类
-
getModifirers: 以int形式返回修饰符
[说明:默认修饰符 是0,public是1,private 是2,protected 是3,static 是8,final 是16]
-
getType:以Class形式返回类型
-
getName:返回属性名
Method类
-
getModifirers: 以int形式返回修饰符
[说明:默认修饰符 是0,public是1,private 是2,protected 是3,static 是8,final 是16]
-
getReturnType:以Class形式获取 返回类型
-
getName:返回方法名
-
getParamterTypes:以Class[]返回参数类型数组
Constructor
-
getModifirers: 以int形式返回修饰符
[说明:默认修饰符 是0,public是1,private 是2,protected 是3,static 是8,final 是16]
-
getName:返回构造器名,全类名
-
getParameterTypes:以Class[]返回参数类型数组
通过方法创建对象
-
方式一:调用类中的public修饰的无参构造器
-
方式二:调用类中的指定构造器
-
Class类相关方法
-
newInstance:调用类中的无参构造器,获取对应类的对象
-
getConstructor(Class....clazz):根据参数列表,获取对应的public构造器对象
-
getDecalaredConstructor(Class....clazz):根据参数列表,获取对应的构造器对象
-
-
Constructor类相关方法
-
setAccessible:暴破
-
newInstance(Object...obj):调用构造器
import java.lang.reflect.Constructor; /** * 通过反射机制创建实例 */ public class ReflecCreateInstance { public static void main(String[] args) throws Exception{ //1.先获取到User类的Class类对象 Class<?> aClass = Class.forName("com.hsp.User"); //2.通过public的无参构造器创建实例 Object o = aClass.newInstance(); System.out.println(o); //3.通过public的有参参构造器创建实例 Constructor<?> constructor = aClass.getConstructor(String.class); Object o1 = constructor.newInstance("aa"); System.out.println(o1); //4.通过非public的有参构造器创建实例 Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(int.class, String.class); declaredConstructor.setAccessible(true);// 暴破【暴力破解】,使用反射可以访问private构造器/方法/属性 Object o2 = declaredConstructor.newInstance(100, "垃圾"); System.out.println(o2); } } class User{ private int age; private String name; public User() { } public User(String name) { this.name = name; } private User(int age, String name) { this.age = age; this.name = name; } @Override public String toString() { return "User{" + "age=" + age + ", name='" + name + '\'' + '}'; } }
-
通过反射访问类中的成员
-
根据属性名获取Field对象
Field f=clazz对象.getDeclareField(属性名);
-
暴破:f.setAccessible(true); //f是Field
-
访问
f.set(o, 值);//o表示对象
f.set(f.get(o));//o表示对象
-
注意:如果是静态属性,则set和get中的参数o,可以写成null
import java.lang.reflect.Field; /** * 通过反射访问类中的成员 */ public class ReflecAccessProperty { public static void main(String[] args) throws Exception{ //1. 得到Student类对应的Class对象 Class<?> aClass = Class.forName("com.hsp.Student"); //2. 创建对象 Object o = aClass.newInstance(); System.out.println(o.getClass()); //3. 使用反射得到age属性 对象 Field age = aClass.getField("age"); Object o1 = age.get(o); System.out.println(o1); //4. 使用反射操作name属性 Field name = aClass.getDeclaredField("name"); name.setAccessible(true); //Object o2 = name.get(o); Object o2 = name.get(null); //因为name是static属性,因此 o 也可以写成null System.out.println(o2); } } class Student{ public int age; private static String name; @Override public String toString() { return "Student{" + "age=" + age + "name" +name+ '}'; } }
通过反射访问类中的方法
-
根据方法名和参数列表获取Method方法对象:Method m=clazz.getDeclaredMethod(方法名,XX.class);//得到本类的所有方法
-
获取对象:Object o = clazz.newInstance();
-
暴破:m.setAccessible(true);
-
访问:Object returnValue = m.invoke(o,实参列表);
-
注意:如果是静态方法,则invoke的参数o,可以写成null!
package com.hsp;
import java.lang.reflect.Method;
/**
* 通过反射调用方法
*/
public class ReflecAccessMethod {
public static void main(String[] args) throws Exception{
//1. 得到Boss类对应的Class对象
Class<?> aClass = Class.forName("com.hsp.Boss");
//2. 创建对象
Object o = aClass.newInstance();
//3. 调用public的hi方法
Method hi = aClass.getMethod("hi",String.class);
hi.invoke(o,"sb");
//4. 调用private static的say方法
Method say = aClass.getDeclaredMethod("say", int.class, String.class, char.class);
say.setAccessible(true);
Object invoke = say.invoke(o, 1, "2", '3');//在反射中,如果方法有返回值,同一返回Object
System.out.println(invoke);
//因为是静态的还可以把传入的对象置空
//System.out.println(say.invoke(null,1,"2",'3'));
}
}
class Boss {
public int age;
public static String name;
private static String say(int n, String s, char c) {
return n + " " + s + " " + c;
}
public void hi(String s) {
System.out.println("hi " + s);
}
}
小结:如果要访问非public的成员一定要使用含有Declared的方法,如果成员设置为私有,那么还需要使用.setAccessible(true),暴破访问,