一、反射定义&为什么要学习反射
1、反射定义
反射(Reflection)指
在编译的时候不需要确定数据的类型,而是延迟到运行时获取类的信息(如类名、方法名、字段名)、调用类的方法、访问或修改类的字段,
而
无需在编译时
确定这些信息,反射使Java这种静态编译型的语言具有了动态性。
反射具有看透类的能力,类的信息在反射面前都是透明的(包括private的属性和方法都是可以调用)
Java 的反射机制主要由 java.lang.reflect 包提供支持,包括 Class 类、Method 类、Field 类等。通过这些类,可以动态地加载类、创建类实例、调用类的方法、访问和修改类的字段等操作。
2、为什么要学习反射
框架:反射、泛型、注解
1、扩展性和灵活性:通过反射机制,可以实现动态加载类、调用方法、访问字段等操作,从而使程序具有更强的扩展性和灵活性。这在开发框架、插件系统或通用库时非常有用。
2、框架设计:许多 Java 框架(如Spring框架)都广泛使用了反射机制,通过反射可以实现依赖注入、AOP编程等功能。
3、框架源码阅读:许多开源框架和库中广泛使用了反射,学习反射可以帮助更好地理解和阅读这些框架的源码。
4、编写通用工具类:通过反射可以编写一些通用的工具类,比如序列化/反序列化工具、注解处理器、配置文件解析器等。
虽然反射机制有很多优点,但也需要注意反射可能带来的性能开销和安全隐患。因此,在使用反射时需要谨慎考虑,并避免滥用。总的来说,学习反射机制有助于提高 Java 程序员的编程技能和应用开发能力。
3、反射的缺点
尽管反射机制提供了很大的灵活性和扩展性,但也存在一些缺点和不足之处,包括:
1、性能开销:使用反射会导致性能开销较大,因为在运行时需要动态地解析类结构、查找方法或字段等,比直接调用方法要慢很多。这对性能要求高的应用可能不太适用。
2、
编译时检查失效:由于反射是在运行时进行的,因此编译器无法对反射代码进行类型检查,容易引入类型错误,造成运行时异常。
3、安全性问题:
通过反射可以访问和修改类的私有字段和方法,这可能会绕过类的封装性,造成安全隐患,特别是在安全敏感的应用中需要格外小心。
4、可读性和维护性差:使用反射的代码通常比直接调用更难理解和维护,因为它隐藏了类的结构和行为,使得代码更加晦涩难懂。
5、性能优化困难:由于反射是在运行时动态进行的,因此很难进行静态分析和优化,使得程序的性能优化变得更加困难。
6、不利于代码重构:使用反射的代码通常依赖于类的具体实现细节,一旦类结构发生变化,可能导致反射代码出错,不利于代码的重构和演化。
综上所述,虽然反射机制为 Java 程序员提供了很多便利,但在实际应用中需要谨慎使用,权衡利弊。在性能要求高、安全性要求严格或代码可维护性重要的情况下,建议尽量避免过度依赖反射机制,寻找其他更合适的解决方案。
二、反射整体理解


类比学习一下:面向对象抽象过程
- 众多的人 ----> Person类
- 众多学生 -----> Stundent类
- 众多的类 -----> Class类:任何一个类里面都包含这些东西:Field[]、Constructor[]、Method[]
- 众多的属性 ------> Field类
- 众多构造方法 ------> Constructor类
- 众多的普通方法 ------> Method类
private String name;
public Integer age;
class Field{
修饰符; //private
类型; //String
变量名字; // name
}

