Java 反射技术应用
![Java 反射](https://i-blog.csdnimg.cn/blog_migrate/2e81666576a7312e7c82f41ad0b447e4.png)
什么是反射
反射的概念是由 Smith 在 1982 年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。
Java中的Reflection是Java程序开发语言的特性之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性。
Class类
Class
类的实例创建并不由开发人员确定,而是由JVM进行实例化,Class
类代码类型运行时的实例;获取一个类的Class
类型实例有如下几种方式:
//方式1
Class<String> class1=String.class;
//方式2
Class<? extends String> class2 = new String().getClass();
try {
//方式3
Class<?> class3 = ClassLoader.getSystemClassLoader().loadClass("java.lang.String");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
- 方式1
- 直接通过类型的常量字段
class
进行获取,获取到的类型为Class<String>
;
- 直接通过类型的常量字段
- 方式2
- 通过类型实例的
getClass
方法进行获取;获取到的类型为Class<? extends String>
;
- 通过类型实例的
- 方式3
- 通过
ClassLoader
对类载入的方式获取;获取到的类型为Class<?>
;
- 通过
通过以上方法获取的同一类型的Class
实例是相同的;可以通过以下方式进行验证:
System.out.println(
"class1 == class2 ? " + (class1 == class2) + "\n" +
"class2 == class3 ? " + (class2 == class3));
Class
类型包含了一个类的所有信息;这些信息包含类的继承关系、字段信息、方法信息、泛型信息、注解等信息;下面用示例来讲解常用方法:
Class<String> strClass=String.class;
System.out.println("是否继承类型Serializable:"+strClass.isAssignableFrom(Serializable.class));
System.out.println("获取Data注解:"+strClass.getAnnotation(Data.class));
System.out.println("是否为注解类型:"+strClass.isAnnotation());
System.out.println("是否为数组:"+strClass.isArray());
System.out.println("是否为枚举:"+strClass.isEnum());
System.out.println("是否为接口:"+strClass.isInterface());
System.out.println("获取构建函数:"+strClass.getConstructor());
System.out.println("获取函数:"+strClass.getMethods());
System.out.println("获取字段:"+strClass.getFields());
System.out.println("获取类型名称:"+strClass.getName());
根据类型创建类实例
通过扫描类路径下的包可以动态的将包内容加载至JVM;那怎样才能创建出这个包中某个类的实例呢?
可以借助Class
类来实现上面的需求,首先使用ClassLoader
获取到类的Class
类型实例;再通过Class
类获取构造函数;再使用构造函数创建出类的实例;下面我们还是以String
类为例子来写这个示例:
public void demo() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//通过ClassLoader加载String类型并获取`Class`实例
Class<String> stringClass = (Class<String>) ClassLoader.getSystemClassLoader().loadClass("java.lang.String");
//输出Class类实例名称
System.out.println(stringClass.getName());
//获取构造函数
Constructor<String> constructor = stringClass.getConstructor(String.class);
//通过构造函数创建实例
String string = constructor.newInstance("Reflect String");
System.out.println("Instance value:" + string);
}
通过上面的这些步骤就实现的使用Java反射特性创建一个String实例的例子。
反射获取类型方法
在定义一个类类型时通常会给这个类添加一些函数,类也会从父类继承函数,那怎样通过反射获取一个类的方法呢?
Class<?> clazz =ClassLoader.getSystemClassLoader().loadClass("java.lang.String");
//获取类型声明的方法 公有的
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("Declared method:"+declaredMethod.getName());
}
//获取这个类型声明的方法和继承的方法 公有的
Method[] methods1 = clazz.getMethods();
for (Method method : methods1) {
System.out.println("method:"+method.getName());
}
getDeclaredMethods
这个方法获取类自身声明的所有公有方法;getMethods
这个方法获取类型自身声明的所有公有方法和通过继承获取的所有公有方法
另外这两个方法还有重载方法,可以通过指定方法名称和参数类型更精确的获取需要的方法类型;当没有找到需要的方法时抛出NoSuchMethodException
;这个异常需要手动的去捕获并处理它。
反射获取类型成员变量
上面讲解了获取类型方法的示例;那获取成员变量两样也有两个方法进行获取;作用与命名的方式也是大致相同的:
Class<?> clazz =ClassLoader.getSystemClassLoader().loadClass("java.lang.String");
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("Declared Field:"+declaredField.getName());
}
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println("Field:"+field.getName());
}
同的获取成员变量的方法getDeclaredFields
与getFields
都有重载方法,通过成员变量名称获取;
下面来做更多的事情;将一个Map对象转换成一个指定的类型;Map中存储类型中的所有字段的值,通过使用反射来将这些值初始化类型。
使用这样的一个需求来总结下Java反射的使用:
public void demo() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Map<String,Object> mapValues=new HashMap<>();
mapValues.put("id",10);
mapValues.put("number",120);
mapValues.put("name","test");
String className = ClassA.class.getName();
Class<?> clazz =ClassLoader.getSystemClassLoader().loadClass(className);
System.out.println(clazz.getName());
//获取构造函数
Constructor<?> constructor = clazz.getConstructor();
//通过构造函数创建实例
Object classAInstance = constructor.newInstance();
//获取所有公共方法
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if(method.getName().startsWith("set")){
//是set方法时对名称做处理
String name = method.getName();
String filed = name.replaceFirst("set", "");
Object value=null;
//将处理后的名称在mapValues中获取value值
if(filed.charAt(0)>=81&&filed.charAt(0)<=106){
value=mapValues.get(new String((filed.charAt(0)+32)+filed.substring(1)));
}else{
value=mapValues.get(filed);
}
if(value!=null){
//调用invoke进行赋值
method.invoke(classAInstance,value);
}
}
}
System.out.println(classAInstance);
}
public class ClassA{
private Long id;
private Long number;
private String name;
//Ignore get set method
@Override
public String toString() {
return "\nid"+getId()+
"\nnumber"+getNumber()+
"\nname"+getName();
}
}
为了减少篇幅删除了ClassA
的get
set
方法的声明。
示例中通过ClassLoader
获取ClassA
类的类型实例;再使用getMethods
的方法获取类型实例中的所有方法,通过对方法进行循环后对方法名做一定的处理获取到名称;通过名称在mapValues
中获取对应的值;再将值通过反射调用的形式(Method#invoke
方法)调用set
方法赋值;最后完成了ClassA
对象类的实例创建与赋值。
这个过程是不是与Json
数据的序列化与反序列化有着相似这处;在序列化与反序列化的过程中对于Java反射有着大量的应用。
往期推荐:
深入Java异常 详细解析JVM异常原理
Java 8种基本值类型
Java String StringPool StringBuilder StringBuffer详解