Java春招面試複習:Java反射的入門到實踐,再到原理
前言
反射是Java底層框架的靈魂技術,學習反射非常有必要,本文將從入門概念,到實踐,再到原理講解反射,希望對大家有幫助。
反射理解
官方解析
Oracle 官方對反射的解釋是:
Reflection is commonly used by programs which require the ability to examine or
modify the runtime behavior of applications running in the Java virtual machine.
This is a relatively advanced feature and should be used only by developers who
have a strong grasp of the fundamentals of the language. With that caveat in
mind, reflection is a powerful technique and can enable applications to perform
operations which would otherwise be impossible.
Java 的反射機制是指在執行狀態中,對於任意一個類都能夠知道這個類所有的屬性和方法; 並且對於任意一個物件,都能夠呼叫它的任意一個方法;這種動態獲取資訊以及動態呼叫物件方法的功能成為Java語言的反射機制。
白話理解
正射
萬物有陰必有陽,有正必有反。既然有反射,就必有“正射”。
那麼正射是什麼呢?
我們在編寫程式碼時,當需要使用到某一個類的時候,都會先了解這個類是做什麼的。然後例項化這個類,接著用例項化好的物件進行操作,這就是正射。
Student student = new Student();
student.doHomework("數學");
反射
反射就是,一開始並不知道我們要初始化的類物件是什麼,自然也無法使用 new 關鍵字來建立物件了。
Class clazz = Class.forName("reflection.Student");
Method method = clazz.getMethod("doHomework", String.class);
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, "語文");
```java
#### 正射與反射對比
以上兩段程式碼,執行效果是一樣的,如圖
![](https://user-gold-cdn.xitu.io/2019/12/13/16efff4ccf899e66?w=1071&h=716&f=png&s=91450)
但是,其實現的過程還是有很大的差別的:
- 第一段程式碼在未執行前就已經知道了要執行的類是```Student```;
- 第二段程式碼則是到整個程式執行的時候,從字串```reflection.Student```,才知道要操作的類是```Student```。
#### 結論
反射就是在執行時才知道要操作的類是什麼,並且可以在執行時獲取類的完整構造,並呼叫對應的方法。
## Class 物件理解
要理解Class物件,我們先來了解一下**RTTI**吧。
**RTTI(Run-Time Type Identification)執行時型別識別**,其作用是在執行時識別一個物件的型別和類的資訊。
Java是如何讓我們在執行時識別物件和類的資訊的?主要有兩種方式:
一種是傳統的**RRTI**,它假定我們在編譯期已知道了所有型別。
另一種是反射機制,它允許我們在執行時發現和使用類的資訊。
**每個類都有一個Class物件**,每當編譯一個新類就產生一個Class物件(更恰當地說,是被儲存在一個同名的.class檔案中)。比如建立一個Student類,那麼,JVM就會建立一個Student對應Class類的Class物件,該Class物件儲存了Student類相關的型別資訊。
![](https://user-gold-cdn.xitu.io/2019/12/21/16f286b0e2fce054?w=1066&h=538&f=png&s=60629)
**Class類的物件作用**是執行時提供或獲得某個物件的型別資訊
## 反射的基本使用
### 獲取 Class 類物件
獲取反射中的Class物件有三種方法。
**第一種,使用 Class.forName 靜態方法。**
```java
Class class1 = Class.forName("reflection.TestReflection");
第二種,使用類的.class 方法
Class class2 = TestReflection.class;
第三種,使用例項物件的 getClass() 方法。
TestReflection testReflection = new TestReflection();
Class class3 = testReflection.getClass();
反射創造物件,獲取方法,成員變數,構造器
本小節學習反射的基本API用法,如獲取方法,成員變數等。
反射創造物件
通過反射建立類物件主要有兩種方式:
例項程式碼:
//方式一
Class class1 = Class.forName("reflection.Student");
Student student = (Student) class1.newInstance();
System.out.println(student);
//方式二
Constructor constructor = class1.getConstructor();
Student student1 = (Student) constructor.newInstance();
System.out.println(student1);
執行結果:
反射獲取類的構造器
看一個例子吧:
Class class1 = Class.forName("reflection.Student");
Constructor[] constructors = class1.getDeclaredConstructors();
for (int i = 0; i < constructors.length; i++) {
System.out.println(constructors[i]);
}
反射獲取類的成員變數
看demo:
// student 一個私有屬性age,一個公有屬性email
public class Student {
private Integer age;
public String email;
}
public class TestReflection {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class class1 = Class.forName("reflection.Student");
Field email = class1.getField("email");
System.out.println(email);
Field age = class1.getField("age");
System.out.println(age);
}
}
執行結果:
即javagetField(String name)
根據引數變數名,返回一個具體的具有public屬性的成員變數,如果該變數不是public屬性,則報異常。
反射獲取類的方法
demo
public class Student {
private void testPrivateMethod() {
}
public void testPublicMethod() {
}
}
public class TestReflection {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class class1 = Class.forName("reflection.Student");
Method[] methods = class1.getMethods();
for (int i = 0; i < methods.length; i++) {
System.out.println(methods[i]);
}
}
}
執行結果:
反射的實現原理
通過上一小節學習,我們已經知道反射的基本API用法了。接下來,跟著一個例子,學習反射方法的執行鏈路。
public class TestReflection {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("reflection.TestReflection");
Method method = clazz.getMethod("target", String.class);
method.invoke(null, "666");
}
public static void target(String str) {
//列印堆疊資訊
new Exception("#" +str).printStackTrace();
System.out.println("invoke target method");
}
}
堆疊資訊反映出反射呼叫鏈路:
java.lang.Exception: #666
invoke target method
at reflection.TestReflection.target(TestReflection.java:17)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at reflection.TestReflection.main(TestReflection.java:11)
```
**invoke方法執行時序圖**
![](https://user-gold-cdn.xitu.io/2019/12/21/16f27996f9e5ed0f?w=1794&h=1030&f=png&s=154261)
**我們跟著反射鏈路去看一下原始碼,先看Method的invoke方法:**
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
//校驗許可權
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor(); //獲取MethodAccessor
}
//返回MethodAccessor.invoke
return ma.invoke(obj, args);
}
由上可知道,Method 的 invoke 方法,其實是返回介面MethodAccessor的invoke方法。MethodAccessor介面有三個實現類,到底呼叫的是哪個類的 invoke 方法呢?
進入acquireMethodAccessor方法,可以看到MethodAccessor由ReflectionFactory 的 newMethodAccessor方法決定。
再進ReflectionFactory的newMethodAccessor方法,我們可以看到返回的是DelegatingMethodAccessorImpl物件,也就是說呼叫的是它的invoke方法。
再看DelegatingMethodAccessorImpl的invoke方法
DelegatingMethodAccessorImpl的invoke方法返回的是MethodAccessorImpl的invoke方法,而MethodAccessorImpl的invoke方法,由它的子類NativeMethodAccessorImpl重寫,這時候返回的是本地方法invoke0,如下
因此,Method的invoke方法,是由本地方法invoke0決定的,再底層就是c++相關了,有興趣的朋友可以繼續往下研究。
反射的一些應用以及問題
反射應用
反射是Java框架的靈魂技術,很多框架都使用了反射技術,如spring,Mybatis,Hibernate等。
JDBC 的資料庫的連線
在JDBC連線資料庫中,一般包括載入驅動,獲得資料庫連線等步驟。而載入驅動,就是引入相關Jar包後,通過Class.forName() 即反射技術,載入資料庫的驅動程式。
Spring 框架的使用
Spring 通過 XML 配置模式裝載 Bean,也是反射的一個典型例子。
裝載過程:
- 將程式內XML 配置檔案載入入記憶體中
- Java類解析xml裡面的內容,得到相關位元組碼資訊
- 使用反射機制,得到Class例項
- 動態配置例項的屬性,使用
這樣做當然是有好處的:
不用每次都去new例項了,並且可以修改配置檔案,比較靈活。
反射存在的問題
效能問題
java反射的效能並不好,原因主要是編譯器沒法對反射相關的程式碼做優化。
有興趣的朋友,可以看一下這個文章java-reflection-why-is-it-so-slow
安全問題
我們知道單例模式的設計過程中,會強調將構造器設計為私有,因為這樣可以防止從外部構造物件。但是反射可以獲取類中的域、方法、構造器,修改訪問許可權。所以這樣並不一定是安全的。
看個例子吧,通過反射使用私有構造器例項化。
public class Student {
private String name;
private Student(String name) {
System.out.println("我是私有構造器,我被例項化了");
this.name = name;
}
public void doHomework(String subject) {
System.out.println("我的名字是" + name);
System.out.println("我在做"+subject+"作業");
}
}
public class TestReflection {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("reflection.Student");
// 獲取私有構造方法物件
Constructor constructor = clazz.getDeclaredConstructor(String.class);
// true指示反射的物件在使用時應該取消Java語言訪問檢查。
constructor.setAccessible(true);
Student student = (Student) constructor.newInstance("jay@huaxiao");
student.doHomework("數學");
}
}
執行結果:
顯然,反射不管你是不是私有,一樣可以呼叫。
所以,使用反射通常需要程式的執行沒有安全限制。如果一個程式對安全性有強制要求,最好不要使用反射啦。
相關文章
- Java春招面試複習:執行緒池解析Java面試執行緒
- Java春招面試複習:有關於Java Map,應該掌握的8個問題Java面試
- Java入門到實踐系列(1)——Java簡介Java
- Java 從入門到精通-反射機制Java反射
- Java 反射最佳實踐Java反射
- Java反射詳解:入門+使用+原理+應用場景Java反射
- Java 反射原理Java反射
- 程式碼除錯-入門、實踐到原理除錯
- Java入門系列-27-反射Java反射
- Java物件複製原理剖析及最佳實踐Java物件
- Java學習從入門到精通Java
- JAVA後端秋招/春招準備方向Java後端
- 真正的Java學習從入門到精通Java
- 面試題-JAVA反射面試題Java反射
- 面試題--Java反射面試題Java反射
- 我是如何進入阿里巴巴的-面向春招應屆生Java面試指南(一)阿里Java面試
- Java秋招校招面試Java面試
- 淺談Java的反射原理Java反射
- 【Java 反射學習】Java 反射基礎Java反射
- [Java 反射學習] Java 反射基礎Java反射
- Java反射機制實現與原理Java反射
- Java 反射機制應用實踐Java反射
- Java反射機制應用實踐Java反射
- 真正的Java學習從入門到精通(轉)Java
- 春招面試(馭勢科技)面試
- Java學習從入門到精通的學習建議Java
- 【原創】關於JAVA複習的最佳敏捷實踐Java敏捷
- redux 入門到實踐Redux
- Java學習路線從入門到入土Java
- Java學習從入門到精通[原創]Java
- Java學習從入門到精通(3)(轉)Java
- Java學習從入門到精通(2)(轉)Java
- java實習面試Java面試
- 【Java面試指北】反射(1) 初識反射Java面試反射
- 阿里春招前端面試(1)阿里前端面試
- Java學習:反射Java反射
- Java學習_反射Java反射
- Java反射學習Java反射