[TOC]html
不少时候咱们会遇到别人问一个问题:你给我讲一下反射,究竟是什么东西?怎么实现的?咱们能用反射来作什么?它有什么优缺点?下面咱们会围绕着这几个问题展开:java
反射是什么?什么是反?什么是正射?
有反就有正,咱们知道正常状况, 若是咱们但愿建立一个对象,会使用如下的语句:数组
Person person = new Person();
其实咱们第一次执行上面的语句的时候,JVM会先加载Person.class
,加载到内存完以后,在方法区/堆中会建立了一个Class
对象,对应这个Person
类。这里有争议,有人说是在方法区,有些人说是在堆。我的感受应该JVM规范说是在方法区,可是不是强制要求,并且不一样版本的JVM实现也不同。具体参考如下连接,这里不作解释:
https://www.cnblogs.com/xy-nb...
而上面正常的初始化对象的方法,也能够说是“正射”,就是使用Class
对象建立出一个Person
对象。安全
而反射则相反,是根据Person
对象,获取到Class
对象,而后能够获取到Person
类的相关信息,进行初始化或者调用等一系列操做。框架
在运行状态时,能够构造任何一个类的对象,获取到任意一个对象所属的类信息,以及这个类的成员变量或者方法,能够调用任意一个对象的属性或者方法。能够理解为具有了 动态加载对象 以及 对对象的基本信息进行剖析和使用 的能力。ide
提供的功能包括:函数
灵活,强大,能够在运行时装配,无需在组件之间进行源代码连接,可是使用不当效率会有影响。全部类的对象都是Class的实例。
既然咱们能够对类的全限定名,方法以及参数等进行配置,完成对象的初始化,那就是至关于增长了java的可配置性。学习
这里特别须要明确的一点:类自己也是一个对象,方法也是一个对象,在Java里面万物皆可对象,除了基础数据类型...测试
package invocation; public class MyInvocation { public static void main(String[] args) { getClassNameTest(); } public static void getClassNameTest(){ MyInvocation myInvocation = new MyInvocation(); System.out.println("class: " + myInvocation.getClass()); System.out.println("simpleName: " + myInvocation.getClass().getSimpleName()); System.out.println("name: " + myInvocation.getClass().getName()); System.out.println("package: " + "" + myInvocation.getClass().getPackage()); } }
运行结果:优化
class: class invocation.MyInvocation simpleName: MyInvocation name: invocation.MyInvocation package: package invocation
由上面结果咱们能够看到:
1.getClass()
:打印会带着class+全类名
2.getClass().getSimpleName()
:只会打印出类名
3.getName()
:会打印全类名
4.getClass().getPackage()
:打印出package+包名
getClass()
获取到的是一个对象,getPackage()
也是。
在java中,一切皆对象。java中能够分为两种对象,实例对象和Class对象。这里咱们说的获取Class对象,其实就是第二种,Class对象表明的是每一个类在运行时的类型信息,指和类相关的信息。好比有一个Student
类,咱们用Student student = new Student()
new一个对象出来,这个时候Student
这个类的信息其实就是存放在一个对象中,这个对象就是Class类的对象,而student这个实例对象也会和Class对象关联起来。
咱们有三种方式能够获取一个类在运行时的Class对象,分别是
实例代码以下:
package invocation; public class MyInvocation { public static void main(String[] args) { getClassTest(); } public static void getClassTest(){ Class<?> invocation1 = null; Class<?> invocation2 = null; Class<?> invocation3 = null; try { // 最经常使用的方法 invocation1 = Class.forName("invocation.MyInvocation"); }catch (Exception ex){ ex.printStackTrace(); } invocation2 = new MyInvocation().getClass(); invocation3 = MyInvocation.class; System.out.println(invocation1); System.out.println(invocation2); System.out.println(invocation3); } }
执行的结果以下,三个结果同样:
class invocation.MyInvocation class invocation.MyInvocation class invocation.MyInvocation
首先咱们有一个Student类,后面都会沿用这个类,将再也不重复。
class Student{ private int age; private String name; public Student() { } public Student(int age) { this.age = age; } public Student(String name) { this.name = name; } public Student(int age, String name) { this.age = age; this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Student{" + "age=" + age + ", name='" + name + '\'' + '}'; }
咱们可使用getInstance()
方法构造出一个Student的对象:
public static void getInstanceTest() { try { Class<?> stduentInvocation = Class.forName("invocation.Student"); Student student = (Student) stduentInvocation.newInstance(); student.setAge(9); student.setName("Hahs"); System.out.println(student); }catch (Exception ex){ ex.printStackTrace(); } } 输出结果以下: Student{age=9, name='Hahs'}
可是若是咱们取消不写Student的无参构造方法呢?就会出现下面的报错:
java.lang.InstantiationException: invocation.Student at java.lang.Class.newInstance(Class.java:427) at invocation.MyInvocation.getInstanceTest(MyInvocation.java:40) at invocation.MyInvocation.main(MyInvocation.java:8) Caused by: java.lang.NoSuchMethodException: invocation.Student.<init>() at java.lang.Class.getConstructor0(Class.java:3082) at java.lang.Class.newInstance(Class.java:412) ... 2 more
这是由于咱们重写了构造方法,并且是有参构造方法,若是不写构造方法,那么每一个类都会默认有无参构造方法,重写了就不会有无参构造方法了,因此咱们调用newInstance()
的时候,会报没有这个方法的错误。值得注意的是,newInstance()
是一个无参构造方法。
除了newInstance()
方法以外,其实咱们还能够经过构造函数对象获取实例化对象,怎么理解?这里只构造函数对象,而不是构造函数,也就是构造函数其实就是一个对象,咱们先获取构造函数对象,固然也可使用来实例化对象。
能够先获取一个类的全部的构造方法,而后遍历输出:
public static void testConstruct(){ try { Class<?> stduentInvocation = Class.forName("invocation.Student"); Constructor<?> cons[] = stduentInvocation.getConstructors(); for(int i=0;i<cons.length;i++){ System.out.println(cons[i]); } }catch (Exception ex){ ex.printStackTrace(); } }
输出结果:
public invocation.Student(int,java.lang.String) public invocation.Student(java.lang.String) public invocation.Student(int) public invocation.Student()
取出一个构造函数咱们能够获取到它的各类信息,包括参数,参数个数,类型等等:
public static void constructGetInstance() { try { Class<?> stduentInvocation = Class.forName("invocation.Student"); Constructor<?> cons[] = stduentInvocation.getConstructors(); Constructor constructors = cons[0]; System.out.println("name: " + constructors.getName()); System.out.println("modifier: " + constructors.getModifiers()); System.out.println("parameterCount: " + constructors.getParameterCount()); System.out.println("构造参数类型以下:"); for (int i = 0; i < constructors.getParameterTypes().length; i++) { System.out.println(constructors.getParameterTypes()[i].getName()); } } catch (Exception ex) { ex.printStackTrace(); } }
输出结果,modifier
是权限修饰符,1表示为public
,咱们能够知道获取到的构造函数是两个参数的,第一个是int,第二个是String类型,看来获取出来的顺序并不必定是咱们书写代码的顺序。
name: invocation.Student modifier: 1 parameterCount: 2 构造参数类型以下: int java.lang.String
既然咱们能够获取到构造方法这个对象了,那么咱们可不能够经过它去构造一个对象呢?答案确定是能够!!!
下面咱们用不一样的构造函数来建立对象:
public static void constructGetInstanceTest() { try { Class<?> stduentInvocation = Class.forName("invocation.Student"); Constructor<?> cons[] = stduentInvocation.getConstructors(); // 一共定义了4个构造器 Student student1 = (Student) cons[0].newInstance(9,"Sam"); Student student2 = (Student) cons[1].newInstance("Sam"); Student student3 = (Student) cons[2].newInstance(9); Student student4 = (Student) cons[3].newInstance(); System.out.println(student1); System.out.println(student2); System.out.println(student3); System.out.println(student4); } catch (Exception ex) { ex.printStackTrace(); }
输出以下:
Student{age=9, name='Sam'} Student{age=0, name='Sam'} Student{age=9, name='null'} Student{age=0, name='null'}
构造器的顺序咱们是必须一一针对的,要不会报一下的参数不匹配的错误:
java.lang.IllegalArgumentException: argument type mismatch at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at invocation.MyInvocation.constructGetInstanceTest(MyInvocation.java:85) at invocation.MyInvocation.main(MyInvocation.java:8)
经过反射咱们能够获取接口的方法,若是咱们知道某个类实现了接口的方法,一样能够作到经过类名建立对象调用到接口的方法。
首先咱们定义两个接口,一个InSchool
:
public interface InSchool { public void attendClasses(); }
一个AtHome
:
public interface AtHome { public void doHomeWork(); }
建立一个实现两个接口的类Student.java
public class Student implements AtHome, InSchool { public void doHomeWork() { System.out.println("I am a student,I am doing homework at home"); } public void attendClasses() { System.out.println("I am a student,I am attend class in school"); } }
测试代码以下:
public class Test { public static void main(String[] args) throws Exception { Class<?> studentClass = Class.forName("invocation.Student"); Class<?>[] interfaces = studentClass.getInterfaces(); for (Class c : interfaces) { // 获取接口 System.out.println(c); // 获取接口里面的方法 Method[] methods = c.getMethods(); // 遍历接口的方法 for (Method method : methods) { // 经过反射建立对象 Student student = (Student) studentClass.newInstance(); // 经过反射调用方法 method.invoke(student, null); } } } }
结果以下:
能够看出其实咱们能够获取到接口的数组,而且里面的顺序是咱们继承的顺序,经过接口的Class对象,咱们能够获取到接口的方法,而后经过方法反射调用实现类的方法,由于这是一个无参数的方法,因此只须要传null便可。
主要是使用getSuperclass()
方法获取父类,固然也能够获取父类的方法,执行父类的方法,首先建立一个Animal.java
:
public class Animal { public void doSomething(){ System.out.println("animal do something"); } }
Dog.java
继承于Animal.java
:
public class Dog extends Animal{ public void doSomething(){ System.out.println("Dog do something"); } }
咱们能够经过反射建立Dog
对象,获取其父类Animal
以及建立对象,固然也能够获取Animal
的默认父类Object
:
public class Test { public static void main(String[] args) throws Exception { Class<?> dogClass = Class.forName("invocation02.Dog"); System.out.println(dogClass); invoke(dogClass); Class<?> animalClass = dogClass.getSuperclass(); System.out.println(animalClass); invoke(animalClass); Class<?> objectClass = animalClass.getSuperclass(); System.out.println(objectClass); invoke(objectClass); } public static void invoke(Class<?> myClass) throws Exception { Method[] methods = myClass.getMethods(); // 遍历接口的方法 for (Method method : methods) { if (method.getName().equalsIgnoreCase("doSomething")) { // 经过反射调用方法 method.invoke(myClass.newInstance(), null); } } } }
输入以下:
建立一个Person.java
,里面有静态变量,非静态变量,以及public
,protected
,private
不一样修饰的属性。
public class Person { public static String type ; private static String subType ; // 名字(公开) public String name; protected String gender; private String address; @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", address='" + address + '\'' + '}'; } }
使用getFields()
能够获取到public的属性,包括static属性,使用getDeclaredFields()
能够获取全部声明的属性,不论是public
,protected
,private
不一样修饰的属性。
修改public
属性,只须要field.set(object,value)
便可,可是private
属性不能直接set,不然会报如下的错误。
Exception in thread "main" java.lang.IllegalAccessException: Class invocation03.Tests can not access a member of class invocation03.Person with modifiers "private" at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102) at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296) at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288) at java.lang.reflect.Field.set(Field.java:761) at invocation03.Tests.main(Tests.java:21)
那么须要怎么作呢?private默认是不容许外界操做其值的,这里咱们可使用field.setAccessible(true);
,至关于打开了操做的权限。
那static的属性修改和非static的同样,可是咱们怎么获取呢?
若是是public
修饰的,能够直接用类名获取到,若是是private
修饰的,那么须要使用filed.get(object)
,这个方法其实对上面说的全部的属性均可以的。
测试代码以下
public class Tests { public static void main(String[] args) throws Exception{ Class<?> personClass = Class.forName("invocation03.Person"); Field[] fields = personClass.getFields(); // 获取公开的属性 for(Field field:fields){ System.out.println(field); } System.out.println("================="); // 获取全部声明的属性 Field[] declaredFields = personClass.getDeclaredFields(); for(Field field:declaredFields){ System.out.println(field); } System.out.println("================="); Person person = (Person) personClass.newInstance(); person.name = "Sam"; System.out.println(person); // 修改public属性 Field fieldName = personClass.getDeclaredField("name"); fieldName.set(person,"Jone"); // 修改private属性 Field addressName = personClass.getDeclaredField("address"); // 须要修改权限 addressName.setAccessible(true); addressName.set(person,"东风路47号"); System.out.println(person); // 修改static 静态public属性 Field typeName = personClass.getDeclaredField("type"); typeName.set(person,"人类"); System.out.println(Person.type); // 修改静态 private属性 Field subType = personClass.getDeclaredField("subType"); subType.setAccessible(true); subType.set(person,"黄种人"); System.out.println(subType.get(person)); } }
结果:
从结果能够看出,不论是public
,仍是protected
,private
修饰的,咱们均可以经过反射对其进行查询和修改,不论是静态变量仍是非静态变量。getDeclaredField()
能够获取到全部声明的属性,而getFields()
则只能获取到public
的属性。对于非public的属性,咱们须要修改其权限才能访问和修改:field.setAccessible(true)
。
获取属性值须要使用field.get(object)
,值得注意的是:每一个属性,其自己就是对象
既然能够获取到公有属性和私有属性,那么我想,执行公有方法和私有方法应该都不是什么问题?
那下面咱们一块儿来学习一下...
先定义一个类,包含各类修饰符,以及是否包含参数,是否为静态方法,Person.java
:
public class Person { // 非静态公有无参数 public void read(){ System.out.println("reading..."); } // 非静态公有无参数有返回 public String getName(){ return "Sam"; } // 非静态公有带参数 public int readABookPercent(String name){ System.out.println("read "+name); return 80; } // 私有有返回值 private String getAddress(){ return "东方路"; } // 公有静态无参数无返回值 public static void staticMethod(){ System.out.println("static public method"); } // 公有静态有参数 public static void staticMethodWithArgs(String args){ System.out.println("static public method:"+args); } // 私有静态方法 private static void staticPrivateMethod(){ System.out.println("static private method"); } }
首先咱们来看看获取里面全部的方法:
public class Tests { public static void main(String[] args) throws Exception { Class<?> personClass = Class.forName("invocation03.Person"); Method[] methods = personClass.getMethods(); for (Method method : methods) { System.out.println(method); } System.out.println("============================================="); Method[] declaredMethods = personClass.getDeclaredMethods(); for (Method method : declaredMethods) { System.out.println(method); } } }
结果以下:
咦,咱们发现getMethods()
确实能够获取全部的公有的方法,可是有一个问题,就是他会把父类的也获取到,也就是上面图片绿色框里面的,咱们知道全部的类默认都继承了Object
类,因此它把Object
的那些方法都获取到了。
而getDeclaredMethods
确实能够获取到公有和私有的方法,不论是静态仍是非静态,可是它是获取不到父类的方法的。
那若是咱们想调用方法呢?先试试调用非静态方法:
public class Tests { public static void main(String[] args) throws Exception { Class<?> personClass = Class.forName("invocation03.Person"); Person person = (Person) personClass.newInstance(); Method[] declaredMethods = personClass.getDeclaredMethods(); for (Method method : declaredMethods) { if(method.getName().equalsIgnoreCase("read")){ method.invoke(person,null); System.out.println("==================="); }else if(method.getName().equalsIgnoreCase("getName")){ System.out.println(method.invoke(person,null)); System.out.println("==================="); }else if(method.getName().equalsIgnoreCase("readABookPercent")){ System.out.println(method.invoke(person,"Sam")); System.out.println("==================="); } } } }
结果以下,能够看出method.invoke(person,null);
是调用无参数的方法,而method.invoke(person,"Sam")
则是调用有参数的方法,要是有更多参数,也只须要在里面多加一个参数便可,返回值也一样能够获取到。
那么private
方法呢?咱们照着来试试,试试就试试,who 怕 who?
public class Tests { public static void main(String[] args) throws Exception { Class<?> personClass = Class.forName("invocation03.Person"); Person person = (Person) personClass.newInstance(); Method[] declaredMethods = personClass.getDeclaredMethods(); for (Method method : declaredMethods) { if(method.getName().equalsIgnoreCase("getAddress")){ method.invoke(person,null); } } } }
结果报错了:
Exception in thread "main" java.lang.IllegalAccessException: Class invocation03.Tests can not access a member of class invocation03.Person with modifiers "private" at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102) at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296) at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288) at java.lang.reflect.Method.invoke(Method.java:491) at invocation03.Tests.main(Tests.java:13)
一看就是没有权限,小场面,不要慌,我来操做一波,只要加上
method.setAccessible(true);
哦豁,完美解决了...
那么问题来了,上面说的都是非静态的,我就想要调用静态的方法。
固然用上面的方法,对象也能够直接调用到类的方法的:
一点问题都没有,为何输出结果有几个null,那是由于这函数是无返回值的呀,笨蛋...
若是我不想用遍历方法的方式,再去判断怎么办?能不能直接获取到我想要的方法啊?那答案确定是能够啊。
public class Tests { public static void main(String[] args) throws Exception { Class<?> personClass = Class.forName("invocation03.Person"); Person person = (Person) personClass.newInstance(); Method method = personClass.getMethod("readABookPercent", String.class); method.invoke(person, "唐诗三百首"); } }
结果和上面调用的彻底同样,图我就不放了,就一行字。要是这个方法没有参数呢?那就给一个null就能够啦。或者不给也能够。
public class Tests { public static void main(String[] args) throws Exception { Class<?> personClass = Class.forName("invocation03.Person"); Person person = (Person) personClass.newInstance(); Method method = personClass.getMethod("getName",null); System.out.println(method.invoke(person)); } }
反射能够在不知道会运行哪个类的状况下,获取到类的信息,建立对象以及操做对象。这其实很方便于拓展,因此反射会是框架设计的灵魂,由于框架在设计的时候,为了下降耦合度,确定是须要考虑拓展等功能的,不能将类型写死,硬编码。
下降耦合度,变得很灵活,在运行时去肯定类型,绑定对象,体现了多态功能。
这么好用,没有缺点?怎么可能!!!有利就有弊,事物都是有双面性的。
即便功能很强大,可是反射是须要动态类型的,JVM
没有办法优化这部分代码,执行效率相对直接初始化对象较低。通常业务代码不建议使用。
反射能够修改权限,好比上面访问到private
这些方法和属性,这是会破坏封装性的,有安全隐患,有时候,还会破坏单例的设计。
反射会使代码变得复杂,不容易维护,毕竟代码仍是要先写给人看的嘛,逃~
此文章仅表明本身(本菜鸟)学习积累记录,或者学习笔记,若有侵权,请联系做者删除。人无完人,文章也同样,文笔稚嫩,在下不才,勿喷,若是有错误之处,还望指出,感激涕零~
技术之路不在一时,山高水长,纵使缓慢,驰而不息。
公众号:「秦怀杂货店」