思考:
Java里main方法执行程序,但是servlet里没有main方法是如何执行的?——通过反射方式执行
反射的定义
反射(Reflection)是 Java 的一种特性,它可以让程序在运行时获取自身的信息,并且动态地操作类或对象的属性、方法和构造器等。通过反射功能,可以让我们在不知道具体类名的情况下,依然能够实例化对象,调用方法以及设置属性。
简而言之,反射是获取类信息的一种能力。
什么是类信息?
在一个类中有方法 变量 构造器 继承和实现的类或接口,这些为类信息。
反射的核心概念
反射的核心是java.lang.reflect
包,主要涉及以下类:
-
Class类:代表一个类或接口
-
Field类:代表类的成员变量
-
Method类:代表类的方法
-
Constructor类:代表类的构造方法
反射的应用
反射使用步骤:
1.生成类对象(3种方式)
2.获取类信息,类信息的存储形式有2种:获取相关集合、直接获取
一、获取类对象
1、通过类名.class获取
Class<String> stringClass = String.class;
2、 通过对象.getClass()获取
String str = "Hello";
Class<?> strClass = str.getClass();
3、 通过Class.forName()获取(最常用)
Class<?> arrayListClass = Class.forName("java.util.ArrayList");
3种方式对应三个阶段
public class CatTest {
public static void main(String[] args) throws Exception{
//方式1
Class clazz1=Class.forName("com.qc.May.Cat");
//方式2
Class clazz2=Cat.class;
//方式3
Cat cat=new Cat("黑色");
Class clazz3=cat.getClass();
//测试是否指向同一块内存空间,结果为true
System.out.println(clazz1==clazz2);
System.out.println(clazz2==clazz3);
}
}
二、获取类信息
反射获取变量 Field
Class<?> clazz = Class.forName("com.example.Person");
// 获取public字段(包括父类的public字段)
Field publicField = clazz.getField("fieldName");
// 获取本类声明的所有字段(包括private,不包括父类的)
Field privateField = clazz.getDeclaredField("fieldName");
// 获取所有public字段
Field[] publicFields = clazz.getFields();
// 获取本类所有字段(包括private)
Field[] allFields = clazz.getDeclaredFields();
反射获取方法 Method
//getDeclaredMethods()获取全部方法
Method[] methods=clazz.getDeclaredMethods();
//getMethods()只能获取public方法
Method[] methods2=clazz.getMethods();
// 获取公共方法(包括继承的)
Method method = clazz.getMethod("方法名", 参数类型.class, ...);
// 获取所有方法(包括私有方法,不包括继承的)
Method privateMethod = clazz.getDeclaredMethod("方法名", 参数类型.class, ...);
// 调用方法
Object result = method.invoke(实例对象, 参数值1, 参数值2, ...);
// 调用私有方法需要先设置可访问
privateMethod.setAccessible(true);
Object result = privateMethod.invoke(实例对象, 参数值...);
反射获取构造器 Constructor
// 1. 获取类中所有的公共(public)构造方法(不包括父类的构造方法)
Constructor[] constructors0 = clazz.getConstructors();
// 2. 获取类中声明的所有构造方法(包括private、protected、default、public)
Constructor[] constructors1 = clazz.getDeclaredConstructors();
// 3. 获取类中特定的无参构造方法(可以是任意访问权限)
// 如果找不到匹配的构造方法会抛出NoSuchMethodException
// 注意:即使构造方法是private的,也能获取到,但调用前需要setAccessible(true)
Constructor constructor2 = clazz.getDeclaredConstructor();
// 4. 获取类中特定的有参构造方法(参数类型为String,可以是任意访问权限)
Constructor constructor3 = clazz.getDeclaredConstructor(String.class);
输出结果
[public com.qc.May.Cat(int,java.lang.String,java.lang.String,double)]
[public com.qc.May.Cat(int,java.lang.String,java.lang.String,double)]
[Ljava.lang.reflect.Constructor;@15db9742
com.qc.May.Cat(java.lang.String)
反射获取接口
Class[] classes=clazz.getInterfaces();
关于构造器
构造器其实是创建类对象的;
类本身有一个未显示的无参构造器,当在类里写构造器时,默认构造器被覆盖;
三、操作类信息
变量的基本操作
获取变量值
Object obj = clazz.newInstance();
Field field = clazz.getDeclaredField("age");
// 对于public字段可以直接获取
int age = (int) field.get(obj);
// 对于private字段需要先设置可访问
field.setAccessible(true); // 突破private限制
int age = (int) field.get(obj);
设置变量值
Object obj = clazz.newInstance();
Field field = clazz.getDeclaredField("name");
// 设置public字段
field.set(obj, "张三");
// 设置private字段
field.setAccessible(true);
field.set(obj, "李四");
方法的调用
基本调用
Object obj = clazz.newInstance();
Method method = clazz.getMethod("sayHello", String.class);
// 调用方法
Object result = method.invoke(obj, "张三");
调用静态方法
Method staticMethod = clazz.getMethod("staticMethod", String.class);
// 静态方法调用时,obj参数传null
Object result = staticMethod.invoke(null, "参数");
调用私有方法
Method privateMethod = clazz.getDeclaredMethod("privateMethod");
// 突破private限制
privateMethod.setAccessible(true);
Object result = privateMethod.invoke(obj);
方法信息获取
Method method = clazz.getMethod("toString");
// 获取方法名
String methodName = method.getName();
// 获取返回类型
Class<?> returnType = method.getReturnType();
// 获取参数类型数组
Class<?>[] paramTypes = method.getParameterTypes();
// 获取异常类型数组
Class<?>[] exceptionTypes = method.getExceptionTypes();
// 获取修饰符
int modifiers = method.getModifiers();
boolean isPublic = Modifier.isPublic(modifiers);
boolean isStatic = Modifier.isStatic(modifiers);
反射的优缺点
优点:
-
极大的灵活性
-
可以在运行时动态操作类和对象
-
适合开发通用框架和工具
缺点:
-
性能开销大(比直接调用慢)
-
破坏封装性(可以访问私有成员)
-
增加代码复杂度
-
绕过泛型检查
-
安全问题(可以调用任意方法)