Java反射详细介绍

反射

目录介绍

  • 1.反射概述
    • 1.1 反射概述
    • 1.2 获取class文件对象的三种方式
    • 1.3 反射常用的方法介绍
    • 1.4 反射的定义
    • 1.5 反射的组成
    • 1.6 反射的作用有哪些
  • 2.反射的相关使用
    • 2.1.1 通过反射获取无参构造方法并使用
    • 2.1.2 通过反射获取带参构造方法并使用
    • 2.1.3 通过反射获取私有构造方法并使用
    • 2.1.4 通过反射获取成员变量并使用
    • 2.1.5 通过反射获取无参无返回值成员方法并使用
    • 2.1.6 通过反射获取带参无返回值成员方法并使用
    • 2.1.7 通过反射获取带参带返回值成员方法并使用
    • 2.1.8 通过反射获取无参带返回值成员方法并使用
  • 3.相关知识点
    • 3.1.1 设置setAccessible(true)暴力访问权限
    • 3.1.2 获取Filed两个方法的区别
    • 3.1.3 获取Field的类型
    • 3.1.4 Method获取方法名,获取方法参数
    • 3.1.5 Method方法的invoke()方法执行

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计47篇[近20万字],转载请注明出处,谢谢!
  • 链接地址:https://github.com/yangchong211/YCBlogs
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!

0.问题答疑

  • 0.1 被反射的类是否一定需要无参构造方法?为什么?
  • 0.2 反射的使用有什么优势和劣势?为什么说反射可以降低耦合?
  • 0.3 反射比较损耗性能,为什么这样说?能否通过案例对比说明反射机制损耗性能……
  • 0.4 反射是一种具有与类进行动态交互能力的一种机制,为什么要强调动态交互呢?
  • 0.5 Java反射中的setAccessible()方法是否破坏了类的访问规则
  • 0.2 反射的使用有什么优势和劣势?
    • 射的初衷不是方便你去创建一个对象,而是让你在写代码的时候可以更加灵活,降低耦合,提高代码的自适应能力。
  • 0.4 反射是一种具有与类进行动态交互能力的一种机制,为什么要强调动态交互呢
    • 动态加载,也就是在运行的时候才会加载,而不是在编译的时候,在需要的时候才进行加载获取,或者说你可以在任何时候加载一个不存在的类到内存中,然后进行各种交互,或者获取一个没有公开的类的所有信息,换句话说,开发者可以随时随意的利用反射的这种机制动态进行一些特殊的事情。

1.反射概述

1.1 反射概述

  • JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
  • 对于任意一个对象,都能够调用它的任意一个方法和属性;
  • 这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
  • 要想解剖一个类,必须先要获取到该类的字节码文件对象。
  • 而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象

1.2 获取class文件对象的三种方式

  • 1.2.1 这三种方式为:
  • a:Object类的getClass()方法
  • b:静态属性class
  • c:Class类中静态方法forName()
  • 1.2.2 第一种方法【Object类的getClass()方法】
    • 1.在内存中新建一个Person的实例,对象p对这个内存地址进行引用
    • 2.对象p调用getClass()返回对象p所对应的Class对
    • 3.调用newInstance()方法让Class对象在内存中创建对应的实例,并且让p2引用实例的内存地址
    Person p = new Person();
    Class<?> cls=p.getClass();
    Person p2=(Person)cls.newInstance();
    
  • 1.2.3 第二种方法【静态属性class】
    • 1.获取指定类型的Class对象,这里是Person
    • 2.调用newInstance()方法在让Class对象在内存中创建对应的实例,并且让p引用实例的内存地址
    Class<?> cls=Person.Class();
    Person p=(Person)cls.newInstance();
    
  • 1.2.4 第三种方法【Class类中静态方法forName()】
    • 1.通过JVM查找并加载指定的类(上面的代码指定加载了com.fanshe包中的Person类)
    • 2.调用newInstance()方法让加载完的类在内存中创建对应的实例,并把实例赋值给p
    • 注意:如果找不到时,它会抛出 ClassNotFoundException 这个异常,这个很好理解,因为如果查找的类没有在 JVM 中加载的话,自然要告诉开发者。
    Class<?> cls=Class.forName("com.yc.Person"); //forName(包名.类名)
    Person p= (Person) cls.newInstance();
    

