Java反射专题笔记总结:一篇文章带你理清反射
Posted 温文艾尔
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java反射专题笔记总结:一篇文章带你理清反射相关的知识,希望对你有一定的参考价值。
一、一个需求引出反射
1.根据配置文件re.properties指定信息,创建Cat对象并调用方法hi
re.properties:
classfullpath=com.hspedu.Cat
method=hi
这样的需求在学习框架的时候特别多,即通过外部文件配置,在不修改源码情况下来控制程序,这符合设计模式的ocp原则(开闭原则:不修改源码,开扩容功能)
利用反射代码实现
package com.hspedu.reflection.question;
import com.hspedu.Cat;
import javax.annotation.Resource;
import java.io.FileInputStream;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* Description
* User:
* Date:
* Time:
*/
public class ReflectionQuestion {
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
properties.load(new FileInputStream("src\\\\re.properties"));
String classfullpath = properties.get("classfullpath").toString();
String method = properties.get("method").toString();
//(1)加载类,返回Class类型的对象clazz
Class<?> clazz = Class.forName(classfullpath);
//(2)获得对象实例
Object o = clazz.newInstance();
//(3)通过cls得到你加载的类com.hspedu.Cat的methodName"hi" 的方法对象
//在反射中,可以把方法视为对象(万物皆对象)
Method declaredMethod = clazz.getDeclaredMethod(method);
//通过方法对象来实现调用方法
declaredMethod.invoke(o);
}
}
如果Cat类中还有一个cry方法,输出cry cat
public void cry(){
System.out.println("cry cat");
}
我们如果想要执行cry方法,只能将cat.hi()方法修改成cat.cry()方法,这样不仅很麻烦而且涉及到修改源码,如果使用反射,我们便可以不修改源码,只修改配置文件便可控制方法的调用
二、反射机制:
1.Java Reflection
- 反射机制允许程序在执行期借助于ReflectionAPI取得任何类的内部信息(比如成员变量没构造器,成员方法等等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到
- 加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息,通过这个对象得到类的结构,这个对象就像一面镜子,透过这个镜子看到类的结构,
2.Java反射机制原理示意图
Java反射机制可以完成以下事情:
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时得到任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的成员变量和方法
- 生成动态代理
Java反射相关的主要类:
- java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor:代表类的构造方法
Field的使用:
//得到name字段的名字
Field fieldName = clazz.getDeclaredField("name");
fieldName.setAccessible(true);
System.out.println("name:"+fieldName.get(o));
Constructor的使用:
//获得无参构造器
Constructor<?> Noconstructor = clazz.getConstructor();
System.out.println("无参constructor:"+Noconstructor);
//获得有参构造器
Constructor<?> Allconstructor = clazz.getConstructor(String.class);
System.out.println("无参constructor:"+Allconstructor);
3.反射优点和缺点:
优点:
可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑
缺点
使用反射基本是解释执行,对执行速度有印象,直接new出对象速度是反射出对象的几十倍
4.传统方式与反射的效率对比:
package com.hspedu.reflection;
import com.hspedu.Cat;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* Description:测试反射的性能和优化方案
* User:
* Date:
* Time:
*/
public class Reflection02 {
public static void main(String[] args) throws Exception {
m1();
m2();
}
public static void m1(){
//普通new
Cat cat = new Cat();
long Newstart = System.currentTimeMillis();
for (int i =0;i<1000000;i++){
cat.hi();
}
long Newend = System.currentTimeMillis();
System.out.println("传统方式耗时:"+(Newend-Newstart));
}
public static void m2() throws Exception{
//反射创建
Properties properties = new Properties();
properties.load(new FileInputStream("src\\\\re.properties"));
String classfullpath = properties.get("classfullpath").toString();
Class<?> clazz = Class.forName(classfullpath);
Method hi = clazz.getMethod("hi");
Cat cat2 = (Cat)clazz.newInstance();
long Refstart = System.currentTimeMillis();
for (int i =0;i<1000000;i++){
hi.invoke(cat2);
}
long Refend = System.currentTimeMillis();
System.out.println("反射方式耗时:"+(Refend-Refstart));
}
}
对反射方式进行优化:
’setAccessible(true)
反射调用优化-关闭访问检查:
Method和Field、Constructor对象都有setAccessible()方法
setAccessible作用是启动和禁用访问安全检查的开关
参数为true表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为false则表示反射的对象执行访问检查
优化之后:
三、Class类
1.Class也是类,因此也继承Object类
Class类图结构
2.Class类对象不是new出来的,而是系统创建的
传统的new出对象的方式
Cat cat = new Cat();
我们进入创建过程:
反射的方式
由此可见:
两者都是通过ClassLoader类加载Class对象
3.对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
分别执行下面图中的代码
图一:
图二:
随着图一debug我们进入了loadClass方法:
随着图二debug我们没有进入loadClass方法
这是因为图二在执行Cat cat = new Cat()时已经将Cat类加载到内存中,在堆中只会存在一份Class类对象,所以执行时不会再次进入loadClass方法,
对象.getClass()
4.通过Class对象可以完整地得到一个类的完整结构,通过一系列API
每个类的实例都会记得自己是由哪个Class实例所生成
Class getSuperClass() 返回当前Class对象的父类的Class对象
Class[] getInterfaces() 返回当前Class对象的所有接口
ClassLoader getClassLoader()返回该类的类加载器
Class getSuperclass() 返回表示此Class所表示的实体的超类的Class
5.类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括方法代码,变量名,方法名,访问权限等等)
当类加载完毕后,我们的字节码对应的二进制数据加载到方法去中,除此之外在堆中会产生
一个Class类对象,这其实是一种数据结构,可以把成员变量映射成一个对象并对其操作
Class类方法演示:
创建Car类
public class Car {
public String brand="品牌";
public int price;
public String color;
}
package com.hspedu.reflection.class_;
import com.hspedu.Car;
import java.lang.reflect.Field;
/**
* Description:演示Class类的常用方法
* User:
* Date:
* Time:
*/
public class Class02 {
public static void main(String[] args) throws Exception {
String classAllPath = "com.hspedu.Car";
Class<?> clazz = Class.forName(classAllPath);
//1.输出clazz
System.out.println(clazz);//显示clazz对象,是哪个类的Class对象 com.hspedu.Car
System.out.println(clazz.getClass());//输出clazz运行类型 java.lang.Class
//2.得到包名
System.out.println(clazz.getPackage().getName());
//3.得到全类名
System.out.println(clazz.getName());
//4.通过clazz创建对象实例
Car o = (Car)clazz.newInstance();
//5.通过反射获取属性
Field brand = clazz.getDeclaredField("brand");
System.out.println(brand.get(o));
//通过反射给属性赋值
brand.set(o,"奔驰");
System.out.println(brand.get(o));
//遍历得到所有的属性
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println(field.get(o));
}
}
}
获取反射的四种方式
不同阶段获取Class的不同方式
1.前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
创建方式: Class.forName()
应用场景:多用于配置文件,读取类全路径,加载类
2.前提:若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高
创建方式: 类.class
应用场景:多用于参数传递,比如通过反射得到对应构造器对象
3.前提:已知某个类的实例,调用该实例的geiClass()方法获取Class对象
创建方式:对象.getClass()
应用场景:通过创建好的对象,获取Class对象
4.其他方式
ClassLoader cl = 对象.getClass().getClassLoader();
创建方式:Class clazz =cl.getloadClass(“类的全类名”);
代码示例:
package com.hspedu.reflection.class_;
import com.hspedu.Car;
/**
1. Description:演示获取Class对象的四种方式
2. User:
3. Date:
4. Time:
*/
public class GetClass {
public static void main(String[] args) throws Exception {
//1.Class.forName()
Class<?> cls1 = Class.forName("com.hspedu.Car");
//2.类名.class,应用场景:多用于参数传递
Class<Car> cls2 = Car.class;
//3.对象名.getClass()
Car car = new Car();
Class<? extends Car> cls3 = car.getClass();
//4.通过类加载器来获取类的Class对象
//先得到类加载器
ClassLoader classLoader = car.getClass().getClassLoader();
Class<?> cls4 = classLoader.loadClass("com.hspedu.Car");
}
}
四、哪些类型有Class对象
如下类型有Class对象
- 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
- interface:接口
- 数组
- enum:枚举
- annotation:注解
- 基本数据类型
- void
五、动态加载和静态加载:
类加载基本说明:
反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载
- 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
- 动态加载:运行时加载需要的类,如果运行时不用该类,即使不存在该类,也不会报错,降低了依赖性
类加载时机:
- 当创建对象时(new)//静态加载
- 当子类被加载时,父类也加载//静态加载
- 调用类中的静态成员时//静态加载
- 通过反射//动态加载
代码示例:
也就是说,静态加载是不论程序是否会执行A代码,A都会被加载,而动态加载是说,只有在真正要用到A段代码时,A代码才会被加载
类加载过程图:
类加载的三个阶段的任务:
类加载各个阶段的介绍:
一、加载阶段Loading
JVM在该阶段的主要目的时将字节码从不同的数据源(可能是class文件、也可能是jar包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class对象
二、连接阶段Linking
连接阶段-验证:
- 目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机中的要求,并且不会危害虚拟机自身的安全
- 包括:文件格式验证(是否以魔数oxcafebabe开头)、元数据校验、字节码验证和符号饮用验证
- 可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间
连接阶段-准备:
代码案例:
class A{
//连接阶段-准备
//1.n1是实例属性,不是静态变量,因此在连接阶段-准备时不会被分配内存
//2.n2是静态变量,分配内存 n2 是默认在此阶段初始化,值为0,只有在连接阶段后的初始化阶段值才会被初始化为20
//3.n3是常量,他和静态变量不一样,因为一旦赋值就不会改变,所以n3此阶段值为30
public int n1=10;
public static int n2=20;
public static final int n3=30;
}
连接阶段-解析:
虚拟机将常量池内的符号引用替换为直接引用的过程
比如A类中有B类的引用,但只作为符号存在,只有在解析阶段才会将这类的符号引用解析为直接引用
三、初始化阶段Initialization
- 到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段是执行()方法的过程
- clinit()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中所有静态变量的赋值动作和静态代码块中的语句,并进行合并
- 虚拟机会保证一个类的()方法在多线程环境中被正确地加锁,同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕
代码展示:
package com.hspedu.reflection.classload_;
/**初始化过程
* Description
* User:
* Date:
* Time:
*/
public class ClassLoad03 {
public static void main(String[] args) {
//1.加载B类,并生成B的Class对象
//2.连接 num = 0;
//3.初始化
// 依次自动收集类中所有静态变量的赋值动作和静态代码块中的语句,并合并,
//先执行静态代码块中的代码,再执行静态变量
/*
clinit() {
System.out.println("B 静态代码块被执行");
num = 300;
static int num = 100;
}
合并:num=100
*/
// new B();
System.out.println(B.num);
}
}
class B{
static {
System.out.println("B 静态代码块被执行");
num = 300;
}
static int num = 100;
public B(){
System.out.println("B() 构造器被执行");
}
}
JVM对于clinit方法的加锁机制,在加载类的时候有同步机制控制
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
}
六、通过反射获取类的结构信息
java.lang.Class类方法:
- 1.getName:获取全类名
- getSimpleName:获取简单类名
- getFields:获取所有public 修饰的属性,包括本类以及父类的
- getDeclareFields:获取本类中的所有属性
- getMethods:获取所有public修饰的方法,包括本类以及父类的
- getDeclaredMethods:获取本类中的所有方法
- getConstructors:获取所有public修饰的构造器
- getDeclaredConstructors:获取本类中所有构造器
- getPackage:以Package形式返回包信息
- getSuperClass:以Class形式返回父类信息
- getInterfaces:以Class[]形式返回接口信息
- getAnnotations:以Annotation[]形式返回注解信息
代码示例:
package com.hspedu.reflection;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Description:演示如何通过
* User:
* Date:
* Time:
*/
public class ReflectionUtils {
public static void main(String[] args) throws Exception {
api1();
}
public static void api1() throws Exception{
Class<?> clazz = Class.forName("com.hspedu.reflection.A");
Object o = clazz.newInstance();
// 1. getName:获取全类名
System.out.println("获取全类名:"+clazz.getName());
// 2. getSimpleName:获取简单类名
System.out.println("获取简单类名:"+clazz.一文带你了解Java反射机制