Java反射和实现原理

1-反射是什么?

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

实际上,我们创建的每一个类也都是对象,即类本身是java.lang.Class类的实例对象。这个实例对象称之为类对象,也就是Class对象。那么,Class对象又是什么对象呢?

2-Class对象特点

Class对象的由来是将class文件读入内存,并为之创建一个Class对象。

从图中可以得出以下几点:

  1. Class 类的实例对象表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有很多的实例,每个类都有唯一的Class对象。
  2. Class 类没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机自动构造的。也就是说我们不需要创建,JVM已经帮我们创建了。
  3. Class 对象用于提供类本身的信息,比如有几种构造方法, 有多少属性,有哪些普通方法

3-反射机制能做什么

反射机制主要提供了以下功能:

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法;
  • 在运行时调用任意一个对象的方法;
  • 生成动态代理。

4-反射机制的相关API

4.1-通过一个对象获得完整的包名和类名

package com.fangpeng.reflect
public class TestReflect {

public static void main(String[] args) throws Exception {

TestReflect testReflect = new TestReflect();
System.out.println(testReflect.getClass().getName());
// 结果 com.fangpeng.reflect.TestReflect

}

}

4.2-获取类对象

获取类对象有3种方式

  1. Class.forName()常用
  2. Hero.class
  3. new Hero().getClass()

在一个JVM中,一种类,只会有一个类对象存在。所以以上三种方式取出来的类对象,都是一样。(此处准确是在ClassLoader下,只有一个类对象)

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

三种方式中,常用第一种,第二种需要导入类的包,依赖太强,不导包就抛编译错误。第三种对象都有了还要反射干什么。一般都第一种,一个字符串可以传入也可写在配置文件中等多种方法。

4.3-利用反射机制创建对象

与传统的通过new 来获取对象的方式不同反射机制,反射会先拿到Hero的“类对象”,然后通过类对象获取“构造器对象”再通过构造器对象创建一个对象,具体步骤:

  1. 获取类对象 Class class = Class.forName(“pojo.Hero”);
  2. 获取构造器对象 Constructor con = clazz.getConstructor(形参.class);
  3. 获取对象 Hero hero =con.newInstance(实参);

上面是最简单的获取方法,当Hero的构造方法不是无参构造方法时,获取构造器对象略有不同,见下面测试:

构造方法不同时,获取构造器对象的方法

示例:

1. Hero类添加6种构造方法

//---------------构造方法-------------------
//(默认的构造方法)
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);
}

2. 通过反射机制获取对象

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对象调用newInstance(Object... initargs)
*/


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


}

3. 输出结果

**********************所有公有构造方法*********************************
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

4.4-获取一个对象的父类与实现的接口

public class TestReflect implements Serializable {

private static final long serialVersionUID = -2862585049955236662L;

public static void main(String[] args) throws Exception {

Class<?> clazz = Class.forName("com.fangpeng.reflect.TestReflect");
// 取得父类
Class<?> parentClass = clazz.getSuperclass();
System.out.println("clazz的父类为:" + parentClass.getName());
// clazz的父类为: java.lang.Object
// 获取所有的接口
Class<?> intes[] = clazz.getInterfaces();
System.out.println("clazz实现的接口有:");
for (int i = 0; i < intes.length; i++) {
System.out.println((i + 1) + ":" + intes[i].getName());
}
// clazz实现的接口有:
// 1:java.io.Serializable

}

}

4.5-获取成员变量并使用

基本步骤

  1. 获取HeroPlus类的对象;
  2. 获取属性 Field f1 = h.getDeclaredField("属性名")
  3. 修改属性 f1.set(h,实参),注意这里的h是对象,不是类对象

示例:

1. 新增HeroPlus类

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

2. 获取属性并修改

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

4.6-获取某个类的全部方法

public class TestReflect implements Serializable {

private static final long serialVersionUID = -2862585049955236662L;

public static void main(String[] args) throws Exception {

Class<?> clazz = Class.forName("com.fangpeng.reflect.TestReflect");
Method method[] = clazz.getMethods();

for (int i = 0; i < method.length; ++i) {
Class<?> returnType = method[i].getReturnType();
Class<?> para[] = method[i].getParameterTypes();
int temp = method[i].getModifiers();
System.out.print(Modifier.toString(temp) + " ");
System.out.print(returnType.getName() + " ");
System.out.print(method[i].getName() + " ");
System.out.print("(");

for (int j = 0; j < para.length; ++j) {
System.out.print(para[j].getName() + " " + "arg" + j);
if (j < para.length - 1) {
System.out.print(",");
}
}

Class<?> exce[] = method[i].getExceptionTypes();

if (exce.length > 0) {
System.out.print(") throws ");
for (int k = 0; k < exce.length; ++k) {
System.out.print(exce[k].getName() + " ");
if (k < exce.length - 1) {
System.out.print(",");
}
}
} else {
System.out.print(")");
}
System.out.println();
}
}

}

4.7-通过反射机制调用某个类的方法

