Java--反射机制(一)——反射 API

一、概述

1、Java反射机制(Java-Reflect):

在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java的反射机制

反射是Java开发中一个非常重要的概念,掌握了反射的知识,才能更好的学习Java高级课程.

2、Java 反射机制的功能
  1. 在运行时判断任意一个对象所属的类。

  2. 在运行时构造任意一个类的对象。

  3. 在运行时判断任意一个类所具有的成员变量和方法。

  4. 在运行时调用任意一个对象的方法。

  5. 生成动态代理。

3、Java 反射机制的应用场景
  1. 逆向代码 ,例如反编译

  2. 与注解相结合的框架 例如Retrofit

  3. 单纯的反射机制应用框架 例如EventBus

  4. 动态生成类框架 例如Gson

二、通过Java反射查看类信息

1、获得Class对象

每个类被加载之后,系统就会为该类生成一个对应的Class对象。通过该Class对象就可以访问到JVM中的这个类。

在Java程序中获得Class对象通常有如下三种方式:

  1. 使用 Class 类的forName(String clazzName)静态方法。该方法需要传入字符串参数,该字符串参数的值是某个类的全限定名(必须添加完整包名)。

  2. 调用某个类的class属性来获取该类对应的 Class 对象。

  3. 调用某个对象的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 对象的方法来获得该对象的和该类的真实信息了。

  1. 获取 Class 对应类的成员变量
    Field[] getDeclaredFields(); // 获取 Class 对象对应类的所有属性,与成员变量的访问权限无关。
    Field[] getFields(); // 获取 Class 对象对应类的所有 public 属性。
    Field getDeclaredField(String name); // 获取 Class 对象对应类的指定名称的属性,与成员变量的访问权限无关。
    Field getField(String name); // 获取 Class 对象对应类的指定名称的 public 属性。

  2. 获取 Class 对应类的方法
    Method[] getDeclaredMethods(); // 获取 Class 对象对应类的所有声明方法,于方法的访问权限无关。
    Method[] getMethods(); // 获取 Class 对象对应类的所有 public 方法,包括父类的方法。
    Method getMethod(String name, Class...parameterTypes); // 返回此 Class 对象对应类的、带指定形参列表的 public 方法。
    Method getDeclaredMethod(String name, Class...parameterTypes); // 返回此 Class 对象对应类的、带指定形参列表的方法,与方法的访问权限无关。

  3. 获取 Class 对应类的构造函数
    Constructor[] getDeclaredConstructors(); // 获取 Class 对象对应类的所有声明构造函数,于构造函数的访问权限无关。
    Constructor[] getConstructors(); // 获取 Class 对象对应类的所有 public 构造函数。
    Constructor getDeclaredConstructor(Class...parameterTypes); // 返回此 Class 对象对应类的、带指定形参列表的构造函数,与构造函数的访问权限无关。
    Constructor getConstructor(Class...parameterTypes); // 返回此 Class 对象对应类的、带指定形参列表的 public 构造函数。

  4. 获取 Class 对应类的 Annotation(注释)
    A getAnnotation(Class annotationClass); // 尝试获取该 Class 对对象对应类存在的、指定类型的 Annotation;如果该类型的注解不存在,则返回 null。
    A getDeclaredAnnotation(Class annotationClass); // 这是Java8新增的方法,该方法尝试获取直接修饰该 Class 对象对应类、指定类型的Annotation;如果该类型的注解不存在,则返回 null。
    Annotation[] getAnnotations(); // 返回修饰该 Class 对象对应类存在的所有Annotation。
    Annotation[] getDeclaredAnnotations(); // 返回直接修饰该 Class 对应类的所有Annotation。
    A[] getAnnotationsByType(Class annotationClass); // 获取修饰该类的、指定类型的多个Annotation。
    A[] getDeclaredAnnotationsByType(Class annotationClass); // 获取直接修饰该类的、指定类型的多个Annotation。

  5. 获取 Class 对应类的内部类
    Class[] getDeclaredClasses(); // 返回该 Class 对象对应类包含的全部内部类。

  6. 获取 Class 对应类的外部类
    Class getDeclaringClass(); // 返回该 Class 对象对应类所在的外部类。

  7. 获取 Class 对应类所实现的接口
    Class[] getInterfaces();

  8. 获取 Class 对应类所继承的父类
    Class getSuperClass();

  9. 获取 Class 对应类的修饰符、所在包、类名等基本信息
    int getModifiers(); // 返回此类或接口的所有修饰符。修饰符由 public、protected、private、final、static、abstract 等对应的常量组成,返回的整数应使用 Modifier 工具类的方法来解码,才可以获取真实的修饰符。
    Package getPackage() // 获取该类的包。
    String getName() // 以字符串的形式返回此 Class 对象所表示的类的名称。
    String getSimpleName() // 以字符串的形式返回此 Class 对象所表示的类的简称。

  10. 判断该类是否为接口、枚举、注解类型等
    boolean isAnnotation() // 返此 Class 对象是否是一个注解类型(由@interface定义)。
    boolean isAnnotationPresent(Class 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 抽象基类,该对象代表可执行的类成员,该类派生了 ConstructorMethod 两个子类。

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、创建对象

通过反射来生成对象的两种方式:

  1. 使用 Class 对象的 newInstance() 方法来创建该 Class 对象对应类的实例。
    要求:Class 对象的对应类要有默认构造器,而执行 newInstance() 方法实际上是利用默认构造器来创建该类的实例。

  2. 先使用 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 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 对象。

  1. 获取该类的 Class 对象。
  2. 利用 Class 对象的 getConstrustor() 方法来获取指定的构造器。
  3. 调用 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);

你可能感兴趣的:(Java--反射机制(一)——反射 API)