1.3 通过反射获取无参构造方法并使用

  • A:获取所有构造方法
  • public Constructor<?>[] getConstructors()
  • public Constructor<?>[] getDeclaredConstructors()
  • B:获取单个构造方法
  • public Constructor<T> getConstructor(Class<?>... parameterTypes)
  • public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
  • 方法关键字
  • getDeclareMethods() 获取所有的方法
  • getReturnType() 获取方法的返回值类型
  • getParameterTypes() 获取方法的传入参数类型
  • getDeclareMethod("方法名,参数类型.class,....") 获得特定的方法
  • 构造方法关键字
  • getDeclaredConstructors() 获取所有的构造方法
  • getDeclaredConstructors(参数类型.class,....) 获取特定的构造方法
  • 成员变量
  • getDeclaredFields 获取所有成员变量
  • getDeclaredField(参数类型.class,....) 获取特定的成员变量
  • 父类和父接口
  • getSuperclass() 获取某类的父类
  • getInterfaces() 获取某类实现的接口

1.4 反射的定义

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

1.5 反射的组成

  • 由于反射最终也必须有类参与,因此反射的组成一般有下面几个方面组成:
    • 1.java.lang.Class.java:类对象;
    • 2.java.lang.reflect.Constructor.java:类的构造器对象;
    • 3.java.lang.reflect.Method.java:类的方法对象;
    • 4.java.lang.reflect.Field.java:类的属性对象;
  • 反射中类的加载过程
    • 根据虚拟机的工作原理,一般情况下,类需要经过:加载->验证->准备->解析->初始化->使用->卸载这个过程,如果需要反射的类没有在内存中,那么首先会经过加载这个过程,并在在内存中生成一个class对象,有了这个class对象的引用,就可以发挥开发者的想象力,做自己想做的事情了。

1.6 反射的作用有哪些

  • 前面只是说了反射是一种具有与Java类进行动态交互能力的一种机制,在Java和Android开发中,一般情况下下面几种场景会用到反射机制.
    • 需要访问隐藏属性或者调用方法改变程序原来的逻辑,这个在开发中很常见的,由于一些原因,系统并没有开放一些接口出来,这个时候利用反射是一个有效的解决方法
    • 自定义注解,注解就是在运行时利用反射机制来获取的。
    • 在开发中动态加载类,比如在Android中的动态加载解决65k问题等等,模块化和插件化都离不开反射,离开了反射寸步难行。

2.反射的相关使用

2.1.4 通过反射获取成员变量[包含私有]并使用

clazz = Class.forName("com.ycbjie.ycpaidian.four.ReflexUtils");
Constructor constructor = clazz.getDeclaredConstructor(Context.class);
//设置安全检查,访问私有构造函数必须
constructor.setAccessible(true);
Object[] obj = new Object[]{mContext};
//创建实例
ReflexUtils clazzObj = (ReflexUtils) constructor.newInstance(obj);


//反射修改私有变量
// 获取声明的 code 字段,这里要注意 getField 和 getDeclaredField 的区别
Field gradeField = clazz.getDeclaredField("code");
// 如果是 private 或者 package 权限的,一定要赋予其访问权限
gradeField.setAccessible(true);
// 修改 student 对象中的 Grade 字段值
gradeField.set(clazzObj, 2);
LogUtils.e("点击3----"+clazzObj.getCode());

2.1.5 通过反射获取无参无返回值成员方法[包含私有]并使用

clazz = Class.forName("com.ycbjie.ycpaidian.four.ReflexUtils");
Constructor constructor = clazz.getDeclaredConstructor(Context.class);
//设置安全检查,访问私有构造函数必须
constructor.setAccessible(true);
Object[] obj = new Object[]{mContext};
//创建实例
ReflexUtils clazzObj = (ReflexUtils) constructor.newInstance(obj);


// 获取私有方法,同样注意 getMethod 和 getDeclaredMethod 的区别
Method goMethod = clazz.getDeclaredMethod("getMethod");
// 赋予访问权限
goMethod.setAccessible(true);
// 调用 goToSchool 方法。
goMethod.invoke(clazzObj);

2.1.6 通过反射获取带参无返回值成员方法并使用

clazz = Class.forName("com.ycbjie.ycpaidian.four.ReflexUtils");
Constructor constructor = clazz.getDeclaredConstructor(Context.class);
//设置安全检查,访问私有构造函数必须
constructor.setAccessible(true);
Object[] obj = new Object[]{mContext};
//创建实例
ReflexUtils clazzObj = (ReflexUtils) constructor.newInstance(obj);