public class TestReflect {

public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("com.fangpeng.reflect.TestReflect");
// 调用TestReflect类中的reflect1方法
Method method = clazz.getMethod("reflect1");
method.invoke(clazz.newInstance());

// Java 反射机制 - 调用某个类的方法1.
// 调用TestReflect的reflect2方法
method = clazz.getMethod("reflect2", int.class, String.class);
method.invoke(clazz.newInstance(), 20, "张三");

// Java 反射机制 - 调用某个类的方法2.
// age -> 20. name -> 张三

}

public void reflect1() {
System.out.println("Java 反射机制 - 调用某个类的方法1.");
}

public void reflect2(int age, String name) {
System.out.println("Java 反射机制 - 调用某个类的方法2.");
System.out.println("age -> " + age + ". name -> " + name);
}

}

4.8-通过反射机制操作某个类的属性

public class TestReflect {

private String proprety = null;

public static void main(String[] args) throws Exception {

Class<?> clazz = Class.forName("ncom.fangpeng.reflect.TestReflect");
Object obj = clazz.newInstance();

// 可以直接对 private 的属性赋值
Field field = clazz.getDeclaredField("proprety");
field.setAccessible(true);
field.set(obj, "Java反射机制");
System.out.println(field.get(obj));

}

}

4.9-反射机制的动态代理

//定义项目接口
interface Subject {
public String say(String name, int age);
}

// 定义真实项目
class RealSubject implements Subject {
public String say(String name, int age) {
return name + " " + age;
}
}

class MyInvocationHandler implements InvocationHandler {

private Object obj = null;

public Object bind(Object obj) {
this.obj = obj;
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object temp = method.invoke(this.obj, args);
return temp;
}

}

/**
* 在java中有三种类类加载器。
* 1)Bootstrap ClassLoader 此加载器采用c++编写,一般开发中很少见。
* 2)Extension ClassLoader 用来进行扩展类的加载,一般对应的是jrelibext目录中的类
* 3)AppClassLoader 加载classpath指定的类,是最常用的加载器。同时也是java中默认的加载器。
* 如果想要完成动态代理,首先需要定义一个InvocationHandler接口的子类,已完成代理的具体操作。
*
*/
public class TestReflect {

public static void main(String[] args) throws Exception {
MyInvocationHandler demo = new MyInvocationHandler();
Subject sub = (Subject) demo.bind(new RealSubject());
String info = sub.say("Rollen", 20);
System.out.println(info);
}

}

5-反射机制的应用实例

5.1-在泛型为Integer的ArrayList中存放一个String类型的对象。

public class TestReflect {

public static void main(String[] args) throws Exception {

ArrayList<Integer> list = new ArrayList<Integer>();
Method method = list.getClass().getMethod("add", Object.class);
method.invoke(list, "Java反射机制实例。");
System.out.println(list.get(0));

}

}

5.2-通过反射取得并修改数组的信息

public class TestReflect {

public static void main(String[] args) throws Exception {

int[] temp = { 1, 2, 3, 4, 5 };
Class<?> demo = temp.getClass().getComponentType();
System.out.println("数组类型: " + demo.getName());
System.out.println("数组长度 " + Array.getLength(temp));
System.out.println("数组的第一个元素: " + Array.get(temp, 0));
Array.set(temp, 0, 100);
System.out.println("修改之后数组第一个元素为: " + Array.get(temp, 0));

}

}

5.3-通过反射机制修改数组的大小

public class TestReflect {

public static void main(String[] args) throws Exception {

int[] temp = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int[] newTemp = (int[]) arrayInc(temp, 15);
print(newTemp);
String[] atr = { "a", "b", "c" };
String[] str1 = (String[]) arrayInc(atr, 8);
print(str1);

}

// 修改数组大小
public static Object arrayInc(Object obj, int len) {

Class<?> arr = obj.getClass().getComponentType();
Object newArr = Array.newInstance(arr, len);
int co = Array.getLength(obj);
System.arraycopy(obj, 0, newArr, 0, co);
return newArr;

}

// 打印
public static void print(Object obj) {

Class<?> c = obj.getClass();
if (!c.isArray()) {
return;
}

System.out.println("数组长度为: " + Array.getLength(obj));
for (int i = 0; i < Array.getLength(obj); i++) {
System.out.print(Array.get(obj, i) + " ");
}
System.out.println();
}

}

5.4-将反射机制应用于工厂模式

interface fruit {
public abstract void eat();
}

class Apple implements fruit {

public void eat() {
System.out.println("Apple");
}

}

class Orange implements fruit {

public void eat() {
System.out.println("Orange");
}

}

