Java反射的使用

参考文档:https://www.jianshu.com/p/e55770dd48d3

 

一、概述

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

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

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

因为所有的状态都是在运行时确定下来的,所以读取Class中信息的时候,就必须要使用try-catch或者抛异常

最低的异常是IllegalAccessException -> ReflectiveOperationException -> Exception

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<T> getDeclaredConstructor(Class<?>...parameterTypes); // 返回此 Class 对象对应类的、带指定形参列表的构造函数,与构造函数的访问权限无关。
    Constructor<T> getConstructor(Class<?>...parameterTypes); // 返回此 Class 对象对应类的、带指定形参列表的 public 构造函数。

  4. 获取 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。

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

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

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

  8. 获取 Class 对应类所继承的父类
    Class<? super T> 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<? 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 抽象基类,该对象代表可执行的类成员,该类派生了 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<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 对象。

  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);

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值