//这种不行,注意getDeclaredMethod和invoke方法需要传递参数
/*Method copyText = clazz.getDeclaredMethod("copyText");
copyText.setAccessible(true);
copyText.invoke(clazzObj,"测试复制这个功能");*/

// 获取私有方法,同样注意 getMethod 和 getDeclaredMethod 的区别
Method copyText = clazz.getDeclaredMethod("copyText",String.class);
// 赋予访问权限
copyText.setAccessible(true);
// 调用 copyText 方法。
copyText.invoke(clazzObj,"测试复制这个功能");

2.1.7 通过反射获取带参带返回值成员方法并使用

clazz = Class.forName("com.ycbjie.ycpaidian.four.ReflexUtils");
Constructor constructor = clazz.getDeclaredConstructor(Context.class);
//设置安全检查,访问私有构造函数必须
constructor.setAccessible(true);
Object[] obj = new Object[]{mContext};
//创建实例
ReflexUtils clazzObj = (ReflexUtils) constructor.newInstance(obj);

//这种不行,注意getDeclaredMethod和invoke方法需要传递参数
/*Method copyText = clazz.getDeclaredMethod("copyText");
copyText.setAccessible(true);
copyText.invoke(clazzObj,"测试复制这个功能");*/

// 获取私有方法,同样注意 getMethod 和 getDeclaredMethod 的区别
Method copyText1 = clazz.getDeclaredMethod("copyText",String.class,String.class);
// 赋予访问权限
copyText1.setAccessible(true);
// 调用 copyText 方法
boolean isSuccess = (boolean) copyText1.invoke(clazzObj,"测试复制这个功能","1111");

3.相关知识点

3.1.1 设置.setAccessible(true)暴力访问权限
  • 一般情况下,我们并不能对类的私有字段进行操作,利用反射也不例外,但有的时候,例如要序列化的时候,我们又必须有能力去处理这些字段,这时候,我们就需要调用AccessibleObject上的setAccessible()方法来允许这种访问,而由于反射类中的Field,Method和Constructor继承自AccessibleObject,因此,通过在这些类上调用setAccessible()方法,我们可以实现对这些字段的操作。
Field gradeField = clazz.getDeclaredField("code");
// 如果是 private 或者 package 权限的,一定要赋予其访问权限
gradeField.setAccessible(true);

Method goMethod = clazz.getDeclaredMethod("getMethod");
// 赋予访问权限
goMethod.setAccessible(true);

3.1.2 获取Filed两个方法的区别

  • 两者的区别就是 getDeclaredField() 获取的是 Class 中被 private 修饰的属性。 getField() 方法获取的是非私有属性,并且 getField() 在当前 Class 获取不到时会向祖先类获取。
//获取所有的属性,但不包括从父类继承下来的属性
public Field[] getDeclaredFields() throws SecurityException {}

//获取自身的所有的 public 属性,包括从父类继承下来的。
public Field[] getFields() throws SecurityException {}

3.1.3 获取Field的类型

  • 可以看到 getGenericType() 确实把泛型都打印出来了,它比 getType() 返回的内容更详细。
public Type getGenericType() {}
public Class<?> getType() {}

Field[] filed2 = clazz.getFields();
for ( Field f : filed2 ) {
    System.out.println("Field :"+f.getName());
    System.out.println("Field type:"+f.getType());
    System.out.println("Field generic type:"+f.getGenericType());
    System.out.println("-------------------");
}

//打印值
07-31 17:20:41.027 8700-8700/com.ycbjie.ycpaidian I/System.out: Field :cars
07-31 17:20:41.027 8700-8700/com.ycbjie.ycpaidian I/System.out: Field type:interface java.util.List
07-31 17:20:41.028 8700-8700/com.ycbjie.ycpaidian I/System.out: Field generic type:java.util.List<com.ycbjie.ycpaidian.four.Car>
07-31 17:20:41.028 8700-8700/com.ycbjie.ycpaidian I/System.out: Field :map
07-31 17:20:41.028 8700-8700/com.ycbjie.ycpaidian I/System.out: Field type:class java.util.HashMap
07-31 17:20:41.028 8700-8700/com.ycbjie.ycpaidian I/System.out: Field generic type:java.util.HashMap<java.lang.Integer, java.lang.String>
07-31 17:20:41.028 8700-8700/com.ycbjie.ycpaidian I/System.out: Field :name
07-31 17:20:41.028 8700-8700/com.ycbjie.ycpaidian I/System.out: Field type:class java.lang.String
07-31 17:20:41.028 8700-8700/com.ycbjie.ycpaidian I/System.out: Field generic type:class java.lang.String