既然Class是描述类的类的类型,那类结构里面包含哪些东西呢: Field、Constructor、Method,同样这些众多的属性、构造函数、方法也有对应的类类型表示他们。
- Java.lang.Class;
- Java.lang.reflect.Field;
- Java.lang.reflect.Method;
- Java.lang.reflect.Constructor;
对于类型的学习我们可以参考做
月饼的模子,什么样模子就可以做出什么大小,图案的月饼。对于Java里面的int类型是四个字节,那么这个模子就是一个只能存放四个字节的模子,用这个模子做出来的就是int类型。
同理Class、Field、Method、Constructor就分别是类、属性、方法、构造函数的模子。
通过Class可以获得类的所有属性Field[]、方法Method[]、构造方法Constructor[]信息。
通过Field可以获得属性的名字、类型、修饰符
通过Method可以获得方法的名字、参数、返回值。
三、反射常见类
1、Class 反射的核心类
每个类加载到内存后,系统都会有唯一的一份字节码对象(Person.class/Student.class字节码对象都是Class这个类的实例)
public void testClass() throws ClassNotFoundException {
// 1.Class.forName(类路径)
Class clazz1 = Class.forName("com.situ.day15.Student");
// 2.类型.class
Class clazz2 = Student.class;
// 3.对象.getClass()
Student student = new Student();
Class clazz3 = student.getClass();
System.out.println(clazz1 == clazz2);//true
System.out.println(clazz1 == clazz3);//true
}
2、Constructor、Method
public void testConstructor() {
Class clazz = Student.class;
Constructor[] constructors = clazz.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
System.out.println(constructor.getName());//构造方法的名字
System.out.println(constructor.getModifiers());//修饰符
}
}
public com.situ.day15.Student()
com.situ.day15.Student
1
private com.situ.day15.Student(java.lang.String)
com.situ.day15.Student
2
@Test
public void testConstructor2() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class clazz = Student.class;
//private Student(Integer id, String name, Integer age, String gender)
Constructor constructor = clazz.getDeclaredConstructor(Integer.class, String.class, Integer.class, String.class);
//在反射面前,私有的都可以访问
constructor.setAccessible(true);
//Student student = new Student(1, "zhansgan", 23, "男");
Student student = (Student) constructor.newInstance(1, "zhangsan", 23, "男");
System.out.println(student);
}
Student{id=1, name='zhangsan', age=23, gender='男'}
public void testMethod() throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
//Student student = new Student();
//student.setName("lisi");
Class clazz = Class.forName("com.situ.day15.Student");
//得到无参构造方法
Constructor constructor = clazz.getConstructor();
//调用newInstance实例化对象
Student student = (Student) constructor.newInstance();
//student.setName("lisi");
Method method = clazz.getMethod("setName", String.class);
method.invoke(student, "lisi");
System.out.println(student);
}
四、反射综合案例
练习1
利用反射加配置文件加载指定数据库
public interface IDB {
public abstract void getConnection();
}
public class MySql implements IDB{
@Override
public void getConnection() {
System.out.println("MySql.getConnection()");
}
}
public class Oracle implements IDB{
@Override
public void getConnection() {
System.out.println("Oracle.getConnection()");
}
}
public class SqlServer implements IDB{
@Override
public void getConnection() {
System.out.println("SqlServer.getConnection()");
}
}
public class DBTest {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//MySql mySql = new MySql();
//IDB db = new MySql();
//db.getConnection();
//db = new SqlServer();
//db.getConnection();
//只出现接口,不出现具体实现类,面向接口编程
// 反射+配置文件
//1.通过当前的类获取类加载器
ClassLoader classLoader = DBTest.class.getClassLoader();
//2.通过类加载器的方法获取输入流
InputStream inputStream = classLoader.getResourceAsStream("db1.properties");
Properties properties = new Properties();
properties.load(inputStream);
String className = properties.getProperty("className");
Class clazz = Class.forName(className);
//Constructor constructor = clazz.getConstructor();
//IDB db = (IDB) constructor.newInstance();
//如果是new无参构造方法,可以直接clazz.newInstance()
IDB db = (IDB) clazz.newInstance();
db.getConnection();
}
public void db(IDB db) {
db.getConnection();
}
}
练习2
写一个函数,传入任意的对象,可以将对象里面String类型的属性中的a改成b。
public class Teacher {
private String name = "android";
private int age = 18;
private String address = "qingdao";
@Override
public String toString() {
return "Teacher [name=" + name + ", age=" + age + ", address="
+ address + "]";
}
}
@Test
public void test1() throws IllegalAccessException {
Teacher teacher = new Teacher();
System.out.println(teacher);
changeField(teacher);
System.out.println(teacher);
Student student = new Student();
student.setName("lisia");
System.out.println(student);
changeField(student);
System.out.println(student);
}
//动态性体现在传递任意一个对象使用反射都可以完成这个功能。
private void changeField(Object object) throws IllegalAccessException {
//1.获得对应的Class
Class clazz = object.getClass();
//2.获取所有的Field[] fields
Field[] fields = clazz.getDeclaredFields();
//3.遍历所有的Field,如果是String类型,将包含的a改成b
for (Field field : fields) {
//3.1 如果是String类型才修改
if (field.getType() == String.class) {
//属性是私有的
field.setAccessible(true);
//student.getName(); 并不是调用getName,因为调用getName()要拿到对应method
//student.name
//3.2或者Field原来的值 "qingdao"
String oldValue = (String) field.get(object);
if (oldValue != null && !"".equals(oldValue)) {
String newValue = oldValue.replace("a", "b");
//3.3将修改后的值设置到Field
field.set(object, newValue);
}
}
}
}