何为反射?
反射(Reflection),是指Java程序具有在运转期分析类以及修正其本身状况或行为的才能
。
浅显点说 便是 经过反射咱们能够动态地获取一个类的一切特点和办法,还能够操作这些办法和特点。
实例的创立
一般咱们创立一个方针实例Person zhang = new Person();
虽然是简简单单一句,但JVM内部的完成进程是杂乱的:
- 将硬盘上指定方位的Person.class文件加载进内存
- 履行main办法时,在栈内存中拓荒了main办法的空间(压栈-进栈),然后在main办法的栈区分配了一个变量zhang。
- 履行new,在堆内存中拓荒一个 实体类的 空间,分配了一个内存首地址值
- 调用该实体类对应的结构函数,进行初始化(假如没有结构函数,Java会补上一个默许结构函数)。
- 将实体类的 首地址赋值给zhang,变量zhang就引用了该实体。(指向了该方针)
其中上图过程1 Classloader(类加载器) 将class文件加载到内存中详细分为3个阶段:加载、衔接、初始化
而又在 加载阶段,类加载器会将类对应的.class文件中的二进制字节流读入到内存中,将这个字节流转化为办法区的运转时数据结构,然后在堆区创立一个 java.lang.Class方针(类相关的信息),作为对办法区中这些数据的拜访进口
拓宽:
mp.weixin.qq.com/s/v91bqRiKD… mp.weixin.qq.com/s/tsbDfyYLq…
然后再经过类的实例来执操作类的办法和特点,比方zhang.eat(), zhang.getHeight()
等等
假如咱们运用反射的话,咱们需求拿到该类Person的Class方针,再经过Class方针来操作类的办法和特点或许创立类的实例
Class personClass = Person.class;//这边仅仅举一个比方,获取class方针的多种办法,本文后面再渐渐道来
Object person = personClass.newInstance();
咱们能够发现 经过new创立类的实例和反射创立类的实例,都绕不开.class文件 和 Class类的。
.class文件
首先咱们得先了解一下 什么是.class文件 举个简单的比方,创立一个Person类:
public class Person {
/**
* 状况 or 特点
*/
String name;//姓名
String sex;//性别
int height;//身高
int weight;//体重
/**
* 行为
*/
public void sleep(){
System.out.println(this.name+"--"+ "睡觉");
}
public void eat(){
System.out.println("吃饭");
}
public void Dance(){
System.out.println("跳舞");
}
}
咱们履行javac指令,编译生成Person.class文件 然后咱们经过vim 16进制 打开它
#打开file文件
vim Person.class
#在指令形式下输入.. 以16进制显示
:%!xxd
#在指令形式下输入.. 切换回默许显示
:%!xxd -r
不同的操作系统,不同的 CPU 具有不同的指令集,JAVA能做到渠道无关性,依靠的便是 Java 虚拟机。 .java源码是给人类读的,而class字节码是给JVM虚拟机读的,计算机只能识别 0 和 1组成的二进制文件,所以虚拟机便是咱们编写的代码和计算机之间的桥梁。 虚拟机将咱们编写的 .java 源程序文件编译为 字节码 格局的 .class 文件,字节码是各种虚拟机与一切渠道统一运用的程序存储格局,class文件首要用于解决渠道无关性的中间文件
Person.class文件 包含Person类的一切信息
Class类
咱们来看下jdk的官方api文档对其的界说:
Class类的类表明正在运转的Java应用程序中的类和接口。 枚举是一种类,一个注释是一种界面。 每个数组也归于一个反映为类方针的类,该方针由具有相同元素类型和维数的一切数组同享。 原始Java类型( boolean , byte , char , short , int , long , float和double ),和关键字void也表明为类方针。 类没有公共结构函数。 相反, 类方针由Java虚拟机自动构建,由于加载了类,而且经过调用类加载器中的defineClass办法。。
java万物皆是Class类
咱们来看下Class类的源码,源码太多了,挑了几个要点:
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
private static final int ANNOTATION= 0x00002000;
private static final int ENUM = 0x00004000;
private static final int SYNTHETIC = 0x00001000;
private static native void registerNatives();
static {
registerNatives();
}
/*
* Private constructor. Only the Java Virtual Machine creates Class objects.
* This constructor is not used and prevents the default constructor being
* generated.
*/
private Class(ClassLoader loader) { //私有化的 结构器
// Initialize final field for classLoader. The initialization value of non-null
// prevents future JIT optimizations from assuming this final field is null.
classLoader = loader;
}
...
// reflection data that might get invalidated when JVM TI RedefineClasses() is called
private static class ReflectionData<T> {
volatile Field[] declaredFields;//字段
volatile Field[] publicFields;
volatile Method[] declaredMethods;//办法
volatile Method[] publicMethods;
volatile Constructor<T>[] declaredConstructors;//结构器
volatile Constructor<T>[] publicConstructors;
// Intermediate results for getFields and getMethods
volatile Field[] declaredPublicFields;
volatile Method[] declaredPublicMethods;
volatile Class<?>[] interfaces;//接口
// Value of classRedefinedCount when we created this ReflectionData instance
final int redefinedCount;
ReflectionData(int redefinedCount) {
this.redefinedCount = redefinedCount;
}
}
...
//注释数据
private volatile transient AnnotationData annotationData;
private AnnotationData annotationData() {
while (true) { // retry loop
AnnotationData annotationData = this.annotationData;
int classRedefinedCount = this.classRedefinedCount;
if (annotationData != null &&
annotationData.redefinedCount == classRedefinedCount) {
return annotationData;
}
// null or stale annotationData -> optimistically create new instance
AnnotationData newAnnotationData = createAnnotationData(classRedefinedCount);
// try to install it
if (Atomic.casAnnotationData(this, annotationData, newAnnotationData)) {
// successfully installed new AnnotationData
return newAnnotationData;
}
}
}
...
咱们能够发现Class也是类,是一种特殊的类,将咱们界说一般类的共同的部分进行笼统,保存类的特点,办法,结构办法,类名、包名、父类,注解等和类相关的信息。 Class类的结构办法是private,只要JVM能创立Class实例,咱们开发人员 是无法创立Class实例的,JVM在结构Class方针时,需求传入一个类加载器。 类也是能够用来存储数据的,Class类就像 一般类的模板 一样,用来保存“类一切相关信息”的类。
咱们来持续看这个利用反射的比方:Class personClass = Person.class;
由于JVM为加载的 Person.class创立了对应的Class实例,并在该实例中保存了该 Person.class的一切信息,因而,假如获取了Class实例(personClass ),咱们就能够经过这个Class实例获取到该实例对应的Person类的一切信息。
反射的运用
获取Class实例4种办法
- 经过方针调用
getClass()
办法来获取
Person p1 = new Person();
Class c1 = p1.getClass();
像这种已经创立了方针的,再去进行反射的话,有点多此一举。 一般是用于传过来的是Object类型的方针,不知道详细是什么类,再用这种办法比较靠谱
类名.class
Class c2 = Person.class;
这种需求提前知道导入类的包,程序功能更高,比较常用,经过此办法获取 Class 方针**,Person类不会进行初始化**
- 经过 Class 方针的
forName()
静态办法来获取,最常用的一种办法
Class c3 = Class.forName("com.zj.demotest.domain.Person");
这种只需传入类的全途径**,Class.forName会进行初始化initialization过程,即静态初始化(会初始化类变量,静态代码块)。**
- 经过类加载器方针的
loadClass()
办法
public class TestReflection {
public static void main(String[] args) throws ClassNotFoundException {
Person p1 = new Person();
Class c1 = p1.getClass();
Class c2 = Person.class;
Class c3 = Class.forName("com.zj.demotest.domain.Person");
//第4中办法,类加载器
ClassLoader classLoader = TestReflection.class.getClassLoader();
Class c4 = classLoader.loadClass("com.zj.demotest.domain.Person");
System.out.println(c1.equals(c2));
System.out.println(c2.equals(c3));
System.out.println(c3.equals(c4));
System.out.println(c1.equals(c4));
}
}
loadClass的源码:
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
loadClass 传入的第二个参数是”false”,因而它不会对类进行衔接这一过程,依据类的生命周期咱们知道,假如一个类没有进行验证和预备的话,是无法进行初始化进程的,即不会进行类初始化,静态代码块和静态方针也不会得到履行
咱们将c1,c2,c3,c4进行 equals 比较
System.out.println(c1.equals(c2));
System.out.println(c2.equals(c3));
System.out.println(c3.equals(c4));
System.out.println(c1.equals(c4));
成果:
true
true
true
true
由于Class实例在JVM中是仅有的,所以,上述办法获取的Class实例是同一个实例,一个类在 JVM 中只会有一个 Class 实例
Class类常用的API
日常开发的时分,咱们一般运用反射是为了 创立类实例(方针)、反射获取类的特点和调用类的办法
getName() | 取得类的完好名字 |
---|---|
getFields() | 取得类的public类型的特点 |
getDeclaredFields() | 取得类的一切特点。包含private 声明的和继承类 |
getMethods() | 取得类的public类型的办法 |
getDeclaredMethods() | 取得类的一切办法。包含private 声明的和继承类 |
getMethod(String name, Class[] parameterTypes) | 取得类的特定办法,name参数指定办法的名字,parameterTypes 参数指定办法的参数类型。 |
getConstructors() | 取得类的public类型的结构办法 |
getConstructor(Class[] parameterTypes) | 取得类的特定结构办法,parameterTypes 参数指定结构办法的参数类型 |
newInstance() | 经过类的不带参数的结构办法创立这个类的一个方针 |
getSuperClass() | 用于返回表明该 Class 表明的任何类、接口、原始类型或任何 void 类型的超类的Class(即父类)。 |
… | … |
咱们这边就不全部打开讲了,挑几个要点解说一下
创立方针
- 调用class方针的
newInstance()
办法
Class c1 = Class.forName("com.zj.demotest.domain.Person");
Person p1 = (Person) c1.newInstance();
p1.eat();
成果:
吃饭
留意:Person类必须有一个无参的结构器且类的结构器的拜访权限不能是private
- 运用指定结构办法
Constructor
来创立方针
假如咱们非得让Person类的无参结构器设为private呢,咱们能够获取对应的Constructor来创立方针
Class c1 = Class.forName("com.zj.demotest.domain.Person");
Constructor<Person> con = c1.getDeclaredConstructor();
con.setAccessible(true);//答应拜访
Person p1 = con.newInstance();
p1.eat();
成果:
吃饭
留意:setAccessible()办法能在运转时压制Java言语拜访控制查看(Java language access control checks),从而能任意调用被私有化保护的办法、域和结构办法。 由此咱们能够发现单例形式不再安全,反射可破之 !
拜访特点
Field getField(name) | 依据字段名获取某个public的field(包含父类) |
---|---|
Field getDeclaredField(name) | 依据字段名获取当时类的某个field(不包含父类) |
Field[] getFields() | 获取一切public的field(包含父类) |
Field[] getDeclaredFields() | 获取当时类的一切field(不包含父类) |
咱们来看一个比方:
public class TestReflection3 {
public static void main(String[] args) throws Exception {
Object p = new Student("li hua");
Class c = p.getClass();
Field f = c.getDeclaredField("name");//获取特点
f.setAccessible(true);//答应拜访
Object val= f.get(p);
System.out.println(val);
}
static class Student {
private String name;
public Student(String name) {
this.name = name;
}
}
}
成果:
li hua
咱们能够发现反射能够破坏类的封装
调用办法
Method getMethod(name, Class…) | 获取某个public的Method(包含父类) |
---|---|
Method getDeclaredMethod(name, Class…) | 获取当时类的某个Method(不包含父类) |
Method[] getMethods() | 获取一切public的Method(包含父类) |
Method[] getDeclaredMethods() | 获取当时类的一切Method(不包含父类) |
咱们来看一个比方:
public class TestReflection4 {
public static void main(String[] args) throws Exception {
//获取私有办法,需求传参:办法名和参数
Method h = Student.class.getDeclaredMethod("setName",String.class);
h.setAccessible(true);
Student s1 =new Student();
System.out.println(s1.name);
//传入方针方针,调用对应的办法
h.invoke(s1,"xiao ming");
System.out.println(s1.name);
}
static class Student {
private String name;
private void setName(String name) {
this.name = name;
}
}
}
成果:
null
xiao ming
咱们发现获取办法getMethod()时,需求传参 办法名和参数
这是由于.class文件中一般有不止一个办法,获取办法getMethod()时,会去调用searchMethods办法循环遍历一切Method,然后依据 办法名和参数类型 找到仅有契合的Method返回。
咱们知道类的办法是在JVM的办法区中 ,当咱们new 多个方针时,特点会别的拓荒堆空间存放,而办法只要一份,不会额定消耗内存,办法就像一套指令模板,谁都能够传入数据交给它履行,然后得到对应履行成果。
method.invoke(obj, args)
时传入方针方针,即可调用对应方针的办法
假如获取到的Method表明一个静态办法,调用静态办法时,无需指定实例方针,所以invoke办法传入的第一个参数永远为null, method.invoke(null, args)
那假如 办法重写了呢,反射依旧遵从 多态 的原则。
反射的应用场景
假如平常咱们仅仅写业务代码,很少会接触到直接运用反射机制的场景,究竟咱们能够直接new一个方针,功能比还反射要高。 但假如咱们是工具结构的开发者,那一定十分熟悉,像 Spring/Spring Boot、MyBatis 等等结构中都大量运用反射机制,反射被称为结构的魂灵 比方:
- Mybatis Plus能够让咱们只写接口,不写完成类,就能够履行SQL
- 开发项目时,切换不同的数据库只需更改装备文件即可
- 类上加上@Component注解,Spring就帮咱们创立方针
- 在Spring咱们只需 @Value注解就读取到装备文件中的值
- 等等
扩展:反射装备文件
咱们来模仿一个装备高于编码的比方
新建my.properties,将其放在resources的目录下
#Person类的包途径
className=com.zj.demotest.domain.Person
methodName=eat
Person类 还是本文 一向用的,在文章的开头有
最后咱们来编写一个测验类
public class TestProp {
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Properties properties = new Properties();
ClassLoader classLoader = TestProp.class.getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("my.properties");// 加载装备文件
properties.load(inputStream);
String className = properties.getProperty("className");
System.out.println("装备文件中的内容:className="+className);
String methodName = properties.getProperty("methodName");
System.out.println("装备文件中的内容:methodName="+methodName);
Class name = Class.forName(className);
Object object = name.newInstance();
Method method = name.getMethod(methodName);
method.invoke(object);
}
}
成果:
装备文件中的内容:className=com.zj.demotest.domain.Person
装备文件中的内容:methodName=eat
吃饭
紧接着,咱们修正装备文件:
className=com.zj.demotest.domain.Person
methodName=Dance
成果变为:
装备文件中的内容:className=com.zj.demotest.domain.Person
装备文件中的内容:methodName=Dance
跳舞
是不是很方便?
尾语
反射机制是一种功能强大的机制,让Java程序具有在运转期分析类以及修正其本身状况或行为的才能
。
关于特定的杂乱系统编程使命,它是十分必要的,为各种结构供给开箱即用的功能供给了便当,为解耦合供给了保障机制。
可是世事无绝对,反射相当于一系列解释操作,通知 JVM 要做的工作,功能比直接拜访方针要差点(JIT优化后,关于结构来说实践是影响不大的),还会增加程序的杂乱性等(明明直接new一下就能解决的工作,非要写一大段代码)。
本篇文章到这儿就结束啦,很感谢你能看到最后,假如觉得文章对你有帮助,别忘记重视我!更多精彩文章在大众号「小牛呼噜噜 」