3.1.4 Method获取方法名,获取方法参数

//获取方法名


//获取方法参数
//返回的是一个 Parameter 数组,在反射中 Parameter 对象就是用来映射方法中的参数。
public Parameter[] getParameters() {}


//Method获取方法名
Method[] declaredMethods1 = clazz.getDeclaredMethods();
for ( Method m : declaredMethods1 ) {
    System.out.println("method name:"+m.getName());
}
//获取方法参数
for ( Method m : declaredMethods1 ) {
    System.out.println("获取方法参数method name:"+m.getName());
    //获取参数
    Parameter[] paras;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
        paras = m.getParameters();
        for (Parameter c : paras ) {
            System.out.println("获取参数parameter :"+c.getName()+" "+c.getType().getName());
        }
    }
    //获取所有的参数类型
    Class[] pTypes = m.getParameterTypes();
    for ( Class c : pTypes ) {
        System.out.print("参数类型method para types:"+ c.getName());
    }
    System.out.println();
    System.out.println("==========================================");
}

//打印日志如下所示:
07-31 19:23:13.191 4022-4022/com.ycbjie.ycpaidian I/System.out: ==========================================
07-31 19:23:13.191 4022-4022/com.ycbjie.ycpaidian I/System.out: 获取方法参数method name:copyText
07-31 19:23:13.191 4022-4022/com.ycbjie.ycpaidian I/System.out: 获取参数parameter :arg0 java.lang.String
07-31 19:23:13.191 4022-4022/com.ycbjie.ycpaidian I/System.out: 参数类型method para types:java.lang.String
07-31 19:23:13.191 4022-4022/com.ycbjie.ycpaidian I/System.out: ==========================================
07-31 19:23:13.191 4022-4022/com.ycbjie.ycpaidian I/System.out: 获取方法参数method name:copyText
07-31 19:23:13.191 4022-4022/com.ycbjie.ycpaidian I/System.out: 获取参数parameter :arg0 java.lang.String
07-31 19:23:13.191 4022-4022/com.ycbjie.ycpaidian I/System.out: 获取参数parameter :arg1 java.lang.String
07-31 19:23:13.191 4022-4022/com.ycbjie.ycpaidian I/System.out: 参数类型method para types:java.lang.String参数类型method para types:java.lang.String
07-31 19:23:13.191 4022-4022/com.ycbjie.ycpaidian I/System.out: ==========================================
07-31 19:23:13.191 4022-4022/com.ycbjie.ycpaidian I/System.out: 获取方法参数method name:getCode
07-31 19:23:13.192 4022-4022/com.ycbjie.ycpaidian I/System.out: ==========================================
07-31 19:23:13.192 4022-4022/com.ycbjie.ycpaidian I/System.out: 获取方法参数method name:getUserInfo
07-31 19:23:13.192 4022-4022/com.ycbjie.ycpaidian I/System.out: 获取参数parameter :arg0 java.lang.String
07-31 19:23:13.192 4022-4022/com.ycbjie.ycpaidian I/System.out: 参数类型method para types:java.lang.String
07-31 19:23:13.192 4022-4022/com.ycbjie.ycpaidian I/System.out: ==========================================
07-31 19:23:13.192 4022-4022/com.ycbjie.ycpaidian I/System.out: 获取方法参数method name:setCode
07-31 19:23:13.192 4022-4022/com.ycbjie.ycpaidian I/System.out: 获取参数parameter :arg0 int
07-31 19:23:13.192 4022-4022/com.ycbjie.ycpaidian I/System.out: 参数类型method para types:int
07-31 19:23:13.192 4022-4022/com.ycbjie.ycpaidian I/System.out: ==========================================

3.1.5 Method方法的invoke()方法执行

  • Method 调用 invoke() 的时候,存在许多细节:
    • invoke() 方法中第一个参数 Object 实质上是 Method 所依附的 Class 对应的类的实例,如果这个方法是一个静态方法,那么 ojb 为 null,后面的可变参数 Object 对应的自然就是参数。
    • invoke() 返回的对象是 Object,所以实际上执行的时候要进行强制转换。
    • 在对Method调用invoke()的时候,如果方法本身会抛出异常,那么这个异常就会经过包装,由Method统一抛InvocationTargetException。而通过InvocationTargetException.getCause() 可以获取真正的异常。
关于我的博客
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容