文章目錄
概述 |
爲什麼需要反射
在解釋反射前先認識一下java的靜態語言是什麼。
動態語言
是一類在運行時可以改變其結構的語言:例如新的函數、對象、甚至代碼可以被引進,已有的函數可以被刪除或是其他結構上的變化。通俗點說就是在運行時代碼可以根據某些條件改變自身結構。
主要動態語言:Object-C、C#、JavaScript、PHP、Python、Erlang。
靜態語言
與動態語言相對應的,運行時結構不可變的語言就是靜態語言。
如:Java、C、C++。
java是靜態語言??那爲什麼java還能夠如此靈活受歡迎呢?java雖然不是動態語言,但是它可以成爲“準動態語言”,也就是說java有一定的動態性,即可以利用反射機制、字節碼操作獲得類似動態語言的特性。java的動態性讓編程的時候更加靈活了!
反射是什麼
Reflection(反射)是被視爲動態語言的關鍵,反射機制允許程序在執行期間藉助於Reflection API取得任何類的內部信息,並且能夠直接操作任意對象的內部屬性及方法。
那反射是如何實現的呢?在JVM層面,在加載完類之後,在堆內存的方法區中就產生了一個Class類型的對象(一個類只有一個Class對象),這個對象就可以包含了完整的類的結構信息。我們可以通過這個對象看到類的結構。這個對象就像一面鏡子,投過這個鏡子看到類的結構,所以,我們形容的稱之爲:反射。
示例
在靜態語言中,使用一個變量時,必須知道他的類型。在java中,變量的類型信息在編譯時都保存到了class文件中,這樣在運行時才能保證準確無誤;換句話說,程序在運行時的行爲都是固定的,如果想在運行時改變,使用反射就可以滿足。
在Spring中,有這樣的java bean配置:
<bean id="someID" class="net.liujiacai.Foobar">
<property name="someField" value="someValue" />
</bean>
spring在處理這個bean標籤時,發現class屬性指定的是net.liujiacai.Foobar這個類,就會調用Class.forName(String)來實例化這個類,再通過反射,可以取到someField屬性的值了。
如果我們想改變這個程序運行時的信息,我們這裏直接修改bean,property的屬性即可,無需重新編譯。
在動態語言中,使用變量不需要聲明類型,因而不需要這反射這種機制。
比如在javascript中,我們知道有個變量foobar,不管foobar有沒有sayHello()屬性,我們都可以這麼寫:
foobar.sayHello()
因爲沒有類型檢查,這裏這麼寫是允許的。至於在運行時報不報錯,就要看運行時foobar的真正值了。
反射的實現--API |
java反射提供的功能
- 在運行時判斷任意一個對象所屬的類
- 在運行時構造任意一個類的對象
- 在運行時判斷一個類所具有的成員變量和方法
- 在運行時獲取泛型信息
- 在運行時條用任意一個對象的成員變量和方法
- 在運行時處理註解
- 生成動態代理
反射是可以得到任何他想要的類的信息啊,下面具體如何通過Reflection API來實現上述的功能。
獲取Class的實例
首先定義一個Person類:
public class Person {
private String name;
public int age;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private Person(String name) {
this.name = name;
}
public Person() {
System.out.println("Person()");
}
public void show(){
System.out.println("你好,我是一個人");
}
private String showNation(String nation){
System.out.println("我的國籍是:" + nation);
return nation;
}
}
獲取Class類的實例的四種方式:
@Test
public void test3() throws ClassNotFoundException {
//方式一:調用運行時類的屬性:.class
Class clazz1 = Person.class;
System.out.println(clazz1);
//方式二:通過運行時類的對象,調用getClass()
Person p1 = new Person();
Class clazz2 = p1.getClass();
System.out.println(clazz2);
//方式三:調用Class的靜態方法:forName(String classPath)
Class clazz3 = Class.forName("com.rxs.java.Person");
// clazz3 = Class.forName("java.lang.String");
System.out.println(clazz3);
System.out.println(clazz1 == clazz2);
System.out.println(clazz1 == clazz3);
//方式四:使用類的加載器:ClassLoader
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.rxs.java.Person");
System.out.println(clazz4);
System.out.println(clazz1 == clazz4);
}
class com.rxs.java.Person
Person()
class com.rxs.java.Person
class com.rxs.java.Person
true
true
class com.rxs.java.Person
true
獲取ClassLoader
實踐類加載機制中的過程。
@Test
public void test1(){
//對於自定義類,使用系統類加載器進行加載
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
//調用系統類加載器的getParent():獲取擴展類加載器
ClassLoader classLoader1 = classLoader.getParent();
System.out.println(classLoader1);
//調用擴展類加載器的getParent():無法獲取引導類加載器
//引導類加載器主要負責加載java的核心類庫,無法加載自定義類的。
ClassLoader classLoader2 = classLoader1.getParent();
System.out.println(classLoader2);
ClassLoader classLoader3 = String.class.getClassLoader();
System.out.println(classLoader3);
}
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@a09ee92
null
null
創建運行時的類的對象
newInstance():調用此方法,創建對應的運行時類的對象。內部調用了運行時類的空參的構造器。
要想此方法正常的創建運行時類的對象,要求:
1.運行時類必須提供空參的構造器
2.空參的構造器的訪問權限得夠。通常,設置爲public。
在javabean中要求提供一個public的空參構造器。原因:
1.便於通過反射,創建運行時類的對象
2.便於子類繼承此運行時類時,默認調用super()時,保證父類有此構造器
@Test
public void test1() throws IllegalAccessException, InstantiationException {
Class<Person> clazz = Person.class;
Person obj = clazz.newInstance();
System.out.println(obj);
}
Person()
Person{name='null', age=0}
獲取類的屬性信息
獲取類中屬性的權限修飾符、數據類型、變量名:
@Test
public void test2(){
Class clazz = Person.class;
Field[] declaredFields = clazz.getDeclaredFields();
for(Field f : declaredFields){
//1.權限修飾符
int modifier = f.getModifiers();
System.out.print(Modifier.toString(modifier) + "\t");
//2.數據類型
Class type = f.getType();
System.out.print(type.getName() + "\t");
//3.變量名
String fName = f.getName();
System.out.print(fName);
System.out.println();
}
}
private java.lang.String name
public int age
獲取類中的方法信息
獲取類中方法的權限修飾符 返回值類型 方法名(參數類型1 形參名1,…) :
@Test
public void test2(){
Class clazz = Person.class;
Method[] declaredMethods = clazz.getDeclaredMethods();
for(Method m : declaredMethods){
//1.獲取方法聲明的註解
Annotation[] annos = m.getAnnotations();
for(Annotation a : annos){
System.out.println(a);
}
//2.權限修飾符
System.out.print(Modifier.toString(m.getModifiers()) + "\t");
//3.返回值類型
System.out.print(m.getReturnType().getName() + "\t");
//4.方法名
System.out.print(m.getName());
System.out.print("(");
//5.形參列表
Class[] parameterTypes = m.getParameterTypes();
if(!(parameterTypes == null && parameterTypes.length == 0)){
for(int i = 0;i < parameterTypes.length;i++){
if(i == parameterTypes.length - 1){
System.out.print(parameterTypes[i].getName() + " args_" + i);
break;
}
System.out.print(parameterTypes[i].getName() + " args_" + i + ",");
}
}
System.out.print(")");
//6.拋出的異常
Class[] exceptionTypes = m.getExceptionTypes();
if(exceptionTypes.length > 0){
System.out.print("throws ");
for(int i = 0;i < exceptionTypes.length;i++){
if(i == exceptionTypes.length - 1){
System.out.print(exceptionTypes[i].getName());
break;
}
System.out.print(exceptionTypes[i].getName() + ",");
}
}
System.out.println();
}
}
public java.lang.String toString()
public java.lang.String getName()
public void setName(java.lang.String args_0)
public void setAge(int args_0)
public int getAge()
public void show()
private java.lang.String showNation(java.lang.String args_0)
調用運行時類中的指定的構造器
@Test
public void testConstructor() throws Exception {
Class clazz = Person.class;
//private Person(String name)
/*
1.獲取指定的構造器
getDeclaredConstructor():參數:指明構造器的參數列表
*/
Constructor constructor = clazz.getDeclaredConstructor(String.class);
//2.保證此構造器是可訪問的
constructor.setAccessible(true);
//3.調用此構造器創建運行時類的對象
Person per = (Person) constructor.newInstance("Tom");
System.out.println(per);
}
Person{name='Tom', age=0}
擴展
這些都是Class類常用的方法,在此就不一一展開了。正是因爲反射機制提供的這些API,使得能夠載運行時實現動態的效果,提高java程序的靈活性,在學習源碼時反射總是能巧妙並且簡單的使程序更加優美,學無止境,一起進步!
感謝大神們的分享:
https://segmentfault.com/q/1010000002583151
https://www.cnblogs.com/onlywujun/p/3519037.html
https://www.zhihu.com/question/28570203