一、概述
1、Java反射机制(Java-Reflect):
在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java的反射机制。
反射是Java开发中一个非常重要的概念,掌握了反射的知识,才能更好的学习Java高级课程.
2、Java 反射机制的功能
在运行时判断任意一个对象所属的类。
在运行时构造任意一个类的对象。
在运行时判断任意一个类所具有的成员变量和方法。
在运行时调用任意一个对象的方法。
生成动态代理。
3、Java 反射机制的应用场景
逆向代码 ,例如反编译
与注解相结合的框架 例如Retrofit
单纯的反射机制应用框架 例如EventBus
动态生成类框架 例如Gson
二、通过Java反射查看类信息
1、获得Class对象
每个类被加载之后,系统就会为该类生成一个对应的Class对象。通过该Class对象就可以访问到JVM中的这个类。
在Java程序中获得Class对象通常有如下三种方式:
使用 Class 类的
forName(String clazzName)
静态方法。该方法需要传入字符串参数,该字符串参数的值是某个类的全限定名(必须添加完整包名)。调用某个类的
class
属性来获取该类对应的 Class 对象。调用某个对象的
getClass()
方法。该方法是java.lang.Object类中的一个方法。
//第一种方式 通过Class类的静态方法——forName()来实现
class1 = Class.forName("com.lvr.reflection.Person");
//第二种方式 通过类的class属性
class1 = Person.class;
//第三种方式 通过对象getClass方法
Person person = new Person();
Class<?> class1 = person.getClass();
对于方式一和方式二都是直接根据类来取得该类的 Class 对象,相比之下,方式二有如下的两种优势:
- 代码跟安全。程序在编译阶段就能够检查需要访问的 Class 对象是否存在。
- 线程性能更好。因为这种方式无须调用方法,所以性能更好。
可以通过类的类类型创建该类的对象实例。
Class.newInstance();
//
Foot foot = (Foot) c1.newInstance();
2、从 Class 中获取信息
一旦获得了某个类所对应的Class 对象之后,就可以调用 Class 对象的方法来获得该对象的和该类的真实信息了。
获取 Class 对应类的成员变量
Field[] getDeclaredFields();
// 获取 Class 对象对应类的所有属性,与成员变量的访问权限无关。
Field[] getFields();
// 获取 Class 对象对应类的所有 public 属性。
Field getDeclaredField(String name);
// 获取 Class 对象对应类的指定名称的属性,与成员变量的访问权限无关。
Field getField(String name);
// 获取 Class 对象对应类的指定名称的 public 属性。获取 Class 对应类的方法
Method[] getDeclaredMethods();
// 获取 Class 对象对应类的所有声明方法,于方法的访问权限无关。
Method[] getMethods();
// 获取 Class 对象对应类的所有 public 方法,包括父类的方法。
Method getMethod(String name, Class<?>...parameterTypes);
// 返回此 Class 对象对应类的、带指定形参列表的 public 方法。
Method getDeclaredMethod(String name, Class<?>...parameterTypes);
// 返回此 Class 对象对应类的、带指定形参列表的方法,与方法的访问权限无关。获取 Class 对应类的构造函数
Constructor<?>[] getDeclaredConstructors();
// 获取 Class 对象对应类的所有声明构造函数,于构造函数的访问权限无关。
Constructor<?>[] getConstructors();
// 获取 Class 对象对应类的所有 public 构造函数。
Constructor<T> getDeclaredConstructor(Class<?>...parameterTypes);
// 返回此 Class 对象对应类的、带指定形参列表的构造函数,与构造函数的访问权限无关。
Constructor<T> getConstructor(Class<?>...parameterTypes);
// 返回此 Class 对象对应类的、带指定形参列表的 public 构造函数。获取 Class 对应类的 Annotation(注释)
<A extends Annotation>A getAnnotation(Class<A> annotationClass);
// 尝试获取该 Class 对对象对应类存在的、指定类型的 Annotation;如果该类型的注解不存在,则返回 null。
<A extends Annotation>A getDeclaredAnnotation(Class<A> annotationClass);
// 这是Java8新增的方法,该方法尝试获取直接修饰该 Class 对象对应类、指定类型的Annotation;如果该类型的注解不存在,则返回 null。
Annotation[] getAnnotations();
// 返回修饰该 Class 对象对应类存在的所有Annotation。
Annotation[] getDeclaredAnnotations();
// 返回直接修饰该 Class 对应类的所有Annotation。
<A extends Annotation>A[] getAnnotationsByType(Class<A> annotationClass);
// 获取修饰该类的、指定类型的多个Annotation。
<A extends Annotation>A[] getDeclaredAnnotationsByType(Class<A> annotationClass);
// 获取直接修饰该类的、指定类型的多个Annotation。获取 Class 对应类的内部类
Class<?>[] getDeclaredClasses();
// 返回该 Class 对象对应类包含的全部内部类。获取 Class 对应类的外部类
Class<?> getDeclaringClass();
// 返回该 Class 对象对应类所在的外部类。获取 Class 对应类所实现的接口
Class<?>[] getInterfaces();
获取 Class 对应类所继承的父类
Class<? super T> getSuperClass();
获取 Class 对应类的修饰符、所在包、类名等基本信息
int getModifiers();
// 返回此类或接口的所有修饰符。修饰符由 public、protected、private、final、static、abstract 等对应的常量组成,返回的整数应使用 Modifier 工具类的方法来解码,才可以获取真实的修饰符。
Package getPackage()
// 获取该类的包。
String getName()
// 以字符串的形式返回此 Class 对象所表示的类的名称。
String getSimpleName()
// 以字符串的形式返回此 Class 对象所表示的类的简称。判断该类是否为接口、枚举、注解类型等
boolean isAnnotation()
// 返此 Class 对象是否是一个注解类型(由@interface定义)。
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
// 判断此 Class 对象是否使用了Annotation修饰。
boolean isAnonymousClass()
// 此 Class 对象是否是一个匿名类。
boolean isArray()
//此 Class 对象是否是一个数组。
boolean isEnum()
// 此 Class 对象是否是一个枚举(由 enum 关键字定义)。
boolean isInterface()
// 此 Class 对象是否是一个接口(由 interface 关键字定义)。
boolean isInstance(Object obj)
// 判断 obj 是否是此 Class 对象的实例。该方法完全可以替代 instanceof 操作符。
3、Java 8 新增的方法——参数反射
Java 8 在java.lang.reflect包下新增了一个 Executable 抽象基类,该对象代表可执行的类成员,该类派生了 Constructor、Method 两个子类。
Executable 基类提供了大量的方法来获取修饰该方法或构造器的注解信息;还提供了
isVarArgs()
// 用于判断该方法或构造器是否包含数量可变的形参。
getModifiers()
// 获取该方法或构造器的修饰符。
此外,Executable 提供了如下两个方法:
int ParamenterCount()
// 获取该构造器或方法的形参个数。
Paramenter[] getParamenters()
// 获取该构造器或方法的所有形参。
上面的第二个方法返回了一个 Paramenter[] 数组,Paramenter也是 Java 8 新增的API,每个 Paramenter 对象代表方法或构造器的一个参数。
Paramenter 提供了大量方法来获取声明该参数的泛型信息,还提供了如下常用的方法来获取参数信息:
getModifiers()
// 获取修饰该形参的修饰符。
String getName()
// 获取形参名。
Type getParamenterizedType()
// 获取带泛型的形参类型。
Class<?> getType()
// 获取形参类型。
boolean isNamePresent()
// 返回该类的 class 文件中是否包含了方法的形参名信息。
boolean isVarArgs()
// 用于判断该参数是否为个数可变的形参。
三、使用反射生成并操作对象
Class 对象可以获得该类里的方法(由 Method 对象表示)、构造器(由 Constructor 对象表示)、成员变量(由 Field 对象表示),这三个类都位于 java.lang.reflect 包下。
程序可以通过 Method
对象来执行对应的方法,
通过 Constructor
对象来调用对应的构造器创建实例,
通过 Field
对象直接访问并修改对象的成员变量值。
1、创建对象
通过反射来生成对象的两种方式:
使用 Class 对象的
newInstance()
方法来创建该 Class 对象对应类的实例。
要求:Class 对象的对应类要有默认构造器,而执行newInstance()
方法实际上是利用默认构造器来创建该类的实例。先使用 Class 对象获取指定的 Constructor 对象,再调用 Constructor 对象的
newInstance()
方法来创建该 Class 对象对应类的实例。
这种方式可以选择使用指定的构造器来创建实例。
通过第一种方式来创建对象是比较常见的情形,在很多的 JavaEE 框架中都需要根据配置文件信息来创建Java对象。从配置文件中读取的只是某个类的字符串类名,程序需要根据该字符串来创建对应的实例,就必须使用到反射。
先建一个配置文件,obj.properties
a=java.util.Date
b=javax.swing.JFrame
/**
* 功能:实现一个简单的对象处
* 思路:该对象池会根据配置文件读取 key-value 对,然后创建这些对象,
* 并将这些对象放入到一个 HashMap 中。
* @author Administrator
*
*/
public class ObjectPoolFactory {
// 定义一个对象池,<对象名,实际对象>
private Map<String, Object> objectPool = new HashMap<>();
/**
* 创建对象
* @param className 字符串类名
* @return 返回对应的 Java 对象
* @throws ClassNotFoundException
* @throws InstantiationException
* @throws IllegalAccessException
*/
private Object createObject(String className)
throws ClassNotFoundException
, InstantiationException, IllegalAccessException {
// 获取对应的 Class 对象
Class<?> clazz = Class.forName(className);
// 使用对应类的默认构造器创建实例
return clazz.newInstance();
}
/**
* 根据指定文件来初始化对象池
* @param fileName 配置文件名
* @throws ClassNotFoundException
* @throws InstantiationException
* @throws IllegalAccessException
*/
public void initPool(String fileName)
throws ClassNotFoundException
, InstantiationException, IllegalAccessException {
try(FileInputStream fis = new FileInputStream(fileName)) {
Properties props = new Properties();
props.load(fis);
for (String name : props.stringPropertyNames()) {
objectPool.put(name, createObject(props.getProperty(name)));
}
} catch (IOException e) {
System.out.println("读取" + fileName + "异常!");
}
}
/**
* 根据指定的 name 值来获取对应的对象
* @param name
* @return
*/
public Object getObject(String name) {
return objectPool.get(name);
}
}
测试文件:
public class Test {
public static void main(String[] args)
throws ClassNotFoundException
, InstantiationException, IllegalAccessException {
ObjectPoolFactory opf = new ObjectPoolFactory();
opf.initPool("obj.properties");
System.out.println(opf.getObject("a"));
System.out.println(opf.getObject("b"));
}
}
运行的结果:
这种使用配置文件来配置对象,然后由程序根据配置文件来创建对象的方式非常有用,如 Spring 框架就采用这种方式大大简化了 JavaEE 应用的开发,当然,Spring采用的是 XML 配置文件——因为 XML 配置文件能配置的信息更加的丰富。
第二种方式,利用指定的构造器来创建 Java 对象。
- 获取该类的 Class 对象。
- 利用 Class 对象的
getConstrustor()
方法来获取指定的构造器。 - 调用 Construstor 的
newInstance()
方法来创建 Java 对象。
public class CreateObject {
public static void main(String[] args) throws Exception {
Class<?> jframeClass = Class.forName("javax.swing.JFrame");
Constructor<?> ctor = jframeClass.getConstructor(String.class);
Object obj = ctor.newInstance("测试窗口");
System.out.println(obj);
}
}
实际上,只有当程序需要动态创建某个类的对象时才会考虑使用反射,通常在开发通用性比较广的框架、基础平台时可能会大量使用反射。
2、调用方法
当获得某个类对应的 Class 对象后,就可以通过该 Class 对象的方法:
getMethods()
// 获取全部方法。
getMethod()
// 获取指定方法。
这两个方法的返回值是 Method 数组,或者 Method 对象。
每个 Method 对象对应一个方法,获得 Method 对象后,程序就可通过该 Method 来调用它对应的方法。在 Method 里包含一个 invoke()
方法:
Object invoke(Object obj, Object... args)
// 该方法中的 obj 是执行该方法的主调,后面的 args 是执行该方法时传入该方法的实参。
// 生成新的对象:用newInstance()方法
Object obj = class1.newInstance();
// 首先需要获得与该方法对应的Method对象
Method method = class1.getMethod("setAge", int.class);
// 调用指定的函数并传递参数
method.invoke(obj, 28);
当通过Method的invoke()
方法来调用对应的方法时,Java会要求程序必须有调用该方法的权限。如果程序确实需要调用某个对象的 private 方法,则可以先调用以下方法:
setAccessible(boolean flag)
// 值为true,指示该 Method 在使用时应该取消Java语言的访问权限检查;值为false,则知识该Method在使用时要实施Java语言的访问权限检查。
Method 、 Constructor 、Field 都可以调用该方法,从而实现通过反射来调用 private 方法、private 构造器、private 成员变量。
3、访问成员变量值
通过Class对象的:
getFields()
// 获取全部成员变量,返回 Field数组或对象。
getField()
// 获得指定成员变量,返回 Field数组或对象。
Field 提供了两组方法来读取或设置成员变量的值:
getXXX(Object obj)
// 获取obj对象的该成员变量的值。
setXXX(Object obj, XXX val)
// 将 obj 对象的该成员变量设置成val值。
//生成新的对象:用newInstance()方法
Object obj = class1.newInstance();
//获取age成员变量
Field field = class1.getField("age");
//将obj对象的age的值设置为10
field.setInt(obj, 10);
//获取obj对象的age的值
field.getInt(obj);