一、動態語言和動態語言的比較
動態語言
➢ 是一類在運行時可以改變其結構的語言:例如新的函數、對象、甚至代碼可以被引進,已有的函數可以被刪除或是其他結構上的變化。通俗點說就是在運行時代碼可以根據某些條件改變自身結構。
➢ 主要動態語言: Object-C、C#、 JavaScript、 PHP、Python等。
一個具體例子:
# test.py
a=1
s = "print(a)"
eval(s)
======運行結果======
1
可以看出,在python這種動態語言中,一個字符串拼接成的代碼可以被直接運行,這在c++這些靜態語言中是完全無法做到的,因為運行的機器代碼在編譯完成那一刻就不再改變了。
靜態語言
➢ 與動態語言相對應的,運行時結構不可變的語言就是靜態語言。如Java、 C、C++.
➢ Java不是動態語言,但Java可以稱之為“准動態語言”.。即Java有一定的動態性,我們可以利用反射機制獲得類似動態語言的特性。Java的動態性讓編程的時候更加靈活!
二、反射
簡介
Reflection (反射)是Java被視為動態語言的關鍵,反射機制允許程序在執行期借助於Reflection API取得任何類的內部信息,並能直接操作任意對象的內部屬性及方法。
得到任何類的內部信息可以:
➢ 在運行時判斷任意一個對象所屬的類
➢ 在運行時構造任意一個類的對象了
➢ 在運行時獲取任意一個類所具有的成員變量和方法
➢ 在運行時獲取泛型信息
➢ 在運行時調用任意一個對象的成員變量和方法
➢ 在運行時處理注解
➢ 生成動態代理
因此利用反射機制,在運行時我們就能通過一個類名(字符串)來調用一個類的構造函數生成實例對象,甚至可以拼接出類的成員方法使用反射進行相應的調用等等。
反射的常見使用
1. 代碼編輯器
反射最常見的使用莫過於我們編寫java程序需要依賴的代碼編輯器了,這里用IDEA為例,當我們輸入‘對象.’時,即會顯示出對象的各個成員方法的提示框,如:

