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<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);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,293评论 18 399
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 一、概述 Java反射机制定义 Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法...
    CoderZS阅读 1,597评论 0 26
  • 你是不是感觉金钱永远不够? 你是不是被世俗的要求和社会的竞争追赶着,感到身心疲惫? 你是不是想摆脱金钱带来的匮乏感...
    瀞好如琳阅读 3,465评论 0 0
  • @夏雨半支烟:买川投能源好过买国投电力,川投能源没那么疯狂扩张,资金充足,管理费用低。国投每年都会买火电站,卖火电...
    新兴市场的小逻辑阅读 102评论 0 0