前言
上篇文章我們提到了可以使用反射機制破解單例模式。這篇文章我們就來談一談什麼是反射,反射有什麼用,怎麼用,怎麼實現反射。
概述
Java的反射(reflection)機制:是指在程式的執行狀態中,可以構造任意一個類的物件,可以瞭解任意一個物件所屬的類,可以瞭解任意一個類的成員變數和方法,可以呼叫任意一個物件的屬性和方法。這種動態獲取程式資訊以及動態呼叫物件的功能稱為Java語言的反射機制。反射被視為動態語言的關鍵
功能
- 在執行時判斷任意一個物件所屬的類;
- 在執行時構造任意一個類的物件;
- 在執行時判斷任意一個類所具有的成員變數和方法;
- 在執行時呼叫任意一個物件的方法;
- 生成動態代理。
java雖然不是動態語言,但是它卻有著一個非常突出的動態相關機制:Reflection。這個字的意思是“反射、映象、倒影”,用在Java身上指的是我們可以於執行時載入、探知、使用編譯期間完全未知的classes。
換句話說,Java程式可以載入一個執行時才得知名稱的class,獲悉其完整構造(但不包括methods定義),並生成其物件實體、或對其fields設值、或喚起其methods。
特點
優點:
- 能夠執行時動態獲取類的例項,大大提高系統的靈活性和擴充套件性。
- 與Java動態編譯相結合,可以實現無比強大的功能
反射機制的功能非常強大,但不能濫用。在能不使用反射完成時,儘量不要使用,原因有以下幾點:
缺點:
- 效能問題。
Java反射機制中包含了一些動態型別,所以Java虛擬機器不能夠對這些動態程式碼進行優化。因此,反射操作的效率要比正常操作效率低很多。我們應該避免在對效能要求很高的程式或經常被執行的程式碼中使用反射。而且,如何使用反射決定了效能的高低。如果它作為程式中較少執行的部分,效能將不會成為一個問題。 - 安全限制。
使用反射通常需要程式的執行沒有安全方面的限制。如果一個程式對安全性提出要求,則最好不要使用反射。 - 程式健壯性。
反射允許程式碼執行一些通常不被允許的操作,所以使用反射有可能會導致意想不到的後果。反射程式碼破壞了Java程式結構的抽象性,所以當程式執行的平臺發生變化的時候,由於抽象的邏輯結構不能被識別,程式碼產生的效果與之前會產生差異。
獲取Class物件的三種方式
三種方式
- Class.forName("全類名") 多用於配置檔案時
- 類名.class 多用於引數傳遞時
- 物件.getClass 多用於有物件例項時
==我們建立一個Person類用於實驗,分別使用三種方法獲取他們的物件,並列印他們的hashCode(),我們會發現他們的hashcode是相同的,證明他們是獲取的是相同的,所有統一個位元組碼檔案(*.class)在一次執行中,只會被載入一次。不論通過哪一種方式獲取的物件都是同一個。
package hello;
public class ReflectDemo {
public static void main(String[] args) throws Exception{
//Class.forName("全類名")
Class<?> aClass = Class.forName("hello.Person");
System.out.println(aClass);
System.out.println(aClass.hashCode());
//類名.clss
Class<Person> personClass = Person.class;
System.out.println(personClass);
System.out.println(personClass.hashCode());
//物件.getClass()
Person person = new Person();
System.out.println(person.getClass());
System.out.println(person.getClass().hashCode());
}
}
class Person{
private String name;
public Person(){
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Class物件功能
Class物件裡面大概有120多個方法,我們不可能全部都記住,也沒必要全部都記住,我們只需要記住常用的方法和功能就可以了。
獲取功能
-
獲取成員變數
- Field[] getFields()
- Field getFields(String name)
- FIeld[] getDeclaredFields()
- FIeld getDeclaredField(String name)
-
獲取構造方法
- Constructor<?>[] getConstructors()
- Constructor
getConstructor(類<?>..., parameterTypes) - Constructor
getDeclaredConstructor(類<?>..., parameterTypes) - Constructor<?>[] getDeclaredConstructors()
-
獲取成員方法
- Method[] getMethods()
- Method getMethod(String name,類<?>..., parameterType)
- Method[] getDeclaredMethods()
- Method getDeclaredMethod(String name,類<?>...,parameterTypes)
-
獲取類名
- String getName()
方法 | 作用 |
---|---|
Field[] getFields() | 獲取public的成員變數名 |
Field getFields(String name) | 獲取public的成員變數名,需要指定名稱 |
FIeld[] getDeclaredFields() | 獲取所有的成員變數,包括private |
FIeld getDeclaredField(String name) | 獲取指定的成員變數,私有的也可以獲取,如果要改這個變數的值需要setAccessible(ture)暴力反射 |
Constructor<?>[] getConstructors() | 用於返回一個建構函式物件陣列,該陣列反映此Class物件表示的類的所有公共建構函式。 |
Constructor |
獲取指定引數的構造方法 |
Constructor |
方法返回一個 Constructor 物件,該物件反映了此 Class 物件表示的類或介面的指定建構函式 |
Constructor<?>[] getDeclaredConstructors() | 用於返回一個Constructor物件陣列,該陣列指示此Class物件所表示的類定義的建構函式的型別(Constructor可以是public,private,protected或default) |
Method[] getMethods() | 公共的所有方法 |
Method getMethod(String name,類<?>..., parameterType) | 指定公共的其中的方法,parameterType時一個陣列 |
Method[] getDeclaredMethods() | 所有的包括私有的 |
Method getDeclaredMethod(String name,類<?>...,parameterTypes) | 指定的,parameterTypes表示陣列 |
String getName() | 獲取類名程 |
package hello;
import com.sun.org.apache.xerces.internal.impl.XMLEntityScanner;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectDemo {
public static void main(String[] args) throws Exception{
//獲取class物件
Class<Person> personClass = Person.class;
//Field[] getFields()
Field[] fields = personClass.getFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("******************************");
//FIeld[] getDeclaredFields()
Field[] declaredFields = personClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
System.out.println("******************************");
//Constructor<?>[] getConstructors()
Constructor<Person> constructor = personClass.getConstructor();
System.out.println(constructor);
System.out.println("******************************");
//Method[] getMethods()
Method[] methods = personClass.getMethods();
for (Method method : methods) {
System.out.println(method);
}
System.out.println("******************************");
//String getName()
System.out.println(personClass.getName());
}
}
class Person{
public int id;
private String name;
public Person(){
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
案例
建立一個功能,不改裡面的程式碼,只需要修改配置檔案就能呼叫不同的方法
第一步:建立配置檔案,”data.properties"
這個路徑一定要寫成hello.Preson,寫成斜槓會識別不了。
# 這個寫全路徑類名
className=hello.Preson
# 這個寫類裡面想要呼叫的方法
methodName=eat
第二步:具體類"Preson.java"
package hello;
public class Preson {
public String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void eat(){
System.out.println("eat....");
}
}
第三步:編寫實現程式碼,名成ReflectDemo.java
package hello;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
public class ReflectDemo {
public static void main(String[] args) throws Exception{
//載入配置檔案
//建立properties物件
Properties properties = new Properties();
//載入配置檔案,轉為一個集合
ClassLoader classLoader = ReflectDemo.class.getClassLoader();
InputStream resourceAsStream = classLoader.getResourceAsStream("hello/data.properties");
properties.load(resourceAsStream);
//獲取配置檔案定義的資料
String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");
//載入該類進入記憶體
Class aClass = Class.forName(className);
Object o = aClass.newInstance();
Method method = aClass.getMethod(methodName);
method.invoke(o);
}
}
第四步:執行,我們只需要修改properties檔案裡面的類名和方法就能實現不同類不同方法的建立了。