代碼編輯器正是通過反射機制獲取到對象所在類的成員方法,進而以UI的方式展示在用戶的面前。
2. Spring等框架的IoC容器
此外使用Spring框架的IoC容器時,若我們使用xml文件設置JavaBean,就會發現它需要我們填入全類名,然后會在運行時在容器中保存該對象,其背后的原理就是Spring框架從xml文件中讀取到類名字符串,然后通過反射取到對應的類,進而調用該類中的構造函數新建出實例對象保存起來。
Spring中xml配置的bean:
<bean id="hello" class="pojo.Hello"> <--!填入全類名-->
<property name="str" value="Spring"/>
</bean>
3. 和注解的配合使用
如前面寫的注解文章所說,注解相當於程序的tag,需要一個讀取的工具查看tag中的信息,進而進行不同的操作。而這個讀取的工具就是反射。
例如在SpringBoot框架中,我們希望生成一個類的實例對象(單例)並將其放置到IoC容器中,只需要在哪個類打上@Component等注解即可,SpringBoot正是通過反射讀取到xx類中表明了這個注解才會將其放置到IoC容器中進行管理:
@Service <---
public class EmployeesServiceImpl extends ServiceImpl<EmployeesMapper, Employees> implements EmployeesService {
}
原理
java虛擬機在加載完類之后,在堆內存的方法區中就產生了一個Class類型的對象(一個類只有一個Class對象),這個對象就包含了完整的類的結構信息。我們可以通過這個對象看到類的結構。這個對象就像一面鏡子, 透過這個鏡子看到類的結構,所以,我們形象的稱之為:反射
反射優缺點
優點:
➢ 可以實現動態創建對象和編譯,體現出很大的靈活性
缺點:
➢對性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什么並且它滿足我們的要求。這類操作總是慢於直接執行相同的操作。
調試查看
我們先使用IDE進行調試查看一下Class對象的內部信息:
// 使用全類名獲取到對應類的Class對象
Class c1 = Class.forName("reflection.User");
可以看到這個對象中確實包含了許多這個類的內部信息。
tips:
- 一個類在內存中只有一個Class對象
- 一個類被加載后,類的結構都被封裝在Class對象中
Class類
對象照鏡子后可以得到的信息:某個類的屬性、方法和構造器、某個類到底實現了哪些接口。對於每個類而言,JRE都為其保留一個不變的Class類型的對象。一個Class對象包含了特定某個結構(class/interface/enum/annotation/primitive type/void/[])的有關信息。
➢Class本身也是一個類
➢Class 對象只能由系統建立對象
➢一個加載的類在JVM中只會有一個Class實例
➢一個Class對象對應的是一個加載到JVM中的一個class文件
➢每個類的實例都會記得自己是由哪個Class實例所生成
➢通過Class可以完整地得到一個類中的所有被加載的結構
➢Class類是Reflection的根源, 針對任何你想動態加載、運行的類,唯有先獲得相應的Class對象
獲取Class實例的方式
1. 通過對象獲得
// 方式一:通過對象獲得
Person person = new Student();
Class c1 = person.getClass();
常用的方式是某個方法接收的參數是一個抽象類或接口,然后通過這種方式可以得到輸入的參數所在的實現類或子類。
2. 通過Class.forname獲得
// 方式二:通過Class.forname獲得
Class c2 = Class.forName("reflection.Student");
輸入一個全類名,可以獲得類(.class文件)對應的Class對象。
3. 通過類名.class獲得
// 方式三:通過類名.class獲得
Class c3 = Student.class;
4.基本內置類型的包裝類用Type屬性獲得
Class c4 = Integer.TYPE;
5. 一個Class實例通過.getSuperclass()獲得父類的Class實例
Class c5 = c1.getSuperclass();
后面還有Class[] getInterfaces()等,這里不再贅述。
哪些類型可以有Class對象
➢class: 外部類,成員(成員內部類,靜態內部類), 局部內部類,匿名內部類。
➢interface: 接口
➢[]:數組
➢enum:枚舉
➢annotation: 注解@interface
➢primitive type: (基本數據類型)
➢void
測試:
public static void main(String[] args) {
Class c1 = Object.class; //類
Class c2 = Comparable.class; //接口
Class c3 = String[].class; //一維數組
Class c4 = int[][].class; //二維數組
Class c5 = Override.class; //注解
Class c6 = ElementType.class; //枚舉
Class c7 = Integer.class; //基本數據類型
Class c8 = void.class; //void
Class c9 = Class.class; //Class
// 打印...
}
=================運行結果=================
class java.lang.Object
interface java.lang.Comparable
class [Ljava.lang.String;
class [[I
interface java.lang.Override
class java.lang.annotation.ElementType
class java.lang.Integer
void
java內存分析
類的加載過程:

➢加載:將class文件字節碼內容加載到內存中,並將這些靜態數據轉換成方法區的運行時數據結構,
然后生成一個代表這個類的java.lang.Class對象.
➢鏈接:將Java類的二進制代碼合並到JVM的運行狀態之中的過程。
- ➢驗證:確保加載的類信息符合JVM規范,沒有安全方面的問題
- ➢准備:正式為類變量(
static) 分配內存並設置類變量默認初始值的階段,這些內存都將在方法區中進行分配。 - ➢解析:虛擬機常量池內的符號引用(常量名)替換為直接引用(地址)的過程。
➢初始化:
- ➢執行類構造器< clinit> ()方法的過程。類構造器< clinit> ()方法是由編譯期自動收集類中所有類變量的賦值動作和靜態
代碼塊中的語句合並產生的。(類構造 器是構造類信息的,不是構造該類對象的構造器)。 - ➢當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化。
- ➢虛擬機會保證一個類的< clinit> ()方法在多線程環境中被正確加鎖和同步。
測試例:
public class Test05 {
public static void main(String[] args) {
A a = new A();
System.out.println(A.m);
/*
1. 加載到內存,會產生一個類對應的Class對象
2. 鏈接,鏈接結束后 m=0(初始值)
3. 初始化
<clint>(){
System.out.println("A類靜態代碼塊初始化");
m=300;
m = 100;
}
m==100
*/
}
}
class A{
static {
System.out.println("A類靜態代碼塊初始化");
m=300;
}
static int m = 100;
public A(){
System.out.println("A類的無參構造初始化");
}
}
========================運行結果========================
A類靜態代碼塊初始化
A類的無參構造初始化
100
下一部分:Java 反射(二)運行時獲取類的信息
