Java基础系列11: 反射机制
本文是作者的读书笔记和心得整理,部分内容来源于网络,如有侵权,请联系作者。
什么是反射
通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息.
反射的核心是JVM在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。
也就是说有些时候我们需要在程序运行到某一步时才去创建一个类的对象,或者根据这个对象的类型去调用某个函数,而这些在编译器是未知的.
也就是动态的创建对象
什么时候运用反射
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
- 在运行时调用任意一个对象的方法
注意,全部都是运行时,而不是编译时
反射的使用举例
很多框架(比如Spring)都是配置化的(比如通过XML文件配置JavaBean,Action之类的),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射——运行时动态加载需要加载的对象。
Java反射是Java被视为动态(或准动态)语言的一个关键性质。这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public、static等)、superclass(例如Object)、实现之interfaces(例如Cloneable),也包括fields和methods的所有信息,并可于运行时改变fields内容或唤起methods。
实现Java反射机制的类都位于java.lang.reflect包中;
反射的基本使用
获取Class对象
- 使用forName的静态方法
public static Class<?> forName(String className)
在JDBC开发中常用此方法加载数据库驱动:
要使用全类名来加载这个类,一般数据库驱动的配置信息会写在配置文件中。加载这个驱动前要先导入jar包
Class.forName(driver);
或者这样,就获取到了UserBean这个类的类对象
Class clazz = Class.forName("com.javase.反射.UserBean");
- 直接获取某一个对象的class,比如:
//Class<?>是一个泛型表示,用于获取一个类的类型。
Class<?> klass = int.class;
Class<?> classInt = Integer.TYPE;
- 调用某个对象的getClass()方法,比如:
StringBuilder str = new StringBuilder("123");
Class<?> klass = str.getClass();
判断是否为某个类的实例
可以使用instanceof关键字来判断是否为某个类的实例.
也可以用native方法,public native boolean isInstance(Object obj);
创建实例
通过反射来生成对象主要有两种方法:
1)使用Class对象的newInstance方法来创建Class对象对应类的实例,当前类必须有无参构造器
//Class<?>代表任何类的一个类对象。
//使用这个类对象可以为其他类进行实例化
//因为jvm加载类以后自动在堆区生成一个对应的*.Class对象
//该对象用于让JVM对进行所有*对象实例化。
Class<?> c = String.class;
//Class<?> 中的 ? 是通配符,其实就是表示任意符合泛类定义条件的类,和直接使用 Class
//效果基本一致,但是这样写更加规范,在某些类型转换时可以避免不必要的 unchecked 错误。
Object str = c.newInstance();
2)先获取一个类对象(类型是Class<?>),再调用getConstructor方法来获取构造器,最后调用构造器的newInstance方法.
//获取String所对应的Class对象
Class<?> c = String.class;
//获取String类带一个String参数的构造器
Constructor constructor = c.getConstructor(String.class);
//根据构造器创建实例
Object obj = constructor.newInstance("23333");
System.out.println(obj);
获取方法
getDeclaredMethods()方法返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法.
(因为反射是创建了本类对象,所以相当于是内部调用,也就有了本不应该给外部的private方法的权限,反射攻击要注意这部分)
public Method[] getDeclaredMethods() throws SecurityException
getMethods()方法返回某个类的所有公用(public)方法,包括其继承类的公用方法。
public Method[] getMethods() throws SecurityException
getMethod方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象
public Method getMethod(String name, Class<?>... parameterTypes)
几个例子:
打印一个类的所有方法及详细信息
public class 打印所有方法 {
public static void main(String[] args) {
Class userBeanClass = UserBean.class;
Field[] fields = userBeanClass.getDeclaredFields();
//注意,打印方法时无法得到局部变量的名称,因为jvm只知道它的类型
Method[] methods = userBeanClass.getDeclaredMethods();
for (Method method : methods) {
//依次获得方法的修饰符,返回类型和名称,外加方法中的参数
String methodString = Modifier.toString(method.getModifiers()) + " " ; // private static
methodString += method.getReturnType().getSimpleName() + " "; // void
methodString += method.getName() + "("; // staticMethod
Class[] parameters = method.getParameterTypes();
Parameter[] p = method.getParameters();
for (Class parameter : parameters) {
methodString += parameter.getSimpleName() + " " ; // String
}
methodString += ")";
System.out.println(methodString);
}
//注意方法只能获取到其类型,拿不到变量名
/* public String getName()
public long getId()
public static void staticMethod(String int )
public void publicMethod()
private void privateMethod()*/
}
}
获取构造器信息
public class 打印构造方法 {
public static void main(String[] args) {
// constructors
Class<?> clazz = UserBean.class;
Class userBeanClass = UserBean.class;
//获得所有的构造方法
Constructor[] constructors = userBeanClass.getDeclaredConstructors();
for (Constructor constructor : constructors) {
String s = Modifier.toString(constructor.getModifiers()) + " ";
s += constructor.getName() + "(";
//构造方法的参数类型
Class[] parameters = constructor.getParameterTypes();
for (Class parameter : parameters) {
s += parameter.getSimpleName() + ", ";
}
s += ")";
System.out.println(s);
//打印结果//public com.javase.反射.UserBean(String, long, )
}
}
}
获取类的成员变量的字段信息:
getFiled: 访问公有的成员变量
getDeclaredField:所有已声明的成员变量。但不能得到其父类的成员变量
getFileds和getDeclaredFields用法同上(参照Method)
public class 打印成员变量 {
public static void main(String[] args) {
Class userBeanClass = UserBean.class;
//获得该类的所有成员变量,包括static private
Field[] fields = userBeanClass.getDeclaredFields();
for(Field field : fields) {
//private属性即使不用下面这个语句也可以访问
// field.setAccessible(true);
//因为类的私有域在反射中默认可访问,所以flag默认为true。
String fieldString = "";
fieldString += Modifier.toString(field.getModifiers()) + " "; // `private`
fieldString += field.getType().getSimpleName() + " "; // `String`
fieldString += field.getName(); // `userName`
fieldString += ";";
System.out.println(fieldString);
//打印结果
// public String userName;
// protected int i;
// static int j;
// private int l;
// private long userId;
}
}
}
使用invoke方法来调用类方法
这里有一个非常标准的示例,来源于:https://how2playlife.com/2019/09/12/12%E5%8F%8D%E5%B0%84/
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
public class 使用反射调用方法 {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchMethodException {
Class userBeanClass = UserBean.class;
//获取该类所有的方法,包括静态方法,实例方法。
//此处也包括了私有方法,只不过私有方法在用invoke访问之前要设置访问权限
//也就是使用setAccessible使方法可访问,否则会抛出异常
// // IllegalAccessException的解释是
// * An IllegalAccessException is thrown when an application tries
// * to reflectively create an instance (other than an array),
// * set or get a field, or invoke a method, but the currently
// * executing method does not have access to the definition of
// * the specified class, field, method or constructor.
// getDeclaredMethod*()获取的是类自身声明的所有方法,包含public、protected和private方法。
// getMethod*()获取的是类的所有共有方法,这就包括自身的所有public方法,和从基类继承的、从接口实现的所有public方法。
//就是说,当这个类,域或者方法被设为私有访问,使用反射调用但是却没有权限时会抛出异常。
Method[] methods = userBeanClass.getDeclaredMethods(); // 获取所有成员方法
for (Method method : methods) {
//反射可以获取方法上的注解,通过注解来进行判断
if (method.isAnnotationPresent(Invoke.class)) { // 判断是否被 @Invoke 修饰
//判断方法的修饰符是是static
if (Modifier.isStatic(method.getModifiers())) { // 如果是 static 方法
//反射调用该方法
//类方法可以直接调用,不必先实例化
method.invoke(null, "wingjay",2); // 直接调用,并传入需要的参数 devName
} else {
//如果不是类方法,需要先获得一个实例再调用方法
//传入构造方法需要的变量类型
Class[] params = {String.class, long.class};
//获取该类指定类型的构造方法
//如果没有这种类型的方法会报错
Constructor constructor = userBeanClass.getDeclaredConstructor(params); // 获取参数格式为 String,long 的构造函数
//通过构造方法的实例来进行实例化
Object userBean = constructor.newInstance("wingjay", 11); // 利用构造函数进行实例化,得到 Object
if (Modifier.isPrivate(method.getModifiers())) {
method.setAccessible(true); // 如果是 private 的方法,需要获取其调用权限
// Set the {@code accessible} flag for this object to
// * the indicated boolean value. A value of {@code true} indicates that
// * the reflected object should suppress Java language access
// * checking when it is used. A value of {@code false} indicates
// * that the reflected object should enforce Java language access checks.
//通过该方法可以设置其可见或者不可见,不仅可以用于方法
//后面例子会介绍将其用于成员变量
//打印结果
// I'm a public method
// Hi wingjay, I'm a static methodI'm a private method
}
method.invoke(userBean); // 调用 method,无须参数
}
}
}
}
}
这里还有一个更加通俗的,转载自https://blog.youkuaiyun.com/a695422768/article/details/73330951 ,感觉更加好理解一些:
public static void main(String[] args) throws Exception {
Class<?> class1 = null;
// 反射获取类实例,用的最多的就是jdbc获取驱动的时候就是用Class.forName("xxx");
// 一般采用这种形式
class1 = Class.forName("com.xxx.TestReflect");
// class1 = new TestReflect().getClass();
// class1 = TestReflect.class;
// 类实例化,到这里就可以访问TestReflect类的public属性的成员方法和成员变量了
TestReflect tr = (TestReflect) class1.newInstance();
// 通过java.lang.Class类得到一个Method对象
// api中java.lang.Class.getDeclaredMethod方法介绍
// 返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。
Method method = class1.getDeclaredMethod("mPrivate");
Method method1 = class1.getDeclaredMethod("mProtected");
//将此对象的 accessible 标志设置为指示的布尔值。
//值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
//值为 false 则指示反射的对象应该实施 Java 语言访问检查。
method.setAccessible(true);
method1.setAccessible(true);
// 调用该方法,注意是方法对象来invoke实例对象
method.invoke(tr);
method1.invoke(tr);
}
使用反射创建数组
要使用java.lang.reflect.Array类
public class 用反射创建数组 {
public static void main(String[] args) {
Class<?> cls = null;
try {
cls = Class.forName("java.lang.String");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Object array = Array.newInstance(cls,25);
//往数组里添加内容
Array.set(array,0,"hello");
Array.set(array,1,"Java");
Array.set(array,2,"fuck");
Array.set(array,3,"Scala");
Array.set(array,4,"Clojure");
//获取某一项的内容
System.out.println(Array.get(array,3));
//Scala
}
}