一. 反射概述
1. 为什么要用反射?
Java开发中往往要遵循OCP,简单来说,就是软件实体对扩展开放,而对修改关闭。因此,一部分数据需要放在配置文件中供修改,可以有效减小修改源码和重新编译的次数;也正是因为Java的反射机制,Java代码更容易实现OCP,因此催生了许多Java生态,如Spring,SpringMVC等。
2. 反射是什么?
Java的反射机制允许程序在执行期借助Reflection API取得任何类的内部信息(方法, 字段,构造器等),并能操作对象的属性和方法。
3. Java反射机制原理图
4. 反射简单case
package com.hspedu.reflection.question;
import com.hspedu.Cat;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* @author 左阳
* @version v0.1
*/
public class ReflectionQuestion {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//1. 传统方式
//Cat cat = new Cat();
//cat.hi();
//2. 读取配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src/re.properties"));
String classPath = properties.get("classfullpath").toString();
String methodName = properties.get("method").toString();
//(1)若直接通过字符串来创建对象
//System.out.println("classPath=" + classPath);
//System.out.println("method=" + method);
//行不通:本质是因为需要从配置文件中读取所需要的类
//3. 采用反射的机制
//(1). 【Class类】加载一个class类,会返回一个Class这么一个类型的对象
Class cls = Class.forName(classPath);
//(2). 【获取对象实例】通过 cls对象,可以创建/得到加载的类
Object o = cls.newInstance();
//(3). 【Method类】通过method1可以调用方法
Method method1 = cls.getMethod(methodName);
System.out.println("===================");
method1.invoke(o);//反射中是反过来,即通过
//(4). 【Field类】
Field name = cls.getField("name"); //getField不能得到私有的属性,
System.out.println("Cat类 name字段为:" + name.get(o));
//(5). 【Construct类】
Constructor constructors = cls.getConstructor(); // ()中,可以指定构造器参数类型,这里返回一个无参构造器
System.out.println(constructors); // 若想获取有参构造器,请传入类型的对象,如String.class
}
}
5. 反射的优缺点
(1)优点:可以动态的创建和使用对象(也即是框架的底层核心),使用灵活。
(2)缺点:使用反射基本是解释执行,对运行速度有影响。
二. Class类
1. Class类要点
(1)Class类对象不是new出来的,而是系统创建的
(2)对于某个类的Class对象,内存中最多只有一份,因为类只加载一次
(3)通过Class对象可以完整的得到一个类的结构
(4)CLass对象是存放在堆里的
(5)类的字节码信息,是放在方法区的
2. Class类的常用方法
package com.hspedu.class_;
import com.hspedu.Car;
import com.hspedu.Cat;
import java.lang.reflect.Field;
/**
* @author 左阳
* @version v0.1
*/
@SuppressWarnings({"all"})
public class Class01 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//1. 定义类的名字,并获取Class对象
String className = "com.hspedu.Car";
Class cls = Class.forName(className);
//2. 获取class对象是哪个类的class对象
System.out.println(cls);
//3. 运行类型 -- java.lang.Class
System.out.println(cls.getClass());
//4. 获取Class类的类所在包的名字
System.out.println(cls.getPackage().getName());
//5. 获取名字
System.out.println(cls.getName());
//6. 通过反射创建对象
Car car = (Car) cls.newInstance();
//7. 建立字段对象,通过字段对象获取属性,设置属性
Field name = cls.getField("brand");
System.out.println(name.get(car)); // 宝马
name.set(car, "奔驰");
System.out.println(name.get(car)); // 奔驰
//8. 获取所有字段
Field[] fields = cls.getFields();
for(Field f : fields) {
System.out.println(f.getName());
}
}
}
3. 如何获取Class类
| 方法 | 应用场景 |
|---|---|
| Class的静态方法Class.forName() | 多用于配置文件的读取 |
| 类名.class | 多用于参数传递 |
| 对象.getClass() | 可通过创建好的对象来获取 |
| 通过类加载器:对象.getClass().getClassLoad().loadClass("类名") | 可通过创建好的对象来获取 |
| 基本数据类型.class | NA |
| 包装数据类型.TYPE | NA |
4. 哪些类型具有Class类?
外部类,内部类,接口,数组,枚举,注解,基本数据类型,void,Class类(本身也是外部类)
5. 类的加载过程
反射机制是Java实现动态语言的关键,降低了代码的依赖性。
下面为类的加载过程图:
三. 反射过程中的其他信息(类的整个数据结构)
方法比较多,使用过程中查看相关API即可
下面举一些经典case
1. 通过反射创建某类的对象
其中,该类有一个无参public构造器,一个有参public构造器,一个有参private构造器
package com.hspedu.reflection.question;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @author 左阳
* @version v0.1
*/
@SuppressWarnings({"all"})
public class ReflecCreateInstance {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
//1. 先获取到User类的Class对象
Class<?> userClass = Class.forName("com.hspedu.reflection.question.User");
//2. 通过public的无参构造器创建实例
Object o1 = userClass.newInstance();
System.out.println("o1=" + o1);
//3. 通过public的有参构造器创建实例
Constructor<?> constructor = userClass.getConstructor(String.class);
Object o2 = constructor.newInstance("zuoyang");
System.out.println(o2);
//4. 通过非public的有参构造器来创建实例
Constructor<?> declaredConstructor = userClass.getDeclaredConstructor(int.class, String.class);
declaredConstructor.setAccessible(true); //暴力破解,反射面前,一切都是纸老虎
Object o3 = declaredConstructor.newInstance(30, "hspedu");
System.out.println(o3);
}
}
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 + ''' +
'}';
}
}
- 通过Class对象,只能获取public无参构造器
- 通过构造器对象,可以用其他方式获取对象:
(1)若需要通过有参public方式构建对象,需要获取构造器对象,再传入参数
(2)若需要通过有参private方式构造对象,还需将构造器通过setAccessible()暴力破解
2. 通过反射访问属性
package com.hspedu.reflection.question;
import java.lang.reflect.Field;
/**
* @author 左阳
* @version v0.1
*/
public class ReflecAccessProperty {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//1. 创建对象
Class<?> aClass = Class.forName("com.hspedu.reflection.question.Student");
Object o = aClass.newInstance();
//2. 获取public field字段,通过getField()方法
Field age = aClass.getField("age");
age.set(o, 20);
System.out.println(o); // 20 , null
//3. 获取private字段,并修改
Field name = aClass.getDeclaredField("name");
name.setAccessible(true); //暴力破解
name.set(o , "123");
System.out.println(o); // 20 123
name.set(null, "456");
System.out.println(o); // 20 , 456
System.out.println(name.get(o)); // 456
}
}
class Student {
public int age;
private static String name;
public Student() {
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
"name=" + name +
'}';
}
}
- public字段可以通过api,直接获取与修改
- private字段对象可以通过getDclaredField获取,并暴力破解后读和修改
- 静态字段可以不传入具体Object修改,而写入null