class Factory {

public static fruit getInstance(String ClassName) {

fruit f = null;
try {
f = (fruit) Class.forName(ClassName).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return f;
}

}

/**
* 对于普通的工厂模式当我们在添加一个子类的时候,就需要对应的修改工厂类。 当我们添加很多的子类的时候,会很麻烦。
* 现在我们利用反射机制实现工厂模式,可以在不修改工厂类的情况下添加任意多个子类。
* 但是有一点仍然很麻烦,就是需要知道完整的包名和类名,这里可以使用properties配置文件来完成。
*
*/

public class TestReflect {

public static void main(String[] args) throws Exception {
fruit f = Factory.getInstance("com.fangpeng.reflect.Apple");
if (f != null) {
f.eat();
}
}

}

6-反射的实现原理

Java反射应用十分广泛,例如spring的核心功能控制反转IOC就是通过反射来实现的。

如下为Method类中invoke方法,可以看出该方法实际是将反射方法的调用委派给MethodAccessor,通过MethodAccessor的invoke方法完成方法调用。该接口有两个现有的实现类,一个是直接调用本地方法,另一个是通过委派模式。Mehod的实例第一次调用都会生成一个委派实现,它实现的就是一个本地实现,相当于在Method和本地实现之间加了一个中间层,本地实现比较好理解,就是讲参数准备好,然后进入目标方法。

通过如下代码进行一个简单的测试,通过打印的堆栈信息可以看出,Method实例的invoke方法首先调用的是委派实现DelegatingMethodAccessorImpl的invoke方法,最后调用本地实现。那么为什么会加上一个看似多余的中间委派层呢?实际上,java语言实现反射的方法除了本地实现之外,还会采用动态生成字节码的方式直接调用目标方法,运用委派模式的目的就是在本地实现和动态实现之间进行切换。

public class TestReflect {
public static void target(int i) {
new RuntimeException("让我们来看看方法调用栈").printStackTrace();
}
public static void main(String[] args) throws Exception {
Class<?> klass = Class.forName("com.fangpeng.reflect.TestReflect");
Method method = klass.getMethod("target", int.class);
method.invoke(null, 128);
}
}

方法调用栈如下所示:

java.lang.RuntimeException: 让我们来看看方法调用栈
at com.kafka.demo.com.kafka.demo.test.TestReflect.target(TestReflect.java:12)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.kafka.demo.com.kafka.demo.test.TestReflect.main(TestReflect.java:47)

动态实现和本地实现相比,其运行效率是本地实现的20倍左右,但是第一次生成字节码是比较耗时的,所以仅单次调用的话,本地实现的性能反而是动态实现的3到4倍。JVM通过参数-Dsun.reflect.inflationThresHold来进行调整,默认是15次,即前15次调用会使用本地实现,15次之后会生成字节码,采用动态实现的方式。我们将方法的调用循环20次得到结果如下所示:

java.lang.RuntimeException: 让我们来看看第15次方法调用栈
at com.kafka.demo.com.kafka.demo.test.TestReflect.target(TestReflect.java:12)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.kafka.demo.com.kafka.demo.test.TestReflect.main(TestReflect.java:48)
java.lang.RuntimeException: 让我们来看看第16次方法调用栈
at com.kafka.demo.com.kafka.demo.test.TestReflect.target(TestReflect.java:12)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.kafka.demo.com.kafka.demo.test.TestReflect.main(TestReflect.java:48)
java.lang.RuntimeException: 让我们来看看第17次方法调用栈
at com.kafka.demo.com.kafka.demo.test.TestReflect.target(TestReflect.java:12)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.kafka.demo.com.kafka.demo.test.TestReflect.main(TestReflect.java:48)

在第十六次方法调用的过程中动态生成了字节码,并在第十七次方法调用的过程中就直接使用GenerateMethodAccessor1了。

7-反射的性能

一般都认为反射是比较消耗性能的,就连甲骨文关于反射的教学中也强调反射性能开销大的缺点。从上面的例子来看,Class.forName()需要调用本地方法,getMethod()方法需要遍历目标类的所有方法,如果没有匹配到还需要遍历父类的所有方法,这些都是比较消耗性能的操作,但是我们可以将它们的实例对象放在缓存中,来减小对性能的影响,这里我们主要讨论Method实例的invoke方法对性能的影响。

我做了一个简单的实验来进行验证,将一个空方法直接调用20亿次,每1亿次打印一个该阶段的运行时间,取最后五次时间求取平均值(这样做的目的是为了得到预热峰值性能),在我32G内存的ThinkStation上面直接调用1亿次耗时90ms,通过反射调用平均耗时为300ms,约为直接调用的3.3倍。前文中介绍过动态实现和本地实现,由于本地实现比较消耗性能,通过-Dsun.reflect.noInflation=true来关闭本地实现直接使用动态实现,此时平均耗时为270ms,约为直接调用的3倍。此外方法调用时需要检查方法的权限,如果跳过权限检测过程,即设置method.setAccessible(true),此时平均耗时为210ms,约为直接调用的2倍左右。

至此我们可以看出对于反射的应用会对性能造成一定的影响,但是可以通过优化减小影响。那是不是为了性能就杜绝使用反射呢?显然不是,前文中提到IOC就是用反射实现的,类似接口和实现类,接口的使用也是对性能有影响的(虽然这个影响可以忽略不计,也许这个类比也不太确切),但是不能为了一点性能的提升而放弃优雅的设计模式。