什麼是反射
反射常用API
通過反射和自定義註解獲取屬性信息
static void initUser(User user) throws IllegalAccessException {
// 獲取User類中所有的屬性(getFields無法獲得private屬性)
Field[] fields = User.class.getDeclaredFields();
// 遍歷所有屬性
for (Field field : fields) {
// 如果屬性上有此註解,則進行賦值操作
if (field.isAnnotationPresent(InitSex.class)) {
InitSex init = field.getAnnotation(InitSex.class);
field.setAccessible(true);
// 設置屬性的性別值
field.set(user, init.sex().toString());
System.out.println("完成屬性值的修改,修改值爲:" + init.sex().toString());
}
}
}
獲取Class對象的三種方法
第一種方法:當你知道類的全路徑名時,可使用Class.forName靜態方法來獲得Class對象。上面的示例就是通過這種方法獲得:
Class clz = Class.forName("com.choupangxia.reflect.User");
第二種方法:通過“.class”獲得。前提條件是在編譯前就能夠拿到對應的類。
Class clz = User.class;
第三種:使用類對象的getClass()方法。
User user = new User();
Class clz = user.getClass();
創建對象的兩種方法
可以通過Class對象的newInstance()方法和通過Constructor 對象的newInstance()方法創建類的實例對象。
第一種:通過Class對象的newInstance()方法。
Class clz = User.class;
User user = (User) clz.newInstance();
第二種:通過Constructor對象的newInstance()方法。
Class clz = Class.forName("com.xxx.reflect.User");
Constructor constructor = clz.getConstructor();
User user = (User) constructor.newInstance();
其中第二種方法創建類對象可以選擇特定構造方法,而通過 Class對象則只能使用默認的無參數構造方法。
Class clz = User.class;
Constructor constructor = clz.getConstructor(String.class);
User user = (User) constructor.newInstance("公衆號");
獲取類屬性、方法、構造器
通過Class對象的getFields()方法獲取非私有屬性。
Field[] fields = clz.getFields();
for(Field field : fields){
System.out.println(field.getName());
}
上述實例中的User對象屬性都是private,無法直接通過上述方法獲取,可將其中一個屬性改爲public,即可獲取。
通過Class對象的getDeclaredFields()方法獲取所有屬性。
Field[] fields = clz.getDeclaredFields();
for(Field field : fields){
System.out.println(field.getName());
}
執行打印結果:
username
age
當然針對屬性的其他值也是可以獲取的,針對私有屬性的修改需要先調用field.setAccessible(true)方法,然後再進行賦值。關於具體應用,回頭看我們最開始關於註解的實例中的使用。
獲取方法的示例如下:
Method[] methods = clz.getMethods();
for(Method method : methods){
System.out.println(method.getName());
}
打印結果:
setUsername
setAge
getUsername
getAge
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
可以看到,不僅獲取到了當前類的方法,還獲取到了該類父類Object類中定義的方法。
關於獲取構造器的方法上面已經講到了,就不再贅述。而上述的這些方法在Class中都有相應的重載的方法,可根據具體情況進行靈活使用。
利用反射創建數組
最後,我們再看一個通過反射創建數組的實例。
@Test
public void createArray() throws ClassNotFoundException {
Class<?> cls = Class.forName("java.lang.String");
Object array = Array.newInstance(cls,5);
// 向數組添加內容
Array.set(array,0,"Hello");
Array.set(array,1,"公衆號");
Array.set(array,2,"Java");
// 獲取數組中指定位置的內容
System.out.println(Array.get(array,2));
}
一個例子
public class User {
static String country;
private String name;
public int age;
private Result<String> result;
public void say(String world){
System.out.println("我說:" + world);
}
private void writeNote(){
System.out.println("寫日記");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Result<String> getResult() {
return result;
}
public void setResult(Result<String> result) {
this.result = result;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class ReflectTest {
public static void main(String[] args) throws Exception{
reflectDemo();
}
public static void reflectDemo() throws Exception{
Class clazz = Class.forName("kite.lab.reflect.User");
Field[] declaredFields = clazz.getDeclaredFields();
for(Field declaredField:declaredFields){
System.out.println(declaredField.getName());
}
Method[] methods = clazz.getDeclaredMethods();
for(Method method:methods){
System.out.println(method.getName());
}
Object userInstance = clazz.newInstance();
Method sayMethod = clazz.getDeclaredMethod("say", String.class);
sayMethod.invoke(userInstance,"你好");
Method writeNoteMethod = clazz.getDeclaredMethod("writeNote");
writeNoteMethod.setAccessible(true);
writeNoteMethod.invoke(userInstance);
}
}
總結
說起反射,大家可能都知道性能差。那反射的性能爲什麼差呢?
根本的原因就是因爲反射是動態加載,所以 jit 對其所做的優化極其有限。
jit - 即時編譯器,是 JVM 優化性能的殺手級利器,它會對熱點代碼進行一系列優化,比如非常重要的優化手段-方法內聯。而反射的代碼則享受不到這種待遇。
反射中性能最差的部分在於獲取方法和屬性的部分,比如 getMethod() 方法,是因爲獲取這個方法需要遍歷所有的方法列表,包括父類。而如果不是反射的話,那這個方法地址都是提前確定的。
還有在 method#invoke() 方法執行的過程中需要執行要對參數做封裝和解封操作,invoke 方法的參數是 Object[] 類型,所以傳入的參數要轉換爲 Object 並封裝成數組,而到了真正執行方法的時候,還要把 Object 數組解封。這樣一來一回就浪費了不少時間。
另外還需要需要檢查方法可見性和參數的校驗,這樣做是爲了保證調用安全,檢查的過程也要耗時。
如果真的是會頻繁調用反射方法,採用緩存的方案可以很大程度上優化性能。比如在第一次調用某個方法的時候將它緩存起來,下次再調用直接從緩存拿就可以了。