Java反射和实现原理 1-反射是什么? 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
实际上,我们创建的每一个类也都是对象,即类本身是java.lang.Class类的实例对象。这个实例对象称之为类对象,也就是Class对象。那么,Class对象又是什么对象呢?
2-Class对象特点 Class对象的由来是将class文件读入内存,并为之创建一个Class对象。
从图中可以得出以下几点:
Class 类的实例对象表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有很多的实例,每个类都有唯一的Class对象。 Class 类没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机自动构造的。也就是说我们不需要创建,JVM已经帮我们创建了。 Class 对象用于提供类本身的信息,比如有几种构造方法, 有多少属性,有哪些普通方法 3-反射机制能做什么 反射机制主要提供了以下功能:
在运行时判断任意一个对象所属的类; 在运行时构造任意一个类的对象; 在运行时判断任意一个类所具有的成员变量和方法; 在运行时调用任意一个对象的方法; 生成动态代理。 4-反射机制的相关API 4.1-通过一个对象获得完整的包名和类名 package com.fangpeng.reflectpublic class TestReflect { public static void main (String[] args) throws Exception { TestReflect testReflect = new TestReflect(); System.out.println(testReflect.getClass().getName()); } }
4.2-获取类对象 获取类对象有3种方式
Class.forName()
(常用 )Hero.class
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); System.out.println(pClass1==pClass3); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
三种方式中,常用第一种,第二种需要导入类的包,依赖太强,不导包就抛编译错误。第三种对象都有了还要反射干什么。一般都第一种,一个字符串可以传入也可写在配置文件中等多种方法。
4.3-利用反射机制创建对象 与传统的通过new 来获取对象的方式不同反射机制,反射会先拿到Hero的“类对象”,然后通过类对象获取“构造器对象”再通过构造器对象创建一个对象,具体步骤:
获取类对象 Class class = Class.forName(“pojo.Hero”); 获取构造器对象 Constructor con = clazz.getConstructor(形参.class); 获取对象 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 { public static void main (String[] args) throws Exception { Class clazz = Class.forName("pojo.Hero" ); 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 ); 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()); Class<?> intes[] = clazz.getInterfaces(); System.out.println("clazz实现的接口有:" ); for (int i = 0 ; i < intes.length; i++) { System.out.println((i + 1 ) + ":" + intes[i].getName()); } } }
4.5-获取成员变量并使用 基本步骤
获取HeroPlus类的对象; 获取属性 Field f1 = h.getDeclaredField("属性名")
修改属性 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 () { 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(); h.name = "garen" ; try { Field f1= h.getClass().getDeclaredField("name" ); f1.set(h, "teemo" ); System.out.println(h.name); } catch (Exception e) { 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" ); Method method = clazz.getMethod("reflect1" ); method.invoke(clazz.newInstance()); method = clazz.getMethod("reflect2" , int .class, String.class); method.invoke(clazz.newInstance(), 20 , "张三" ); } 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(); 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; } } 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; } } 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就是用反射实现的,类似接口和实现类,接口的使用也是对性能有影响的(虽然这个影响可以忽略不计,也许这个类比也不太确切),但是不能为了一点性能的提升而放弃优雅的设计模式。