反射的思想及作用
在学习反射之前,先来了解正射是什么。我们平常用的最多的 new
方式实例化对象的方式就是一种正射的体现。假如我需要实例化一个HashMap
,代码就会是这样子
Map<Integer, Integer> map = new HashMap<>();
map.put(1, 1);
某一天发现,该段程序不适合用 HashMap 存储键值对,更倾向于用LinkedHashMap
存储。重新编写代码后变成下面这个样子
Map<Integer, Integer> map = new LinkedHashMap<>();
map.put(1, 1);
假如又有一天,发现数据还是适合用 HashMap来存储,难道又要重新修改源码吗?
发现问题了吗?我们每次改变一种需求,都要去重新修改源码,然后对代码进行编译,打包,再到 JVM 上重启项目。这么些步骤下来,效率非常低。
对于这种需求频繁变更但变更不大的场景,频繁地更改源码肯定是一种不允许的操作,我们可以使用一个开关
,判断什么时候使用哪一种数据结构。
public Map<Integer, Integer> getMap(String param) {
Map<Integer, Integer> map = null;
if (param.equals("HashMap")) {
map = new HashMap<>();
} else if (param.equals("LinkedHashMap")) {
map = new LinkedHashMap<>();
} else if (param.equals("WeakHashMap")) {
map = new WeakHashMap<>();
}
return map;
}
通过传入参数param
决定使用哪一种数据结构,可以在项目运行时,通过动态传入参数决定使用哪一个数据结构。
如果某一天还想用TreeMap
,还是避免不了修改源码,重新编译执行的弊端。这个时候,反射就派上用场了。
在代码运行之前,我们不确定将来会使用哪一种数据结构,只有在程序运行时才决定使用哪一个数据类,而反射
可以在程序运行过程中动态获取类信息和调用类方法。通过反射构造类实例,代码会演变成下面这样。
public Map<Integer, Integer> getMap(String className) {
Class clazz = Class.forName(className);
Consructor con = clazz.getConstructor();
return (Map<Integer, Integer>) con.newInstance();
}
无论使用什么 Map,只要实现了Map接口
,就可以使用全类名路径
传入到方法中,获得对应的 Map 实例。例如java.util.HashMap / java.util.LinkedHashMap····如果要创建其它类例如WeakHashMap
,我也不需要修改上面这段源码。
我们来回顾一下如何从 new
一个对象引出使用反射
的。
- 在不使用反射时,构造对象使用 new 方式实现,这种方式在编译期就可以把对象的类型确定下来。
- 如果需求发生变更,需要构造另一个对象,则需要修改源码,非常不优雅,所以我们通过使用
开关
,在程序运行时判断需要构造哪一个对象,在运行时可以变更开关来实例化不同的数据结构。 - 如果还有其它扩展的类有可能被使用,就会创建出非常多的分支,且在编码时不知道有什么其他的类被使用到,假如日后
Map
接口下多了一个集合类是xxxHashMap
,还得创建分支,此时引出了反射
总结:
- 反射的思想:在程序运行过程中确定和解析数据类的类型。
- 反射的作用:对于在
编译期
无法确定使用哪个数据类的场景,通过反射
可以在程序运行时构造出不同的数据类实例。
Java反射机制
Java 反射机制在程序运行时,可以判断一个对象所属的类,可以构造任意一个类的对象,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种 动态的获取信息 以及 动态调用对象的方法 的功能称为 java 的反射机制。
反射机制很重要的一点就是“运行时”,其使得我们可以在程序运行时加载、探索以及使用编译期间完全未知的 .class
文件。换句话说,Java 程序可以加载一个运行时才得知名称的 .class
文件,然后获悉其完整构造,并生成其对象实体、或对其 fields(变量)设值、或调用其 methods(方法)
反射的基本使用
- java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
- java.lang.reflect.Method:代表类的方法,内部包含了该方法的所有信息,与
Constructor
类似,不同之处是 Method 拥有返回值类型信息,因为构造方法是没有返回值的。 - java.lang.reflect.Field:代表类的属性,内部包含了构造方法的所有信息,例如参数类型,参数名字,访问修饰符。Field对象代表某个类的成员变量
- java.lang.reflect.Constructor:代表类的构造方法,内部包含了构造方法的所有信息,例如参数类型,参数名字,访问修饰符,Constructor对象代表某个类的构造方法
获取类的 Class 对象
- 前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundExecption,应用实例
String classPath = "com.neutech.eneity.Car";
//1.获取Car类 对应的Class对象
Class aClass = Class.forName(classPath);
应用场景:多用于配置文件,读取类全路径,加载类
2.前提:诺已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高。实例
Class aClass1 = Car.class;
应用场景:多用于参数传递,比如通过反射得到对应构造器对象
3.前提:已知某个类的实例对象,调用该实例的getClass()方法获取Class对象,实例Class clazz = 对象.getClass()
Car car = new Car();
Class aClass2 = car.getClass(); //运行时实例
4.其他方式
ClassLoader classLoader = 对象.getClass().getClassLoader();
Class aClass3 = classLoader.loadClass("类的全类名");
//4.通过类的加载器来获取类的Class对象
//(1)先得到类加载器
ClassLoader classLoader = car.getClass().getClassLoader();
//(2)通过类加载器得到Class对象
Class aClass3 = classLoader.loadClass("com.neutech.eneity.Car");
System.out.println(aClass3);
5.基本数据类型(int,short,boolan等基本数据类型)按如下方式得到Class类对象
Class aclass = 基本数据类型.class
Class<Integer> integerClass = int.class;
6.基本数据类型对应的包装类,可以通过.type得到Class类对象
Class aclass = 包装类.TYPE
Class<Integer> type = Integer.TYPE;
构造类的实例化对象
通过反射构造一个类的实例方式有2
种:
- Class对象调用newInstance()方法
//创建Class类对象
Class aClass = Class.forName("com.neutech.refleaction.demo06.User");
//1.通过public的无参构造器创建实例 User实体类必须有无参构造方法
Object user = aClass.newInstance();
- Constructor 构造器调用newInstance()方法
//2.通过public的有参构造器创建实例
/* Constructor对象就是
* public User(String name) {
this.name = name;
}
*/
Constructor constructor = aClass.getConstructor(String.class);
Object user1 = constructor.newInstance("媛");
System.out.println(user1);
//3.通过无参构造器创建实例
//Constructor constructor1 = aClass.getConstructor(Integer.class,String.class);//因为getConstructor()只能获取public修饰的构造方法
Constructor constructor1 = aClass.getDeclaredConstructor(int.class,String.class); //aClass.getDeclaredConstructor获取所有的构造方法
//暴力破解 使用反射可以访问private构造器。反射面前都是纸老虎
constructor1.setAccessible(true);
Object user2 = constructor1.newInstance(12, "媛1");
通过Class
对象调用 newInstance() 会走默认无参构造方法,如果想通过显式构造方法构造实例,需要提前从Class中调用getConstructor()方法获取对应的构造器,通过构造器去实例化对象。
获取一个类的所有信息
Class 对象中包含了该类的所有信息,在编译期我们能看到的信息就是该类的变量、方法、构造器,在运行时最常被获取的也是这些信息。
获取类中的变量(Field)
- Field[] getFields():获取本类及父类中所有被public修饰的所有变量
- Field getField(String name):根据变量名获取类中的一个变量,该变量必须被public修饰
- Field[] getDeclaredFields():获取类中所有的变量,但无法获取继承下来的变量
- Field getDeclaredField(String name):根据姓名获取类中的某个变量,无法获取继承下来的变量
获取类中的方法(Method)
- Method[] getMethods():获取本类及父类中被public修饰的所有方法
- Method getMethod(String name, Class... paramTypes):根据名字和参数类型获取对应方法,该方法必须被public修饰
- Method[] getDeclaredMethods():获取
所有
方法,但无法获取继承下来的方法 - Method getDeclaredMethod(String name, Class... paramTypes):根据名字和参数类型获取对应方法,无法获取继承下来的方法 获取类的构造器(Constructor)
操作实例
import java.lang.reflect.Method;
public class ReflectMethod {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
Class clazz = Class.forName("reflect.Circle");
//根据参数获取public的Method,包含继承自父类的方法
Method method = clazz.getMethod("draw",int.class,String.class);
System.out.println("method:"+method);
//获取所有public的方法:
Method[] methods =clazz.getMethods();
for (Method m:methods){
System.out.println("m::"+m);
}
System.out.println("=========================================");
//获取当前类的方法包含private,该方法无法获取继承自父类的method
Method method1 = clazz.getDeclaredMethod("drawCircle");
System.out.println("method1::"+method1);
//获取当前类的所有方法包含private,该方法无法获取继承自父类的method
Method[] methods1=clazz.getDeclaredMethods();
for (Method m:methods1){
System.out.println("m1::"+m);
}
}
/**
输出结果:
method:public void reflect.Shape.draw(int,java.lang.String)
m::public int reflect.Circle.getAllCount()
m::public void reflect.Shape.draw()
m::public void reflect.Shape.draw(int,java.lang.String)
m::public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
m::public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
m::public final void java.lang.Object.wait() throws java.lang.InterruptedException
m::public boolean java.lang.Object.equals(java.lang.Object)
m::public java.lang.String java.lang.Object.toString()
m::public native int java.lang.Object.hashCode()
m::public final native java.lang.Class java.lang.Object.getClass()
m::public final native void java.lang.Object.notify()
m::public final native void java.lang.Object.notifyAll()
=========================================
method1::private void reflect.Circle.drawCircle()
m1::public int reflect.Circle.getAllCount()
m1::private void reflect.Circle.drawCircle()
*/
}
class Shape {
public void draw(){
System.out.println("draw");
}
public void draw(int count , String name){
System.out.println("draw "+ name +",count="+count);
}
}
class Circle extends Shape{
private void drawCircle(){
System.out.println("drawCircle");
}
public int getAllCount(){
return 100;
}
}
获取类中的构造器(Constuctor)
- Constuctor[] getConstructors():获取本类中所有被public修饰的构造器
- Constructor getConstructor(Class... paramTypes):根据参数类型获取类中某个构造器,该构造器必须被public修饰
- Constructor[] getDeclaredConstructors():获取本类中所有构造器
- Constructor getDeclaredConstructor(class... paramTypes):根据参数类型获取对应的构造器
操作实例:
public static void main(String[] args) throws Exception {
//创建Class对象
Class aClass = Class.forName("com.neutech.refleaction.demo06.User");
//1.通过public的无参构造器创建实例
Object user = aClass.newInstance();
System.out.println(user);
//2.通过public的有参构造器创建实例
/* Constructor对象就是
* public User(String name) {
this.name = name;
}
*/
Constructor constructor = aClass.getConstructor(String.class);
Object user1 = constructor.newInstance("媛");
System.out.println(user1);
//3.通过无参构造器创建实例
//Constructor constructor1 = aClass.getConstructor(Integer.class,String.class);//因为getConstructor()只能获取public修饰的构造方法
Constructor constructor1 = aClass.getDeclaredConstructor(int.class,String.class); //aClass.getDeclaredConstructor获取所有的构造方法
//暴力破解 使用反射可以访问private构造器。反射面前都是纸老虎
constructor1.setAccessible(true);
Object user2 = constructor1.newInstance(12, "媛1");
System.out.println(user2);
}
}
class User{
private int age = 10;
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 + '\'' +
'}';
}
}
每种功能内部以 Declared 细分为2
类:
有Declared
修饰的方法:可以获取该类内部包含的所有变量、方法和构造器,但是无法获取继承下来的信息
无Declared
修饰的方法:可以获取该类中public
修饰的变量、方法,可获取继承下来的信息,构造器只能获取本类的
通过反射调用方法
通过反射获取到某个 Method 类对象后,可以通过调用invoke
方法执行。
invoke(Oject obj, Object... args)
:参数``1指定调用该方法的**对象**,参数
2`是方法的参数列表值。
如果调用的方法是静态方法,参数1只需要传入null
,因为静态方法不与某个对象有关,只与某个类有关。
可以像下面这种做法,通过反射实例化一个对象,然后获取Method
方法对象,调用invoke()
指定SmallPineapple
的getInfo()
方法。
Class clazz = Class.forName("reflect.Circle");
//创建对象
Circle circle = (Circle) clazz.newInstance();
//获取指定参数的方法对象Method
Method method = clazz.getMethod("draw",int.class,String.class);
//通过Method对象的invoke(Object obj,Object... args)方法调用
method.invoke(circle,15,"圈圈");
//对私有无参方法的操作
Method method1 = clazz.getDeclaredMethod("drawCircle");
//修改私有方法的访问标识
method1.setAccessible(true);
method1.invoke(circle);
//对有返回值得方法操作
Method method2 =clazz.getDeclaredMethod("getAllCount");
Integer count = (Integer) method2.invoke(circle);
System.out.println("count:"+count);
/**
输出结果:
draw 圈圈,count=15
drawCircle
count:100
*/
通过反射操作属性(暴力破解私有属性)
public class Test1 {
public static void main(String[] args) throws Exception {
//1.得到Student对应的Class对象
Class<?> stuClass = Class.forName("com.neutech.refleaction.demo06.Student");
//2.创建对象
Object o = stuClass.newInstance(); //运行时类型 class com.neutech.refleaction.demo06.Student
System.out.println(o.getClass());
//使用反射得到age属性对象
Field ageField = stuClass.getField("age");
//通过反射操作属性
ageField.set(o,88);
System.out.println(o);
//通过反射得到name属性对象 name私有静态
Field nameField = stuClass.getDeclaredField("name");
//爆破
nameField.setAccessible(true);
//通过反射操作属性 给属性赋值
nameField.set(o,"liang");
nameField.set(null,"liang--"); //因为name是static属性,因此o也可以写成null
System.out.println(o);
//获取属性的值
System.out.println(nameField.get(o)); //liang--
}
}
class Student{
public int age;
private static String name;
public Student(){}
@Override
public String toString() {
return "Student{" +
"age=" + age +
"name=" + name +
'}';
}
}
反射的应用场景
反射常见的应用场景这里介绍2个:
- Spring 实例化对象:当程序启动时,Spring 会读取配置文件
applicationContext.xml
并解析出里面所有的 标签实例化到IOC
容器中。 - JDBC连接数据库:使用JDBC连接数据库时,指定连接数据库的
驱动类
时用到反射加载驱动类
Spring 的 IOC 容器
在 Spring 中,经常会编写一个上下文配置文件applicationContext.xml
,里面就是关于bean
的配置,程序启动时会读取该 xml 文件,解析出所有的 <bean>
标签,并实例化对象放入IOC
容器中。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="smallpineapple" class="com.bean.SmallPineapple">
<constructor-arg type="java.lang.String" value="小菠萝"/>
<constructor-arg type="int" value="21"/>
</bean>
</beans>
在定义好上面的文件后,通过ClassPathXmlApplicationContext
加载该配置文件,程序启动时,Spring 会将该配置文件中的所有bean
都实例化,放入 IOC 容器中,IOC 容器本质上就是一个工厂,通过该工厂传入 <bean> 标签的id
属性获取到对应的实例。
public class Main {
public static void main(String[] args) {
ApplicationContext ac =
new ClassPathXmlApplicationContext("applicationContext.xml");
SmallPineapple smallPineapple = (SmallPineapple) ac.getBean("smallpineapple");
smallPineapple.getInfo(); // [小菠萝的年龄是:21]
}
}
Spring 在实例化对象的过程经过简化之后,可以理解为反射实例化对象的步骤:
- 获取Class对象的构造器
- 通过构造器调用 newInstance() 实例化对象
JDBC 加载数据库驱动类
在导入第三方库时,JVM不会主动去加载外部导入的类,而是等到真正使用时,才去加载需要的类,正是如此,我们可以在获取数据库连接时传入驱动类的全限定名,交给 JVM 加载该类。
public class DBConnectionUtil {
/** 指定数据库的驱动类 */
private static final String DRIVER_CLASS_NAME = "com.mysql.jdbc.Driver";
public static Connection getConnection() {
Connection conn = null;
// 加载驱动类
Class.forName(DRIVER_CLASS_NAME);
// 获取数据库连接对象
conn = DriverManager.getConnection("jdbc:mysql://···", "root", "root");
return conn;
}
}
在我们开发 SpringBoot 项目时,会经常遇到这个类,但是可能习惯成自然了,就没多大在乎,我在这里给你们看看常见的application.yml
中的数据库配置。
这里的 driver-class-name,和我们一开始加载的类是不是觉得很相似,这是因为MySQL版本不同引起的驱动类不同,这体现使用反射的好处:不需要修改源码,仅加载配置文件就可以完成驱动类的替换。
反射优点和缺点
优点:可以动态的创建和使用对象(框架底层核心),使用灵活,没有反射机制,框架底层失去支撑,增加程序的灵活性:面对需求变更时,可以灵活地实例化不同对象
缺点:破坏类的封装性:可以强制访问 private 修饰的信息,性能损耗:反射相比直接实例化对象、调用方法、访问变量,中间需要非常多的检查步骤和解析步骤,JVM无法对它们优化。
反射调用优化
1.Method,Field和Constructor对象都有setAccessible()方法
2.setAccessible作用是启动和禁用访问安全检测的开关
3.参数值为true时,反射的对象在使用时取消访问检查,提高反射的效率,参数值为false则表示反射的对象执行访问检查
public static void main(String[] args) throws Exception {
m1();
m2();
m3();
}
//new 对象 传统方法
public static void m1(){
long start = System.currentTimeMillis();
Cat cat = new Cat();
for (int i = 0; i < 900000000; i++) {
cat.eat();
}
long end = System.currentTimeMillis();
System.out.println("传统方法=="+(end-start));
}
//反射调用方法
public static void m2() throws Exception {
long start = System.currentTimeMillis();
Class aClass = Class.forName("com.neutech.eneity.Cat");
Object o = aClass.newInstance();
Method eat = aClass.getMethod("eat");
for (int i = 0; i < 900000000; i++) {
eat.invoke(o);
}
long end = System.currentTimeMillis();
System.out.println("反射调用=="+(end-start));
}
//反射优化
public static void m3() throws Exception {
long start = System.currentTimeMillis();
Class aClass = Class.forName("com.neutech.eneity.Cat");
Object o = aClass.newInstance();
Method eat = aClass.getMethod("eat");
eat.setAccessible(true); //取消访问安全检查
for (int i = 0; i < 900000000; i++) {
eat.invoke(o);
}
long end = System.currentTimeMillis();
System.out.println("反射优化=="+(end-start));
}
//输出结果
传统方法==3
反射调用==1172
反射优化==1138
参考博文:
链接:https://juejin.cn/post/6844904005294882830
链接:https://juejin.cn/post/6864324335654404104
学习视频链接
【韩顺平讲Java】Java反射专题 -反射 反射机制 类加载 reflection Class 类结构 等_哔哩哔哩_bilibili