概述
From:Java 高级特性 --- 反射:https://www.jianshu.com/p/9be58ee20dee
From:Java 基础之 --- 反射(非常重要):https://blog.csdn.net/sinat_38259539/article/details/71799078
From:Java 高级 / 反射机制:https://how2j.cn/k/reflection/reflection-class/108.html
From:Java 中反射机制详解:https://www.cnblogs.com/whgk/p/6122036.html
From:深入分析 Java 方法反射的实现原理:https://www.jianshu.com/p/3ea4a6b57f87
Java 反射 - 超详细讲解(附源码):https://blog.csdn.net/lililuni/article/details/83449088 https://github.com/lililuni/reflect-demo
ReflectDemo:https://gitee.com/peter_RD_nj/DemoAllInOne/tree/master/ReflectDemo
Java中泛型 Class<T>、T 与 Class<?>、 Object类 和 Class类、 object.getClass() 和 Object.class:https://blog.csdn.net/freeking101/article/details/109247406
1. 概述
反射是框架设计的灵魂
使用前提条件:必须先得到代表的字节码的Class,Class类 用于表示 .class文件 ( 字节码 )
注意:在运行期间,一个类,只有一个Class对象产生。
1.1 定义
JAVA反射机制( https://baike.baidu.com/item/JAVA反射机制/6015990 )是:
在运行状态中,
- 对于任意一个类,都能够获取到这个类的所有属性和方法;
- 对于任意一个对象 ( 即 类的实例 ),都能够调用它的任意方法和属性 (包括 私有的方法 和 属性);
- 这种 动态获取信息 以及 动态调用对象方法 的功能 称为 java 语言的反射机制。
想要使用反射机制,必须先 获取到该类的 字节码文件对象(.class),而反射使用的就是 Class类 中的方法。
通过 字节码文件对象 就能够通过该类中的方法获取到我们想要的所有信息 ( 方法,属性,类名,父类名,实现的所有接口等等)
每一个类 对应着 一个字节码文件 也就对应着 一个Class类型的对象,也就是 字节码文件对象。
反射就是把 编写的java类 中的各种成分映射成一个个的Java对象
例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把类的所有组成都映射成一个个对象。
Class对象是JVM将 class文件 读入内存时并创建的一个Class对象,其中这个 Class对象 很特殊。我们先了解一下这个 Class类
二、查看 Class类 在 java 中的 api 详解(1.7的API)
Java8 API 中文:https://www.matools.com/api/java8
Java8 API 英文:https://docs.oracle.com/javase/8/docs/api/
如何阅读 java 中的 api 详见 java基础之 --- String字符串处理
Class
类的实例表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象。(包括基本数据类型)
Class
没有公共构造方法。Class
对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass
方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了。
没有公共的构造方法,方法共有64个太多了。下面用到哪个就详解哪个吧
1.2 反射的用途和使用
在日常的第三方应用开发过程中,经常会遇到 某个类 的 某个成员变量、方法 或是 属性 是私有的或是只对系统应用开放,这时候就可以利用 Java 的反射机制通过反射来获取所需的私有成员或是方法。当然,也不是所有的都适合反射,之前就遇到一个案例,通过反射得到的结果与预期不符。阅读源码发现,经过层层调用后在最终返回结果的地方对应用的权限进行了校验,对于没有权限的应用返回值是没有意义的缺省值,否则返回实际值,这样就起到保护用户隐私的目的。
假设我们现在有一个 Hero类
package pojo;
public class Hero {
public String name; //姓名
public float hp; //血量
public float armor; //护甲
public int moveSpeed; //移动速度
}
1、获取类对象
获取 类对象 ( 即 字节码文件对象 ) 的 3种方式
- Class.forName() // 常用。传入 类名的字符串 即可得到 类对象
通过Class类的静态方法: forName(String className)
// 通过Class类中的静态方法forName,直接获取到一个类的字节码文件对象,
// 此时该类还是源文件阶段,并没有变为字节码文件。
Class clazz1 = Class.forName("全限定类名"); - Hero.class // 需要导入类的包,依赖太强,不导包就抛编译错误
( 任何数据类型(包括基本数据类型)都有一个 "静态" 的 class属性 )
// 当类被加载成.class文件时,此时Person类变成了.class,
// 在获取该字节码文件对象,也就是获取自己, 该类处于字节码阶段。
Class clazz2 = Person.class; - new Hero().getClass() // 对象都有了还要反射干什么。。。
( Object --> getClass(); 因为 所有类 都继承 Object类。从而调用 Object类 的 getClass方法 来获取 )
// 通过类的实例获取该类的字节码文件对象,该类处于创建对象阶段
Class clazz3 = p.getClass();
有了 字节码文件对象 才能获得类中所有的信息,上面最常用的是: Class clazz1 = Class.forName("全限定类名");
在一个JVM中,一种类,只会有一个类对象存在。所以以上 3种方式取出来的类对象,都是一样。(此处准确是在ClassLoader下,只有一个类对象)
package pojo;
public class ObjectTest {
public static void main(String[] args) {
String className = "pogo.Hero";
try {
// 获取类对象的第一种方式
Class pClass1 = Class.forName(className);
// 获取类对象的第二种方式
Class pClass2 = Hero.class;
// 获取类对象的第三种方式
Class pClass3 = new Hero().getClass();
System.out.println(pClass1 == pClass2); //输出true
System.out.println(pClass1 == pClass3); //输出true
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
注意:在运行期间,一个类,只有一个Class对象产生。
2、Class类 的 API 详解
2.1、通过字节码对象创建实例对象
2.2、获取指定构造器方法。constructor 如果没有无参构造,只有有参构造如何创建实例呢?
总结上面创建实例对象:
Class类 的 newInstance() 方法是使用该类无参的构造函数创建对象,如果一个类没有无参的构造函数, 就不能这样创建了。
可以调用Class类的 getConstructor(String.class, int.class) 方法获取一个指定的构造函数,然后再调用 Constructor类的 newInstance("张三",20)方法创建对象
获取全部构造方法
2.3、获取 成员变量 并使用 Field对象
获取指定成员变量
Class.getField(String)方法可以获取类中的指定字段(可见的), 如果是私有的可以用 getDeclaedField("name")方法获取,通过 set(obj, "李四")方法可以设置指定对象上该字段的值, 如果是私有的需要先调用 setAccessible(true) 设置访问权限,用获取的指定的字段调用 get(obj) 可以获取指定对象中该字段的值
获取全部成员变量
2.4、获得方法并使用 Method
Class.getMethod(String, Class...) 和 Class.getDeclaredMethod(String, Class...)方法可以获取类中的指定方法,
如果为私有方法,则需要使用 setAccessible(true); 打开一个权限。用 invoke(Object, Object...) 可以调用该方法,跟上面同理,也能一次性获得所有的方法
2.5、获得该类的所有接口
Class[] getInterfaces():确定此对象所表示的类或接口实现的接口
返回值:接口的字节码文件对象的数组
2.6、获取指定资源的输入流
InputStream getResourceAsStream(String name)
return:一个 InputStream 对象;如果找不到带有该名称的资源,则返回 null
参数:所需资源的名称,如果以"/"开始,则绝对资源名为"/"后面的一部分。
2.7、动态代理的概述和实现
动态代理:一种设计模式,其非常简单,很容易理解,你自己可以做这件事,但是觉得自己做非常麻烦或者不方便,所以就叫另一个人(代理)来帮你做这个事情,而你就不用管了,这就是动态代理。举个例子,买火车票叫人代买。
程序运行过程中产生的这个对象,其实就是我们刚才反射讲解的内容,所以,动态代理其实就是通过反射来生成一个代理
在Java中 java.lang.reflect 包下提供了一个 Proxy类 和 一个InvocationHandler接口,通过使用 这个类 和 接口 就可以生成动态代理对象。
JDK提供的代理只能针对接口做代理。我们有更强大的代理 cglib,Proxy类中的方法创建动态代理类对象分三步,但是注意JDK提供的代理只能针对接口做代理,也就是下面的第二步返回的必须要是一个接口。
- 1、new出代理对象,通过实现InvacationHandler接口,然后new出代理对象来。
- 2、通过Proxy类中的静态方法newProxyInstance,来将代理对象假装成那个被代理的对象,也就是如果叫人帮我们代买火车票一样,那个代理就假装成我们自己本人
- 3、执行方法,代理成功
将代理对象中的内容进行实现
1、2、3步
注意 newProxyInstance 的三个参数,第一个,类加载器,第二个被代理对象的接口,第三个代理对象。
2.8、还有很多方法,比如获得类加载器,等等,具体还需要别的,就通过查看API文档来解决。
3、反射机制的应用实例
应用实例 1:
3.1、利用反射,在泛型为 int 的 arryaList 集合中存放一个 String 类型的对象
原理:集合中的泛型只在编译器有效,而到了运行期,泛型则会失效,
3.2、利用反射,简化编写Servlet的个数。
什么意思呢?每当我们写一个功能时,就需要写一个对应的Servlet,导致最后Servlet有很多,自己都看不过来,所以对其进行了优化,两种方式,
3.2.1、每次从页面传过来一个参数,method="xxx"; 然后编写一个Servlet,获得其参数method的值,进行判断,如果是add,则调用add方法,如果是delete,则调用delete方法,这样就可以写在一个servlet中实现所有的功能了。
3.2.2、利用反射
编写一个BaseServlet 继承 HttpServlet,这是一个通用的 BaseServlet。需要明白 servlet 的生命周期
编写具体实现的方法 servlet 类。
MySerlvet001 extends BaseServlet
解释:需要明白servlet的生命周期,也就是service方法,因为是servlet,所以在访问的时候,会经过service方法,而子类MyServlet001中并没有,所以就到父类BaseServlet中找,发现有,然后获取参数即知道了需要调用什么方法,因为方法的编写都在子类中,所以通过反射,获取到子类中对应的方法并运行,其中需要注意的是this这个参数在BaseServlet中的用法。需要理解它。才能理解我们这个程序。
对其一些api进行讲解,不懂的就查看API,重要的思想,就要在实际中遇到了才能得到更好的理解。
应用实例 2:
反射非常强大,但是学习了之后,会不知道该如何使用,反而觉得还不如直接调用方法来的直接和方便。
通常来说,需要在学习了 Spring 的依赖注入,反转控制之后,才会对反射有更好的理解,但是刚学到这里的同学,不一定接触了Spring,所以在这里举两个例子,来演示一下反射的一种实际运用。
步骤 1 : 业务类:https://how2j.cn/k/reflection/reflection-usage/1111.html#step4262
步骤 2 : 非反射方式:https://how2j.cn/k/reflection/reflection-usage/1111.html#step4263
步骤 3 : 反射方式:https://how2j.cn/k/reflection/reflection-usage/1111.html#step4264
- 步骤 1 : 业务类
首先准备两个业务类,这两个业务类很简单,就是各自都有一个业务方法,分别打印不同的字符串
Service1.java
package reflection;
public class Service1 {
public void doService2(){
System.out.println("111111111111 业务方法1");
}
}
Service2.java
package reflection;
public class Service2 {
public void doService2(){
System.out.println("222222222222 业务方法2");
}
}
- 步骤 2 : 非反射方式
当需要从第一个业务方法切换到第二个业务方法的时候,使用非反射方式,必须修改代码,并且重新编译运行,才可以达到效果
package reflection;
public class Test {
public static void main(String[] args) {
new Service1().doService1();
}
}
修改后代码:
package reflection;
public class Test {
public static void main(String[] args) {
// new Service1().doService1();
new Service1().doService2(); // 这里修改为调用 doService2
}
}
- 步骤 3 : 反射方式
使用反射方式,首先准备一个配置文件,就叫做 spring.txt 吧,放在 src 目录下。 里面存放的是类的名称,和要调用的方法名。
spring.txt
class=reflection.Service1
method=doService1
在测试类Test中,首先取出类名称和方法名,然后通过反射去调用这个方法。
package reflection;
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Properties;
public class Test {
@SuppressWarnings({"rawtypes", "unchecked"})
public static void main(String[] args) throws Exception {
//从spring.txt中获取类名称和方法名称
File springConfigFile = new File("e:\project\j2se\src\spring.txt");
Properties springConfig = new Properties();
springConfig.load(new FileInputStream(springConfigFile));
String className = (String) springConfig.get("class");
String methodName = (String) springConfig.get("method");
//根据类名称获取类对象
Class clazz = Class.forName(className);
//根据方法名称,获取方法对象
Method m = clazz.getMethod(methodName);
//获取构造器
Constructor c = clazz.getConstructor();
//根据构造器,实例化出对象
Object service = c.newInstance();
//调用对象的指定方法
m.invoke(service);
}
}
当需要从调用第一个业务方法,切换到调用第二个业务方法的时候,不需要修改一行代码,也不需要重新编译,只需要修改配置文件 spring.txt,再运行即可。
这也是 Spring框架 的最基本的原理,只是它做的更丰富,安全,健壮。
1.3 反射机制的相关类
与 Java 反射相关的类如下:
类名 | 用途 |
---|---|
Class 类 | 代表 类的实体,在运行的 Java 应用程序中表示 类和接口 |
Field 类 | 代表 类的成员变量(成员变量 也称为 类的属性) |
Method 类 | 代表 类的方法 |
Constructor 类 | 代表 类的构造方法 |
1.4 利用反射机制 创建对象
基本步骤
与传统的通过 new 来获取对象的方式不同反射机制,反射会先拿到 Hero 的 "类对象",然后通过类对象获取 "构造器对象" ,再通过构造器对象创建一个对象,具体步骤:
1. 获取类对象
Class class = Class.forName("pojo.Hero");
2. 获取构造器对象Constructor con = clazz.getConstructor(形参.class);
3. 获取对象Hero hero =con.newInstance(实参);
上面是最简单的获取方法,当 Hero 的 构造方法 不是无参构造方法时,获取构造器对象略有不同,见下面测试:
构造方法不同时,获取构造器对象的方法
示例:
- Hero 类 添加 6 种构造方法
package pojo;
public class Hero {
public String name; //姓名
public float hp; //血量
public float armor; //护甲
public int moveSpeed; //移动速度
//---------------构造方法-------------------
Hero(String str) { // 默认的构造方法
System.out.println("(默认)的构造方法 s = " + str);
}
public Hero() { // 无参构造方法
System.out.println("调用了公有、无参构造方法执行了。。。");
}
public Hero(char name) { // 有一个参数的构造方法
System.out.println("姓名:" + name);
}
public Hero(String name, float hp) { // 有多个参数的构造方法
System.out.println("姓名:" + name + "血量:" + hp);
}
protected Hero(boolean n) { // 受保护的构造方法
System.out.println("受保护的构造方法 n = " + n);
}
private Hero(float hp) { // 私有构造方法
System.out.println("私有的构造方法 血量:" + hp);
}
}
- 通过反射机制获取对象
package test;
public class ConstructorTest {
/*
* 通过Class对象可以获取某个类中的:构造方法、成员变量、成员方法;并访问成员;
*
* 1.获取构造方法:
* 1).批量的方法:
* // 所有"公有的"构造方法
* public Constructor[] getConstructors()
* // 获取所有的构造方法(包括私有、受保护、默认、公有)
* public Constructor[] getDeclaredConstructors()
*
* 2).获取单个的方法,并调用:
* // 获取单个的 "公有的" 构造方法:
* public Constructor getConstructor(Class... parameterTypes)
* // 获取"某个构造方法"可以是私有的,或受保护、默认、公有;
* public Constructor getDeclaredConstructor(Class... parameterTypes)
*
* 2.创建对象
* // Constructor 对象调用
* obj = clazz.getDeclaredConstructor(float.class);
* obj.newInstance(Object... init_args)
*/
public static void main(String[] args) throws Exception {
//1.加载Class对象
Class clazz = Class.forName("pojo.Hero");
//2.获取所有公有构造方法
System.out.println("**********所有公有构造方法**********");
Constructor[] conArray = clazz.getConstructors();
for(Constructor c : conArray){
System.out.println(c);
}
System.out.println("*****所有的构造方法(包括:私有、受保护、默认、公有)*****");
conArray = clazz.getDeclaredConstructors();
for(Constructor c : conArray){
System.out.println(c);
}
System.out.println("**********获取公有、无参的构造方法**********");
Constructor con = clazz.getConstructor(null);
//1>、因为是无参的构造方法所以类型是一个null,不写也可以:这里需要的是一个参数的类型,切记是类型
//2>、返回的是描述这个无参构造函数的类对象。
System.out.println("con = " + con);
//调用构造方法
Object obj = con.newInstance();
System.out.println("**********获取私有构造方法,并调用**********");
con = clazz.getDeclaredConstructor(float.class);
System.out.println(con);
//调用构造方法
con.setAccessible(true); //暴力访问(忽略掉访问修饰符)
obj = con.newInstance(100);
}
}
输出:
**********************所有公有构造方法*********************************
public pojo.Hero(java.lang.String,float)
public pojo.Hero(char)
public pojo.Hero()
************所有的构造方法(包括:私有、受保护、默认、公有)***************
private pojo.Hero(float)
protected pojo.Hero(boolean)
public pojo.Hero(java.lang.String,float)
public pojo.Hero(char)
public pojo.Hero()
pojo.Hero(java.lang.String)
*****************获取公有、无参的构造方法*******************************
con = public pojo.Hero()
调用了公有、无参构造方法执行了。。。
******************获取私有构造方法,并调用*******************************
private pojo.Hero(float)
私有的构造方法 血量:100.0
总结:
1. 获取 构造器对象方法:
1). 批量的方法:
public Constructor[] getConstructors(): 所有 "公有的" 构造方法
public Constructor[] getDeclaredConstructors(): 获取所有的构造方法 ( 包括 私有、受保护、默认、公有)
2). 获取单个的方法:
public Constructor getConstructor(Class… parameterTypes): 获取单个的 "公有的" 构造方法
public Constructor getDeclaredConstructor(Class…parameterTypes): 获取 "某个构造方法" 可以是 私有的,或受保护、默认、公有;2、 调用构造方法:Constructor --> newInstance(Object... initargs)
newInstance 是 Constructor类的方法(管理构造函数的类)
api 的解释为:
newInstance(Object... initargs)
使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。
它的返回值是T类型,所以newInstance是创建了一个构造方法的声明类的新实例对象。并为之调用
1.5 获取成员变量并使用
基本步骤
1. 获取 HeroPlus类 的对象
new方法/第2章中的方法
h
2. 获取属性Field f1 = h.getDeclaredField("属性名")
3. 修改属性f1.set(h,实参)
,注意这里的 h 是 对象,不是 类对象
示例:
- 新增 HeroPlus 类
package pojo;
public class HeroPlus {
public String name;
public float hp;
public int damage;
public int id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public HeroPlus(){
}
public HeroPlus(String string) {
name =string;
}
@Override
public String toString() {
return "Hero [name=" + name + "]";
}
public boolean isDead() {
// TODO Auto-generated method stub
return false;
}
public void attackHero(HeroPlus h2) {
System.out.println(this.name+ " 正在攻击 " + h2.getName());
}
}
- 获取属性并修改
package test;
public class ParaTest {
public static void main(String[] args) {
HeroPlus h = new HeroPlus();
//使用传统方式修改name的值为garen
h.name = "garen";
try {
//获取类HeroPlus的名字叫做name的字段
Field f1 = h.getClass().getDeclaredField("name");
//修改这个字段的值
f1.set(h, "teemo");
//打印被修改后的值
System.out.println(h.name);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
补充:getField 和 getDeclaredField 的区别:
- getField 只能获取public 的,包括从父类继承来的字段。
- getDeclaredField 可以获取本类所有的字段,包括 private 的,但是 不能获取继承来的字段。 ( 注: 这里只能获取到private的字段,但并不能访问该private字段的值,除非加上setAccessible(true))
示例 2:
student类:
package fanshe.field;
public class Student {
public Student() {
}
//**********字段*************//
public String name;
protected int age;
char sex;
private String phoneNum;
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", sex=" + sex
+ ", phoneNum=" + phoneNum + "]";
}
}
测试类:
package fanshe.field;
import java.lang.reflect.Field;
/*
* 获取成员变量并调用:
*
* 1.批量的
* 1).Field[] getFields():获取所有的"公有字段"
* 2).Field[] getDeclaredFields():获取所有字段,包括:私有、受保护、默认、公有;
* 2.获取单个的:
* 1).public Field getField(String fieldName):获取某个"公有的"字段;
* 2).public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的)
*
* 设置字段的值:
* Field --> public void set(Object obj,Object value):
* 参数说明:
* 1.obj:要设置的字段所在的对象;
* 2.value:要为字段设置的值;
*
*/
public class Fields {
public static void main(String[] args) throws Exception {
//1.获取Class对象
Class stuClass = Class.forName("fanshe.field.Student");
//2.获取字段
System.out.println("************获取所有公有的字段********************");
Field[] fieldArray = stuClass.getFields();
for (Field f : fieldArray) {
System.out.println(f);
}
System.out.println("************获取所有的字段(包括私有、受保护、默认的)********************");
fieldArray = stuClass.getDeclaredFields();
for (Field f : fieldArray) {
System.out.println(f);
}
System.out.println("*************获取公有字段**并调用***********************************");
Field f = stuClass.getField("name");
System.out.println(f);
//获取一个对象
Object obj = stuClass.getConstructor().newInstance();//产生Student对象--》Student stu = new Student();
//为字段设置值
f.set(obj, "刘德华");//为Student对象中的name属性赋值--》stu.name = "刘德华"
//验证
Student stu = (Student) obj;
System.out.println("验证姓名:" + stu.name);
System.out.println("**************获取私有字段****并调用********************************");
f = stuClass.getDeclaredField("phoneNum");
System.out.println(f);
f.setAccessible(true);//暴力反射,解除私有限定
f.set(obj, "18888889999");
System.out.println("验证电话:" + stu);
}
}
后台输出:
************获取所有公有的字段********************
public java.lang.String fanshe.field.Student.name
************获取所有的字段(包括私有、受保护、默认的)********************
public java.lang.String fanshe.field.Student.name
protected int fanshe.field.Student.age
char fanshe.field.Student.sex
private java.lang.String fanshe.field.Student.phoneNum
*************获取公有字段**并调用***********************************
public java.lang.String fanshe.field.Student.name
验证姓名:刘德华
**************获取私有字段****并调用********************************
private java.lang.String fanshe.field.Student.phoneNum
验证电话:Student [name=刘德华, age=0, sex=
由此可见
调用字段时:需要传递两个参数:
- 第一个参数:要传入设置的对象
- 第二个参数:要传入实参
// 产生Student对象 ---> Student stu = new Student();
Object obj = stuClass.getConstructor().newInstance();
//为字段设置值。为Student对象中的 name 属性赋值 ---> stu.name = "刘德华"
f.set(obj, "刘德华");
1.6 获取成员方法并使用
- 获取 HeroPlus 类的对象
h
- 获取成员方法:
public Method getMethod(String name ,Class<?>… parameterTypes):获取"公有方法";(包含了父类的方法也包含Object类)
public Method getDeclaredMethods(String name ,Class<?>… parameterTypes) :获取成员方法,包括私有的(不包括继承的)
参数解释:
name : 方法名;
Class … : 形参的Class类型对象- 调用方法
Method --> public Object invoke(Object obj,Object… args):
参数说明:
obj : 要调用方法的对象;
args:调用方式时所传递的实参;
示例:
package test;
public class MethodTest {
public static void main(String[] args) {
HeroPlus h = new HeroPlus();
try {
// 获取这个名字叫做setName,参数类型是String的方法
Method m = h.getClass().getMethod("setName", String.class);
// 对h对象,调用这个方法
m.invoke(h, "盖伦");
// 使用传统的方式,调用getName方法
System.out.println(h.getName());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
示例 2:
student类:
package fanshe.method;
public class Student {
//**************成员方法***************//
public void show1(String s) {
System.out.println("调用了:公有的,String参数的show1(): s = " + s);
}
protected void show2() {
System.out.println("调用了:受保护的,无参的show2()");
}
void show3() {
System.out.println("调用了:默认的,无参的show3()");
}
private String show4(int age) {
System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = " + age);
return "abcd";
}
}
测试类:
package fanshe.method;
import java.lang.reflect.Method;
/*
* 获取成员方法并调用:
*
* 1.批量的:
* public Method[] getMethods():获取所有"公有方法";(包含了父类的方法也包含Object类)
* public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)
* 2.获取单个的:
* public Method getMethod(String name,Class<?>... parameterTypes):
* 参数:
* name : 方法名;
* Class ... : 形参的Class类型对象
* public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
*
* 调用方法:
* Method --> public Object invoke(Object obj,Object... args):
* 参数说明:
* obj : 要调用方法的对象;
* args:调用方式时所传递的实参;
):
*/
public class MethodClass {
public static void main(String[] args) throws Exception {
//1.获取Class对象
Class stuClass = Class.forName("fanshe.method.Student");
//2.获取所有公有方法
System.out.println("***************获取所有的”公有“方法*******************");
stuClass.getMethods();
Method[] methodArray = stuClass.getMethods();
for (Method m : methodArray) {
System.out.println(m);
}
System.out.println("***************获取所有的方法,包括私有的*******************");
methodArray = stuClass.getDeclaredMethods();
for (Method m : methodArray) {
System.out.println(m);
}
System.out.println("***************获取公有的show1()方法*******************");
Method m = stuClass.getMethod("show1", String.class);
System.out.println(m);
//实例化一个Student对象
Object obj = stuClass.getConstructor().newInstance();
m.invoke(obj, "刘德华");
System.out.println("***************获取私有的show4()方法******************");
m = stuClass.getDeclaredMethod("show4", int.class);
System.out.println(m);
m.setAccessible(true);//解除私有限定
Object result = m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参
System.out.println("返回值:" + result);
}
}
由此可见:
// 调用制定方法(所有包括私有的),需要传入两个参数,
// 第一个是调用的方法名称,
// 第二个是方法的形参类型,切记是类型。
m = stuClass.getDeclaredMethod("show4", int.class);
System.out.println(m);
// 解除私有限定
m.setAccessible(true);
// 需要两个参数,一个是要调用的对象(获取有反射),一个是实参
Object result = m.invoke(obj, 20);
System.out.println("返回值:" + result);//
控制台输出:
***************获取所有的”公有“方法*******************
public void fanshe.method.Student.show1(java.lang.String)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
***************获取所有的方法,包括私有的*******************
public void fanshe.method.Student.show1(java.lang.String)
private java.lang.String fanshe.method.Student.show4(int)
protected void fanshe.method.Student.show2()
void fanshe.method.Student.show3()
***************获取公有的show1()方法*******************
public void fanshe.method.Student.show1(java.lang.String)
调用了:公有的,String参数的show1(): s = 刘德华
***************获取私有的show4()方法******************
private java.lang.String fanshe.method.Student.show4(int)
调用了,私有的,并且有返回值的,int参数的show4(): age = 20
返回值:abcd
其实这里的成员方法:在模型中有属性一词,就是那些 setter()方法 和 getter()方法。还有字段组成,这些内容在 内省 中详解
1.7 获取 main 方法并使用
示例:
- HeroPlus 新增main方法
public static void main(String[] args) {
System.out.println("执行main方法");
}
- 通过下面步骤获取 main方法
package test;
public class MainTest {
public static void main(String[] args) {
try {
//1、获取HeroPlus对象的字节码
Class clazz = Class.forName("pojo.HeroPlus");
//2、获取main方法,第一个参数:方法名称,第二个参数:方法形参的类型,
Method methodMain = clazz.getMethod("main", String[].class);
//3、调用main方法
// methodMain.invoke(null, new String[]{"a","b","c"});
// 第一个参数,对象类型,因为方法是static静态的,所以为null可以,
// 第二个参数是String数组,这里要注意在jdk1.4时是数组,jdk1.5之后是可变参数
// 这里拆的时候将 new String[]{"a","b","c"} 拆成3个对象。所以需要将它强转。
methodMain.invoke(null, (Object) new String[]{"a", "b", "c"});//方式一
// methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});//方式二
} catch (Exception e) {
e.printStackTrace();
}
}
}
示例 2:
student类:
package fanshe.main;
public class Student {
public static void main(String[] args) {
System.out.println("main方法执行了。。。");
}
}
测试类:
package fanshe.main;
import java.lang.reflect.Method;
/**
* 获取Student类的main方法、不要与当前的main方法搞混了
*/
public class Main {
public static void main(String[] args) {
try {
//1、获取Student对象的字节码
Class clazz = Class.forName("fanshe.main.Student");
//2、获取main方法。
// 第一个参数:方法名称,第二个参数:方法形参的类型,
Method methodMain = clazz.getMethod("main", String[].class);
//3、调用main方法
// methodMain.invoke(null, new String[]{"a","b","c"});
// 第一个参数,对象类型,因为方法是static静态的,所以为null可以,
// 第二个参数是String数组,这里要注意在jdk1.4时是数组,jdk1.5之后是可变参数
// 这里拆的时候将 new String[]{"a","b","c"} 拆成3个对象。。。所以需要将它强转。
methodMain.invoke(null, (Object) new String[]{"a", "b", "c"});//方式一
// methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});//方式二
} catch (Exception e) {
e.printStackTrace();
}
}
}
控制台输出:main方法执行了。。。
深入分析 Java 方法反射的实现原理
From:https://www.jianshu.com/p/3ea4a6b57f87
从一起GC血案谈到反射原理 ( https://mp.weixin.qq.com/s/5H6UHcP6kvR2X5hTj_SBjA ) ,就把 Java 方法的反射机制实现撸了一遍。
方法反射实例
public class ReflectCase {
public static void main(String[] args) throws Exception {
Proxy target = new Proxy();
Method method = Proxy.class.getDeclaredMethod("run");
method.invoke(target);
}
static class Proxy {
public void run() {
System.out.println("run");
}
}
}
通过 Java 的反射机制,可以在运行期间调用对象的任何方法,如果大量使用这种方式进行调用,会有性能或内存隐患么?为了彻底了解方法的反射机制,只能从底层代码入手了。
Method 获取
调用 Class 类的 getDeclaredMethod 可以获取 指定方法名和参数 的 方法对象 Method。
getDeclaredMethod
其中 privateGetDeclaredMethods
方法从 缓存 或 JVM中 获取该Class
中申明的方法列表,searchMethods
方法将从返回的方法列表里找到一个匹配名称和参数的方法对象。
searchMethods
如果找到一个匹配的 Method
,则重新 copy 一份返回,即 Method.copy()
方法
所次每次调用getDeclaredMethod
方法返回的Method
对象其实都是一个新的对象,且新对象的root
属性都指向原来的Method
对象,如果需要频繁调用,最好把Method
对象缓存起来。
privateGetDeclaredMethods
从缓存或 JVM 中获取该 Class
中申明的方法列表,实现如下:
其中 reflectionData()
方法实现如下:
这里有个比较重要的数据结构 ReflectionData
,用来缓存从JVM中读取类的如下属性数据:
从reflectionData()
方法实现可以看出:reflectionData
对象是SoftReference
类型的,说明在内存紧张时可能会被回收,不过也可以通过-XX:SoftRefLRUPolicyMSPerMB
参数控制回收的时机,只要发生GC就会将其回收,如果reflectionData
被回收之后,又执行了反射方法,那只能通过newReflectionData
方法重新创建一个这样的对象了,newReflectionData
方法实现如下:
通过unsafe.compareAndSwapObject
方法重新设置reflectionData
字段;
在privateGetDeclaredMethods
方法中,如果通过reflectionData()
获得的ReflectionData
对象不为空,则尝试从ReflectionData
对象中获取declaredMethods
属性,如果是第一次,或则被GC回收之后,重新初始化后的类属性为空,则需要重新到JVM中获取一次,并赋值给ReflectionData
,下次调用就可以使用缓存数据了。
Method 调用
获取到指定的方法对象Method
之后,就可以调用它的invoke
方法了,invoke
实现如下:
应该注意到:这里的MethodAccessor
对象是invoke
方法实现的关键,一开始methodAccessor
为空,需要调用acquireMethodAccessor
生成一个新的MethodAccessor
对象,MethodAccessor
本身就是一个接口,实现如下:
在acquireMethodAccessor
方法中,会通过ReflectionFactory
类的newMethodAccessor
创建一个实现了MethodAccessor
接口的对象,实现如下:
在ReflectionFactory
类中,有2个重要的字段:noInflation
(默认false
)和inflationThreshold
(默认15),在checkInitted
方法中可以通过-Dsun.reflect.inflationThreshold=xxx
和-Dsun.reflect.noInflation=true
对这两个字段重新设置,而且只会设置一次;
如果noInflation
为false
,方法newMethodAccessor
都会返回DelegatingMethodAccessorImpl
对象,DelegatingMethodAccessorImpl
的类实现
其实,DelegatingMethodAccessorImpl
对象就是一个代理对象,负责调用被代理对象delegate
的invoke
方法,其中delegate
参数目前是NativeMethodAccessorImpl
对象,所以最终Method
的invoke
方法调用的是NativeMethodAccessorImpl
对象invoke
方法,实现如下:
这里用到了ReflectionFactory
类中的inflationThreshold
,当delegate
调用了15次invoke
方法之后,如果继续调用就通过MethodAccessorGenerator
类的generateMethod
方法生成MethodAccessorImpl
对象,并设置为delegate
对象,这样下次执行Method.invoke
时,就调用新建的MethodAccessor
对象的invoke()
方法了。
这里需要注意的是:generateMethod
方法在生成MethodAccessorImpl
对象时,会在内存中生成对应的字节码,并调用ClassDefiner.defineClass
创建对应的class对象,实现如下:
在ClassDefiner.defineClass
方法实现中,每被调用一次都会生成一个DelegatingClassLoader
类加载器对象
这里每次都生成新的类加载器,是为了性能考虑,在某些情况下可以卸载这些生成的类,因为类的卸载是只有在类加载器可以被回收的情况下才会被回收的,如果用了原来的类加载器,那可能导致这些新创建的类一直无法被卸载,从其设计来看本身就不希望这些类一直存在内存里的,在需要的时候有就行了。
Class类
Class 代表类的实体,在运行的 Java 应用程序中表示 类 和 接口。在这个类中提供了很多有用的方法,这里只简单的分类介绍下。
获取类相关的方法
方法 | 用途 |
---|---|
asSubclass(Class<U> clazz) | 把传递的类的对象转换成代表其子类的对象 |
Cast | 把对象转换成代表类或是接口的对象 |
getClassLoader() | 获得类的加载器 |
getClasses() | 返回一个数组,数组中包含该类中所有公共类和接口类的对象 |
getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象 |
forName(String className) | 根据类名返回类的对象 |
getName() | 获得类的完整路径名字 |
newInstance() | 创建类的实例 |
getPackage() | 获得类的包 |
getSimpleName() | 获得类的名字 |
getSuperclass() | 获得当前类继承的父类的名字 |
getInterfaces() | 获得当前类实现的类或是接口 |
获取 类中属性相关的方法
方法 | 用途 |
---|---|
getField(String name) | 获得某个公有的属性对象 |
getFields() | 获得所有公有的属性对象 |
getDeclaredField(String name) | 获得某个属性对象 |
getDeclaredFields() | 获得所有属性对象 |
获取 类中注解相关的方法
方法 | 用途 |
---|---|
getAnnotation(Class<A> annotationClass) | 返回该类中与参数类型匹配的公有注解对象 |
getAnnotations() | 返回该类所有的公有注解对象 |
getDeclaredAnnotation(Class<A> annotationClass) | 返回该类中与参数类型匹配的所有注解对象 |
getDeclaredAnnotations() | 返回该类所有的注解对象 |
获得 类中构造器相关的方法
方法 | 用途 |
---|---|
getConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructor(Class...<?> parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
获取 类中方法相关的方法
方法 | 用途 |
---|---|
getMethod(String name, Class...<?> parameterTypes) | 获得该类某个公有的方法 |
getMethods() | 获得该类所有公有的方法 |
getDeclaredMethod(String name, Class...<?> parameterTypes) | 获得该类某个方法 |
getDeclaredMethods() | 获得该类所有方法 |
类中 其他重要的方法
方法 | 用途 |
---|---|
isAnnotation() | 如果是注解类型则返回 true |
isAnnotationPresent(Class<? extends Annotation> annotationClass) | 如果是指定类型注解类型则返回 true |
isAnonymousClass() | 如果是匿名类则返回 true |
isArray() | 如果是一个数组类则返回 true |
isEnum() | 如果是枚举类则返回 true |
isInstance(Object obj) | 如果obj是该类的实例则返回 true |
isInterface() | 如果是接口类则返回 true |
isLocalClass() | 如果是局部类则返回 true |
isMemberClass() | 如果是内部类则返回 true |
Field 类
Field 代表 类的成员变量(成员变量也称为类的属性)。
方法 | 用途 |
---|---|
equals(Object obj) | 属性与 obj 相等则返回 true |
get(Object obj) | 获得 obj 中对应的属性值 |
set(Object obj, Object value) | 设置 obj 中对应属性值 |
Method 类
Method 代表 类的方法 。
方法 | 用途 |
---|---|
invoke(Object obj, Object... args) | 传递 object 对象及参数调用该对象对应的方法 |
Constructor类
Constructor 代表 类的构造方法。
方法 | 用途 |
---|---|
newInstance(Object... initargs) | 根据传递的参数创建类的对象 |
示例 1:演示反射的使用
为了演示反射的使用,首先构造一个与书籍相关的 model --- Book.java,然后通过反射方法示例创建对象、反射私有构造方法、反射私有属性、反射私有方法,最后给出两个比较复杂的反射示例 --- 获得当前 ZenMode 和 关机Shutdown。
被反射类 Book.java
public class Book{
private final static String TAG = "BookTag";
private String name;
private String author;
@Override
public String toString() {
return "Book{" +
"name='" + name + ''' +
", author='" + author + ''' +
'}';
}
public Book() {
}
private Book(String name, String author) {
this.name = name;
this.author = author;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
private String declaredMethod(int index) {
String string = null;
switch (index) {
case 0:
string = "I am declaredMethod 1 !";
break;
case 1:
string = "I am declaredMethod 2 !";
break;
default:
string = "I am declaredMethod 1 !";
}
return string;
}
}
反射逻辑封装在 ReflectClass.java
public class ReflectClass {
private final static String TAG = "peter.log.ReflectClass";
// 创建对象
public static void reflectNewInstance() {
try {
Class<?> classBook = Class.forName("com.android.peter.reflectdemo.Book");
Object objectBook = classBook.newInstance();
Book book = (Book) objectBook;
book.setName("Android进阶之光");
book.setAuthor("刘望舒");
Log.d(TAG,"reflectNewInstance book = " + book.toString());
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 反射私有的构造方法
public static void reflectPrivateConstructor() {
try {
Class<?> classBook = Class.forName("com.android.peter.reflectdemo.Book");
Constructor<?> declaredConstructorBook = classBook.getDeclaredConstructor(String.class,String.class);
declaredConstructorBook.setAccessible(true);
Object objectBook = declaredConstructorBook.newInstance("Android开发艺术探索","任玉刚");
Book book = (Book) objectBook;
Log.d(TAG,"reflectPrivateConstructor book = " + book.toString());
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 反射私有属性
public static void reflectPrivateField() {
try {
Class<?> classBook = Class.forName("com.android.peter.reflectdemo.Book");
Object objectBook = classBook.newInstance();
Field fieldTag = classBook.getDeclaredField("TAG");
fieldTag.setAccessible(true);
String tag = (String) fieldTag.get(objectBook);
Log.d(TAG,"reflectPrivateField tag = " + tag);
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 反射私有方法
public static void reflectPrivateMethod() {
try {
Class<?> classBook = Class.forName("com.android.peter.reflectdemo.Book");
Method methodBook = classBook.getDeclaredMethod("declaredMethod",int.class);
methodBook.setAccessible(true);
Object objectBook = classBook.newInstance();
String string = (String) methodBook.invoke(objectBook,0);
Log.d(TAG,"reflectPrivateMethod string = " + string);
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 获得系统Zenmode值
public static int getZenMode() {
int zenMode = -1;
try {
Class<?> cServiceManager = Class.forName("android.os.ServiceManager");
Method mGetService = cServiceManager.getMethod("getService", String.class);
Object oNotificationManagerService = mGetService.invoke(null, Context.NOTIFICATION_SERVICE);
Class<?> cINotificationManagerStub = Class.forName("android.app.INotificationManager$Stub");
Method mAsInterface = cINotificationManagerStub.getMethod("asInterface",IBinder.class);
Object oINotificationManager = mAsInterface.invoke(null,oNotificationManagerService);
Method mGetZenMode = cINotificationManagerStub.getMethod("getZenMode");
zenMode = (int) mGetZenMode.invoke(oINotificationManager);
} catch (Exception ex) {
ex.printStackTrace();
}
return zenMode;
}
// 关闭手机
public static void shutDown() {
try {
Class<?> cServiceManager = Class.forName("android.os.ServiceManager");
Method mGetService = cServiceManager.getMethod("getService",String.class);
Object oPowerManagerService = mGetService.invoke(null,Context.POWER_SERVICE);
Class<?> cIPowerManagerStub = Class.forName("android.os.IPowerManager$Stub");
Method mShutdown = cIPowerManagerStub.getMethod("shutdown",boolean.class,String.class,boolean.class);
Method mAsInterface = cIPowerManagerStub.getMethod("asInterface",IBinder.class);
Object oIPowerManager = mAsInterface.invoke(null,oPowerManagerService);
mShutdown.invoke(oIPowerManager,true,null,true);
} catch (Exception ex) {
ex.printStackTrace();
}
}
public static void shutdownOrReboot(final boolean shutdown, final boolean confirm) {
try {
Class<?> ServiceManager = Class.forName("android.os.ServiceManager");
// 获得ServiceManager的getService方法
Method getService = ServiceManager.getMethod("getService", java.lang.String.class);
// 调用getService获取RemoteService
Object oRemoteService = getService.invoke(null, Context.POWER_SERVICE);
// 获得IPowerManager.Stub类
Class<?> cStub = Class.forName("android.os.IPowerManager$Stub");
// 获得asInterface方法
Method asInterface = cStub.getMethod("asInterface", android.os.IBinder.class);
// 调用asInterface方法获取IPowerManager对象
Object oIPowerManager = asInterface.invoke(null, oRemoteService);
if (shutdown) {
// 获得shutdown()方法
Method shutdownMethod = oIPowerManager.getClass().getMethod(
"shutdown", boolean.class, String.class, boolean.class);
// 调用shutdown()方法
shutdownMethod.invoke(oIPowerManager, confirm, null, false);
} else {
// 获得reboot()方法
Method rebootMethod = oIPowerManager.getClass().getMethod("reboot",
boolean.class, String.class, boolean.class);
// 调用reboot()方法
rebootMethod.invoke(oIPowerManager, confirm, null, false);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
调用相应反射逻辑方法
try {
// 创建对象
ReflectClass.reflectNewInstance();
// 反射私有的构造方法
ReflectClass.reflectPrivateConstructor();
// 反射私有属性
ReflectClass.reflectPrivateField();
// 反射私有方法
ReflectClass.reflectPrivateMethod();
} catch (Exception ex) {
ex.printStackTrace();
}
Log.d(TAG," zenmode = " + ReflectClass.getZenMode());
Log 输出结果如下:
08-27 15:11:37.999 11987-11987/com.android.peter.reflectdemo D/peter.log.ReflectClass: reflectNewInstance book = Book{name='Android进阶之光', author='刘望舒'}
08-27 15:11:38.000 11987-11987/com.android.peter.reflectdemo D/peter.log.ReflectClass: reflectPrivateConstructor book = Book{name='Android开发艺术探索', author='任玉刚'}
08-27 15:11:38.000 11987-11987/com.android.peter.reflectdemo D/peter.log.ReflectClass: reflectPrivateField tag = BookTag
08-27 15:11:38.000 11987-11987/com.android.peter.reflectdemo D/peter.log.ReflectClass: reflectPrivateMethod string = I am declaredMethod 1 !
08-27 15:11:38.004 11987-11987/com.android.peter.reflectdemo D/peter.log.ReflectDemo: zenmode = 0
示例 2:关于反射的用法举例
反射非常强大,但是从上面的记录来看,反而觉得还不如直接调用方法来的直接和方便2。
通常来说,需要在学习了Spring 的依赖注入,反转控制之后,才会对反射有更好的理解,所以先这里举两个例子,来演示一下反射的一种实际运用3。
1、通过反射运行配置文件内容
1. 首先准备两个业务类
package service;
public class Service1 {
public void doService1(){
System.out.println("业务方法1");
}
}
package service;
public class Service2 {
public void doService2(){
System.out.println("业务方法2");
}
}
2. 当需要从第一个业务方法切换到第二个业务方法的时候,使用非反射方式,必须修改代码,并且重新编译运行,才可以达到效果
package service;
public class CommonTest {
public static void main(String[] args) {
//new Service1().doService1();
//必须重新修改代码
new Service2().doService2();
}
}
3. 使用反射方式则方便很多
- 首先准备一个配置文件,就叫做spring.txt吧, 放在src目录下。
里面存放的是类的名称,和要调用的方法名。首先准备一个配置文件,就叫做spring.txt吧, 放在src目录下。里面存放的是类的名称,和要调用的方法名。- 在测试类Test中,首先取出类名称和方法名,然后通过反射去调用这个方法。
- 当需要从调用第一个业务方法,切换到调用第二个业务方法的时候,不需要修改一行代码,也不需要重新编译,只需要修改配置文件spring.txt,再运行即可。
示例:spring.txt 内容
class=reflection.Service1
method=doService1
测试类
package service;
public class ReflectTest {
@SuppressWarnings({"rawtypes", "unchecked"})
public static void main(String[] args) throws Exception {
//从spring.txt中获取类名称和方法名称
File springConfigFile = new File("H:\eclpise-workspace\reflect-demo\src\spring.txt");
Properties springConfig = new Properties();
springConfig.load(new FileInputStream(springConfigFile));
String className = (String) springConfig.get("class");
String methodName = (String) springConfig.get("method");
//根据类名称获取类对象
Class clazz = Class.forName(className);
//根据方法名称,获取方法对象
Method m = clazz.getMethod(methodName);
//获取构造器
Constructor c = clazz.getConstructor();
//根据构造器,实例化出对象
Object service = c.newInstance();
//调用对象的指定方法
m.invoke(service);
}
}
示例 2:
student 类:
public class Student {
public void show() {
System.out.println("is show()");
}
}
配置文件以txt文件为例子(pro.txt):
className = cn.fanshe.Student
methodName = show
测试类:
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Properties;
/*
* 我们利用反射和配置文件,可以使:应用程序更新时,对源码无需进行任何修改
* 我们只需要将新类发送给客户端,并修改配置文件即可
*/
public class Demo {
public static void main(String[] args) throws Exception {
//通过反射获取Class对象
Class stuClass = Class.forName(getValue("className"));//"cn.fanshe.Student"
//2获取show()方法
Method m = stuClass.getMethod(getValue("methodName"));//show
//3.调用show()方法
m.invoke(stuClass.getConstructor().newInstance());
}
//此方法接收一个key,在配置文件中获取相应的value
public static String getValue(String key) throws IOException {
Properties pro = new Properties();//获取配置文件的对象
FileReader in = new FileReader("pro.txt");//获取输入流
pro.load(in);//将流加载到配置文件对象中
in.close();
return pro.getProperty(key);//返回根据key获取的value值
}
}
控制台输出:is show()
需求:当我们升级这个系统时,不要 Student 类,而需要新写一个 Student2 的类时,这时只需要更改 pro.txt 的文件内容就可以了。代码就一点不用改动
要替换的 student2类:
public class Student2 {
public void show2() {
System.out.println("is show2()");
}
}
配置文件更改为:
className = cn.fanshe.Student2
methodName = show2
控制台输出:is show2();
2:通过反射越过泛型检查
泛型是在编译期间起作用的。在编译后的.class文件中是没有泛型的。所有比如T或者E类型啊,本质都是通过Object处理的。所以可以通过使用反射来越过泛型。
示例:
package test;
public class GenericityTest {
public static void main(String[] args) throws Exception {
ArrayList<String> list = new ArrayList<>();
list.add("this");
list.add("is");
// strList.add(5);报错
/********** 越过泛型检查 **************/
//获取ArrayList的Class对象,反向的调用add()方法,添加数据
Class listClass = list.getClass();
//获取add()方法
Method m = listClass.getMethod("add", Object.class);
//调用add()方法
m.invoke(list, 5);
//遍历集合
for (Object obj : list) {
System.out.println(obj);
}
}
}
示例 2:
泛型用在编译期,编译过后泛型擦除(消失掉)。所以是可以通过反射越过泛型检查的
测试类:
import java.lang.reflect.Method;
import java.util.ArrayList;
/*
* 通过反射越过泛型检查
*
* 例如:有一个String泛型的集合,怎样能向这个集合中添加一个Integer类型的值?
*/
public class Demo {
public static void main(String[] args) throws Exception {
ArrayList<String> strList = new ArrayList<>();
strList.add("aaa");
strList.add("bbb");
// strList.add(100);
// 获取ArrayList的Class对象,反向的调用add()方法,添加数据
Class listClass = strList.getClass(); //得到 strList 对象的字节码 对象
// 获取add()方法
Method m = listClass.getMethod("add", Object.class);
// 调用add()方法
m.invoke(strList, 100);
// 遍历集合
for (Object obj : strList) {
System.out.println(obj);
}
}
}
控制台输出:
aaa
bbb
100
总结
本文列举了反射机制使用过程中常用的、重要的一些类及其方法,更多信息和用法需要近一步的阅读 Google 提供的相关文档和示例。
在阅读 Class类 文档时发现一个特点,以通过反射获得 Method 对象为例,一般会提供四种方法,
- getMethod(parameterTypes) // 获取某个公有的方法的对象,
- getMethods() // 获取该类所有公有的方法。
- getDeclaredMethod(parameterTypes) // 获取该类某个方法。
- getDeclaredMethods()。 // 获取该类所有方法。
带有 Declared 修饰的方法可以反射到私有的方法,没有Declared修饰的只能用来反射公有的方法。其他的 Annotation、Field、Constructor 也是如此。
在 ReflectClass类中还提供了两种反射 PowerManager.shutdown() 的方法,在调用的时候会输出如下 log,提示没有相关权限。之前在项目中尝试反射其他方法的时候还遇到过有权限和没权限返回的值不一样的情况。如果源码中明确进行了权限验证,而你的应用又无法获得这个权限的话,建议就不要浪费时间反射了。
W/System.err: java.lang.reflect.InvocationTargetException
W/System.err: at java.lang.reflect.Method.invoke(Native Method)
W/System.err: at .ReflectClass.shutDown(ReflectClass.java:104)
W/System.err: at .MainActivity$1.onClick(MainActivity.java:25)
W/System.err: at android.view.View.performClick(View.java:6259)
W/System.err: at android.view.View$PerformClick.run(View.java:24732)
W/System.err: at android.os.Handler.handleCallback(Handler.java:789)
W/System.err: at android.os.Handler.dispatchMessage(Handler.java:98)
W/System.err: at android.os.Looper.loop(Looper.java:164)
W/System.err: at android.app.ActivityThread.main(ActivityThread.java:6592)
W/System.err: at java.lang.reflect.Method.invoke(Native Method)
W/System.err: at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:769)
W/System.err: Caused by: java.lang.SecurityException: Neither user 10224 nor current process has android.permission.REBOOT.
W/System.err: at android.os.Parcel.readException(Parcel.java:1942)
W/System.err: at android.os.Parcel.readException(Parcel.java:1888)
W/System.err: at android.os.IPowerManager$Stub$Proxy.shutdown(IPowerManager.java:787)
W/System.err: ... 12 more
ReflectDemo:https://gitee.com/peter_RD_nj/DemoAllInOne/tree/master/ReflectDemo
参考文献
敬业的小码哥 How2jJava
认识反射机制(Reflection)
Java 反射机制
一个例子让你了解Java反射机制
Java反射机制的原理及在Android下的简单应用
java中的反射机制
Android注解与反射机制
java.lang.reflect.Method
最后
以上就是体贴煎蛋为你收集整理的Java 高级特性 --- 反射1. 概述深入分析 Java 方法反射的实现原理Class类Field 类Method 类Constructor类示例 1:演示反射的使用示例 2:关于反射的用法举例总结的全部内容,希望文章能够帮你解决Java 高级特性 --- 反射1. 概述深入分析 Java 方法反射的实现原理Class类Field 类Method 类Constructor类示例 1:演示反射的使用示例 2:关于反射的用法举例总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复