[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
這些方法和屬性,這是會破壞封裝性的,有安全隱患,有時候,還會破壞單例的設計。
反射會使代碼變得複雜,不容易維護,畢竟代碼仍是要先寫給人看的嘛,逃~
此文章僅表明本身(本菜鳥)學習積累記錄,或者學習筆記,若有侵權,請聯繫做者刪除。人無完人,文章也同樣,文筆稚嫩,在下不才,勿噴,若是有錯誤之處,還望指出,感激涕零~
技術之路不在一時,山高水長,縱使緩慢,馳而不息。
公衆號:「秦懷雜貨店」