Introduction
内容源自对慕课网视频Java反射教程的笔记
慕课网教程-反射——Java高级开发必须懂的
Class类的使用
- 类是对象,类是java.lang.Class的对象
- 如何表示这个对象
public class ClassDemo1 {
// Foo的实例对象的表示
Foo foo = new Foo();
// 万事万物皆对象,那么Foo这个类怎么作为对象表示出来
}
class Foo {}
我们进入Class的源代码,可以看到一个私有的构造函数(这个构造函数可能因为JDK的版本不同而不同)
/*
* Private constructor. Only the Java Virtual Machine creates Class objects.
* This constructor is not used and prevents the default constructor being
* generated.
*/
private Class(ClassLoader loader) {
// Initialize final field for classLoader. The initialization value of non-null
// prevents future JIT optimizations from assuming this final field is null.
classLoader = loader;
}
说得很明确了只有JVM才能访问。
要想把Foo这个类作为对象表示出来有三种方式,内容基本都在注释里面了
package com.nevercome;
/**
* @author: sun
* @date: 2019/4/6
*/
public class ClassDemo1 {
public static void main(String[] args) {
// Foo的实例对象的表示
// Foo foo = new Foo("1");
Foo foo = new Foo();
// 万事万物皆对象,Foo这个类怎么作为对象表示出来
// 任何一个类都是Class的实例对象,但是我我们无法用构造函数的方式
// 但其实有三种方式
// 1. .class
Class c1 = Foo.class; // 这说明所有的类,都有一个为class的静态变量
// 2. 已知该类的对象 getClass()
Class c2 = foo.getClass();
// 来梳理一下概念
// 根据官网的定义: c1,c2 表示了Foo类的类类型(class type)
// 类类型指的就是类自己,它是Class类的一个实例,如Foo类的类类型就是c1
// c1是它作为Class类的实例(对象),我们称之为该类(这里是Foo)的类类型
// c1 和 c2都是Foo的类类型,一个类作为Class类的实例只可能有一个实例对象
System.out.println(c1 == c2); // true
Class c3 = null;
try {
c3 = Class.forName("com.nevercome.Foo");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(c2 == c3); // true
// 我们还可以通过类的类类型来创建该类的对象
// 这要求该类必须有无参的构造函数(隐式和显示皆可)
// 不然会抛出NoSuchMethodException
try {
Foo foo1 = (Foo)c1.newInstance();
Foo foo2 = (Foo)c2.newInstance();
foo.print();
foo1.print();
foo2.print();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
class Foo {
// public Foo(String name) {
//
// }
public void print() {
System.out.println("foo");
}
}
动态加载类 类的反射
上述说的第三种方式Class.forName(“类的权限定名”),不仅代表了类的类类型,还代表了动态加载类。编译时刻加载类是静态加载类,运行时刻加载类是动态加载类。
看一下下面这个Office类:
public class Office {
public static void mian(String[] args) {
if("Word".equals(args[0])) {
Word w = new Word();
w.start();
}
if("Excel".equals(args[0])) {
Excel e = new Excel();
e.start();
}
}
}
使用javac命令编译这个Office类,毫无疑问,它会报错。因为我们没有Word和Excel类。那么如果你有了Word类呢,当然还是会报错,因为你没有Excel。仔细想一下这个问题,这是否和你的真实需求相背离了呢?我们希望有Word类,Word的功能就可以使用,而不是因为Excel缺失了而所有的Office全家桶都无法使用了。这就是静态加载类(new 都是静态加载),编译时刻要求加载所需的全部类的缺陷。我们希望只有在我们需要使用他们的时候,他们才被加载,Class.forName()可以完成这个任务。
public class OfficeBetter {
public static void main(String[] args) {
try {
// 动态加载类 运行时加载
Class c = Class.forName(args[0]);
// 通过类类型创建对象
// 强制类型转换? Word 还是 Excel
// 没错!需要接口或者继承
OfficeAble oa = (OfficeAble) c.newInstance();
oa.start();
} catch(Exception e) {
e.printStackTrace();
}
}
}
public interface OfficeAble {
void start();
}
public class Excel implements OfficeAble {
public void start() {
System.out.println("excel start");
}
}
public class Word implements OfficeAble {
public void start() {
System.out.println("word start");
}
}
这样子我们就实现了功能上的彼此独立与动态扩展。
获取方法信息 方法的反射
基本数据类型、void关键字都可以获取类的类类型
public class ClassDemo2 {
public static void main(String[] args) {
Class c1 = int.class; // int数据类型 的类类型
Class c2 = String.class; // String类的 类类型
Class c3 = double.class; // double数据类型 的类类型
Class c4 = Double.class; // Double类 的类类型
Class c5 = void.class; // void关键字的 类类型
// 只要是可以在类内被定义的,就都可以获取到它的类类型
// Class c6 = package.class; // 会报错
System.out.println(c1.getName()); // int
System.out.println(c2.getName()); // java.lang.String
System.out.println(c2.getSimpleName()); // String
System.out.println(c3.getName()); // double
System.out.println(c4.getName()); // java.lang.Double
System.out.println(c5.getName()); // void
}
}
public class ClassUtils {
/**
* 打印类的信息:包括类的成员函数 成员变量
* @param obj 该对象所属类的信息
*/
public static void printClassInfo(Object obj) {
// 1. 要获取类的信息 首先要获取类的类类型
// 这里我们传入的是一个Object参数 那么我可以使用obj.getClass()方法
// Object是所有对象的父类 那么这里传递的是哪个子类 获取到的就是那个子类的类类型
// 其底层实现使用了Native方法来实现...Native方法是Java声明底层用C语言去实现的 目前我们不关心它的底层原理
Class c = obj.getClass();
System.out.println("类的全限定名是:" + c.getName());
// 2. 要获取成员函数...而方法是Method类的对象
/*
* Method类,方法对象
* 一个成员方法就是一个Method对象
* getMethods() 获取的是所有public的方法,包括从父类继承而来
* getDeclaredMethods() 获取的是该类自己声明的方法,不包括符类,不限定访问权限
*/
Method[] methods = c.getMethods();
// c.getDeclaredMethods();
for(Method method : methods) {
// 1. 获取方法的返回类型
Class returnType = method.getReturnType(); // 返回的是类类型
System.out.print(returnType.getName() + " ");
// 2. 获取方法的名称
System.out.print(method.getName() + "(");
// 3. 获取参数列表(类型)
Class[] paramTypes = method.getParameterTypes(); // 返回的是参数列表的类类型
for(int i=0; i<paramTypes.length; i++) {
if(i == paramTypes.length - 1) {
System.out.print(paramTypes[i].getName());
} else {
System.out.print(paramTypes[i].getName() + ", ");
}
}
System.out.println(")");
}
}
}
获取构造函数与成员变量信息 构造函数与成员变量的反射
上面说到了获取方法信息,接着来看构造函数与成员变量。
/**
* 打印成员变量的信息
* @param obj
*/
public static void printFieldInfo(Object obj) {
/*
* 成员变量也是对象
* 是java.lang.reflect.Field的对象
* getFields() 获取的是public 包括父类
* getDeclaredFields() 所有自己声明的
*/
Class c = obj.getClass();
System.out.println("类的成员变量:");
Field[] fields = c.getFields();
Field[] fields1 = c.getDeclaredFields();
for(Field field : fields1) {
Class fieldType = field.getType(); // 成员变量类型的类类型
String typeName = fieldType.getName();
String filedName = field.getName(); // 成员变量的名字 如int a 即为a
System.out.println(fieldType + " " + filedName);
}
}
和方法的获取基本是一致的。
同样的,构造函数也是基本一致的
/**
* 打印构造函数的信息
* @param obj
*/
public static void printContructInfo(Object obj) {
Class c = obj.getClass();
/*
* 构造函数也是对象
* java.lang.Constructor 封装了构造函数的信息
* getConstructors() public
* getDeclaredConstructors() 自己声明的
*/
// Constructor[] constructors = c.getConstructors();
Constructor[] constructors = c.getDeclaredConstructors();
System.out.println("类的全限定名是:" + c.getName());
System.out.println("类的自己声明的构造函数:");
for(Constructor constructor : constructors) {
// 获取构造函数名
System.out.print(constructor.getName() + "(");
// 参数列表
Class[] paramTypes = constructor.getParameterTypes();
for (int i = 0; i < paramTypes.length; i++) {
if (i == paramTypes.length - 1) {
System.out.print(paramTypes[i].getName());
} else {
System.out.print(paramTypes[i].getName() + ", ");
}
}
System.out.println(")");
}
}
当然Class还有很多其它的API…诸如获取到父类…包名等待,要记住的一点是要使用Class的接口就必须要先获取到类类型。
方法的反射
- 获取某一个方法 getMethod(名称, 参数列表)
- 方法反射的操作 method.invoke(对象, 参数列表)
public class MethodDemo1 {
public static void main(String[] args) {
// 要获取到方法 首先要得到类的类类型
A a = new A();
Class c = a.getClass();
// 获取什么样的方法
// print(int,int)
// getMethod 共有方法
// getDeclaredMethod 声明的方法
try {
// ...参数类型
// Method method = c.getMethod("print", new Class[]{int.class, int.class});
Method m = c.getMethod("print", int.class, int.class);
a.print(1, 2);
try {
// 有返回值则获取返回值 没有则为null
Object o = m.invoke(a, 1, 2);
System.out.println(o);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
try {
Method m = c.getMethod("print", String.class, String.class);
m.invoke(a, "hello", "java");
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
try {
Method m = c.getMethod("print");
m.invoke(a);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
class A {
public void print() {
System.out.println("Hello World");
}
public void print(int a, int b) {
System.out.println(a+b);
}
public void print(String a, String b) {
System.out.println(a.toUpperCase() + " " + b.toUpperCase());
}
}
通过反射来认识集合泛型的本质
集合泛型是设计来规定输入的
public class ClassMethod4 {
public static void main(String[] args) {
ArrayList list = new ArrayList();
ArrayList<String> list1 = new ArrayList<>();
list1.add("hello");
// list1.add(1); // 当然会报错 集合泛型规定了只能放入String
// 但是
Class c = list.getClass();
Class c1 = list1.getClass();
System.out.println(c == c1); // true
/*
* 反射的操作都是编译之后的操作
* c1 == c2 说明编译之后集合的泛型是去泛型化的
* Java中集合的泛型是防止错误输入的 绕过编译 泛型就不起作用了
* 我们可以用方法的泛型来绕过编译
*/
try {
Method m = c.getMethod("add", Object.class);
m.invoke(list1, 100);
System.out.println(list1.size()); // 2
System.out.println(list1); // [hello, 100]
// 那么这样子在获取数据的时候就造成了困难
// 我们很难用foreach和for来遍历集合
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
ReflectonUtils
来看一个Reflection的工具类,里面有一些最基本的用法
代码出自JeeSite
获取对象的DeclaredMethod
/**
* 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
* 如向上转型到Object仍无法找到, 返回null.
* 只匹配函数名。
*
* 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
*/
public static Method getAccessibleMethodByName(final Object obj, final String methodName) {
Validate.notNull(obj, "object can't be null");
Validate.notBlank(methodName, "methodName can't be blank");
for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
Method[] methods = searchType.getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
makeAccessible(method);
return method;
}
}
}
return null;
}
/**
* 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
* 如向上转型到Object仍无法找到, 返回null.
* 只匹配函数名。
*
* 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
*/
public static Method getAccessibleMethodByName(final Object obj, final String methodName) {
Validate.notNull(obj, "object can't be null");
Validate.notBlank(methodName, "methodName can't be blank");
for (Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) {
Method[] methods = searchType.getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
makeAccessible(method);
return method;
}
}
}
return null;
}
其中的Validate出自org.apache.commons.lang3,方法的意思很好理解,getMethods和getDeclaredMethod一个只能获取共有方法,一个只能是自己声明的。那么我们通过getSuperclass获取父类,继续调用父类的getDeclaredMethod来找到方法,并最后返回method。
其中的makeAccessible
/**
* 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
*/
public static void makeAccessible(Method method) {
if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
&& !method.isAccessible()) {
method.setAccessible(true);
}
}
对应的调用方式
/**
* 直接调用对象方法, 无视private/protected修饰符.
* 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用.
* 同时匹配方法名+参数类型,
*/
public static Object invokeMethod(final Object obj, final String methodName, final Class<?>[] parameterTypes, final Object[] args) {
Method method = getAccessibleMethod(obj, methodName, parameterTypes);
if (method == null) {
throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
}
try {
return method.invoke(obj, args);
} catch (Exception e) {
throw convertReflectionExceptionToUnchecked(e);
}
}
/**
* 直接调用对象方法, 无视private/protected修饰符,
* 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用.
* 只匹配函数名,如果有多个同名函数调用第一个。
*/
public static Object invokeMethodByName(final Object obj, final String methodName, final Object[] args){
Method method = getAccessibleMethodByName(obj, methodName);
if (method == null) {
throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
}
try {
return method.invoke(obj, args);
} catch (Exception e) {
throw convertReflectionExceptionToUnchecked(e);
}
}
获取对象的DeclaredFeild
/**
* 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问.
*
* 如向上转型到Object仍无法找到, 返回null.
*/
public static Field getAccessibleField(final Object obj, final String fieldName) {
Validate.notNull(obj, "object can't be null");
Validate.notBlank(fieldName, "fieldName can't be blank");
for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass=superClass.getSuperclass()) {
try {
Field field = superClass.getDeclaredField(fieldName);
makeAccessible(field);
return field;
} catch (NoSuchFieldException e) {
// Field不在当前类定义,继续向上转型
continue;
}
}
return null;
}
/**
* 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。
*/
public static void makeAccessible(Field field) {
if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier
.isFinal(field.getModifiers())) && !field.isAccessible()) {
field.setAccessible(true);
}
}
对应的读取对象属性值的方法
/**
* 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数.
*/
public static Object getFieldValue(final Object obj, final String fieldName) {
Field field = getAccessibleField(obj, fieldName);
if (field == null) {
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
}
Object result = null;
try {
result = field.get(obj);
} catch (IllegalAccessException e) {
logger.error("不可能抛出的异常{}", e.getMessage());
}
return result;
}
/**
* 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数.
*/
public static void setFieldValue(final Object obj, final String fieldName, final Object value) {
Field field = getAccessibleField(obj, fieldName);
if (field == null) {
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
}
try {
field.set(obj, value);
} catch (IllegalAccessException e) {
logger.error("不可能抛出的异常:{}", e.getMessage());
}
}