本文目錄
3.3 什麼叫對象序列化,什麼是反序列化,實現對象序列化需要做哪些工作?
1、Java反射介紹
1.1、反射簡介
反射(Reflection)是Java 程序開發語言的特徵之一,它允許運行中的 Java 程序獲取自身的信息,並且可以操作類或對象的內部屬性。
通過反射,我們可以在運行時獲得程序或程序集中每一個類型的成員和成員的信息。程序中一般的對象的類型都是在編譯期就確定下來的,而Java反射機制可以動態地創建對象並調用其屬性,這樣的對象的類型在編譯期是未知的。所以我們可以通過反射機制直接創建對象,即使這個對象的類型在編譯期是未知的。
反射的核心是JVM在運行時才動態加載類或調用方法/訪問屬性,它不需要事先(寫代碼的時候或編譯期)知道運行對象是誰。
Java反射框架主要提供以下功能:
- 1)在運行時判斷任意一個對象所屬的類;
- 2)在運行時構造任意一個類的對象;
- 3)在運行時判斷任意一個類所具有的成員變量和方法(通過反射甚至可以調用private方法);
- 4)在運行時調用任意一個對象的方法
注意:是運行時而不是編譯時 。
1.2、反射基礎:
關於Class類:
- Class是一個類,一個描述類的類(也就是描述類本身),封裝了描述方法的Method,描述字段的Filed,描述構造器的Constructor等屬性;
- =對象照鏡子後(反射)可以得到的信息:某個類的數據成員名、方法和構造器、某個類到底實現了哪些接口;
- 對於每個類而言,JRE 都爲其保留一個不變的 Class 類型的對象。一個Class對象包含了特定某個類的有關信息;
- Class 對象只能由系統建立對象;
- 一個類在 JVM 中只會有一個Class實例。
總結就是:
JDK有一個類叫做Class,這個類用來封裝所有Java類型,包括這些類的所有信息,JVM中類信息是放在方法區的。所有類在加載後,JVM會爲其在堆中創建一個Class<類名稱>的對象,並且每個類只會有一個Class對象,這個類的所有對象都要通過Class<類名稱>來進行實例化。
上面說的是JVM進行實例化的原理,當然實際上在Java寫代碼時只需要用類名稱就可以進行實例化了。
//上面說的是JVM進行實例化的原理,當然實際上在Java寫代碼時只需要用 類名稱就可以進行實例化了。
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
//虛擬機會保持唯一一
//通過類名.class獲得唯一的Class對象。
Class<UserBean> cls = UserBean.class;
//通過integer.TYPEl來獲取Class對象
Class<Integer> inti = Integer.TYPE;
//接口本質也是一個類,一樣可以通過.class獲取
Class<User> userClass = User.class;
JAVA反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。要想解剖一個類,必須先要獲取到該類的字節碼文件對象。而解剖使用的就是Class類中的方法.所以先要獲取到每一個字節碼文件對應的Class類型的對象。
什麼是反射總結:反射就是把java類中的各種成分映射成一個個的Java對象。 例如:一個類有:成員變量、方法、構造方法、包等等信息,利用反射技術可以對一個類進行解剖,把個個組成部分映射成一個個對象。(其實:一個類中這些成員方法、構造方法、在加入類中都有一個類來描述) 如圖是類的正常加載過程:反射的原理在與class對象。 熟悉一下加載的時候:Class對象的由來是將class文件讀入內存,併爲之創建一個Class對象。
1.3、反射用途
很多人都認爲反射在實際的Java開發應用中並不廣泛,其實不然。當我們在使用IDE(如Eclipse,IDEA)時,當我們輸入一個對象或類並想調用它的屬性或方法時,一按點號,編譯器就會自動列出它的屬性或方法,這裏就會用到反射。
反射最重要的用途就是開發各種通用框架。很多框架(比如Spring)都是配置化的(比如通過XML文件配置JavaBean,Action之類的),爲了保證框架的通用性,它們可能需要根據配置文件加載不同的對象或類,調用不同的方法,這個時候就必須用到反射——運行時動態加載需要加載的對象。
對於框架開發人員來說,反射雖小但作用非常大,它是各種容器實現的核心。而對於一般的開發者來說,不深入框架開發則用反射用的就會少一點,不過了解一下框架的底層機制有助於豐富自己的編程思想,也是很有益的。
1.4、爲什麼要使用反射
Java中編譯類型有兩種:
(1)靜態編譯:
在編譯時確定類型,綁定對象即通過。
動態編譯:運行時確定類型,綁定對象。
動態編譯最大限度地發揮了Java的靈活性,體現了多態的應用,可以減低類之間的耦合性。
Java反射是Java被視爲動態(或準動態)語言的一個關鍵性質。這個機制允許程序在運行時透過Reflection APIs取得任何一個已知名稱的class的內部信息,包括其modifiers(諸如public、static等)、superclass(例如Object)、實現之interfaces(例如Cloneable),也包括fields和methods的所有信息,並可於運行時改變fields內容或喚起methods。
Reflection可以在運行時加載、探知、使用編譯期間完全未知的classes。即Java程序可以加載一個運行時才得知名稱的class,獲取其完整構造,並生成其對象實體、或對其fields設值、或喚起其methods。
反射(reflection)允許靜態語言在運行時(runtime)檢查、修改程序的結構與行爲。 在靜態語言中,使用一個變量時,必須知道它的類型。在Java中,變量的類型信息在編譯時都保存到了class文件中,這樣在運行時才能保證準確無誤;換句話說,程序在運行時的行爲都是固定的。如果想在運行時改變,就需要反射這東西了。
實現Java反射機制的類都位於java.lang.reflect包中:
- Class類:代表一個類;
- Field類:代表類的成員變量(類的屬性);
- Method類:代表類的方法 ;
- Constructor類:代表類的構造方法;
- Array類:提供了動態創建數組,以及訪問數組的元素的靜態方法;
一句話概括就是使用反射可以賦予jvm動態編譯的能力,否則類的元數據信息只能用靜態編譯的方式實現,例如熱加載,Tomcat的classloader等等都沒法支持。
2、Java反射的使用
2.1、反射的基本使用
上面我們提到了反射可以用於判斷任意對象所屬的類,獲得Class對象,構造任意一個對象以及調用一個對象。這裏我們介紹一下基本反射功能的實現(反射相關的類一般都在java.lang.relfect包裏) :
2.1.1 獲取Class對象
(1)使用Class類的forName靜態方法:
public static Class<?> forName(String className)
(2)直接獲取某一個對象的class,示例:
//Class<?>是一個泛型表示,用於獲取一個類的類型。
Class<?> klass = int.class;
Class<?> classInt = Integer.TYPE;
(3)調用某個對象的getClass()方法,示例:
StringBuilder str = new StringBuilder("123");
Class<?> klass = str.getClass();
2.1.2 判斷是否爲某個類的實例
一般地,我們用instanceof關鍵字來判斷是否爲某個類的實例。同時我們也可以藉助反射中Class對象的isInstance()方法來判斷是否爲某個類的實例,它是一個Native方法:
public native boolean isInstance(Object obj);
2.1.3 創建實例
通過反射來生成對象主要有兩種方式:
(1)使用Class對象的newInstance()方法來創建Class對象對應類的實例。需要注意:利用newInstance創建對象:調用的類必須有無參的構造器。
//Class<?>代表任何類的一個類對象。
//使用這個類對象可以爲其他類進行實例化
//因爲jvm加載類以後自動在堆區生成一個對應的*.Class對象
//該對象用於讓JVM對進行所有*對象實例化。
Class<?> c = String.class;
//Class<?> 中的 ? 是通配符,其實就是表示任意符合泛類定義條件的類,和直接使用 Class
//效果基本一致,但是這樣寫更加規範,在某些類型轉換時可以避免不必要的 unchecked 錯誤。
Object str = c.newInstance();
(2)先通過Class對象獲取指定的Constructor對象,再調用Constructor對象的newInstance()方法來創建實例。這種方法可以用指定的構造器構造類的實例。
//獲取String所對應的Class對象
Class<?> c = String.class;
//獲取String類帶一個String參數的構造器
Constructor constructor = c.getConstructor(String.class);
//根據構造器創建實例
Object obj = constructor.newInstance("23333");
System.out.println(obj);
2.1.4 獲取方法
獲取某個Class對象的方法集合,主要有以下幾個方法:
(1)getDeclaredMethods()方法返回類或接口聲明的所有方法,包括公共、保護、默認(包)訪問和私有方法,但不包括繼承的方法:
public Method[] getDeclaredMethods() throws SecurityException
(2)etMethods()方法返回某個類的所有公用(public)方法,包括其繼承類的公用方法:
public Method[] getMethods() throws SecurityException
(3)getMethod方法返回一個特定的方法,其中第一個參數爲方法名稱,後面的參數爲方法的參數對應Class的對象
public Method getMethod(String name, Class<?>... parameterTypes)
2.1.5 獲取構造器信息
獲取類構造器的用法與上述獲取方法的用法類似。主要是通過Class類的getConstructor方法得到Constructor類的一個實例,而Constructor類有一個newInstance方法可以創建一個對象實例:
public class 打印構造方法 {
public static void main(String[] args) {
// constructors
Class<?> clazz = UserBean.class;
Class userBeanClass = UserBean.class;
//獲得所有的構造方法
Constructor[] constructors = userBeanClass.getDeclaredConstructors();
for (Constructor constructor : constructors) {
String s = Modifier.toString(constructor.getModifiers()) + " ";
s += constructor.getName() + "(";
//構造方法的參數類型
Class[] parameters = constructor.getParameterTypes();
for (Class parameter : parameters) {
s += parameter.getSimpleName() + ", ";
}
s += ")";
System.out.println(s);
//打印結果//public com.javase.反射.UserBean(String, long, )
}
}
}
2.1.6 獲取類的成員變量信息
主要是這幾個方法:
getFiled: 訪問公有的成員變量;
getDeclaredField:所有已聲明的成員變量。但不能得到其父類的成員變量;
getFileds和getDeclaredFields用法同上。
public class 打印成員變量 {
public static void main(String[] args) {
Class userBeanClass = UserBean.class;
//獲得該類的所有成員變量,包括static private
Field[] fields = userBeanClass.getDeclaredFields();
for(Field field : fields) {
//private屬性即使不用下面這個語句也可以訪問
// field.setAccessible(true);
//因爲類的私有域在反射中默認可訪問,所以flag默認爲true。
String fieldString = "";
fieldString += Modifier.toString(field.getModifiers()) + " "; // `private`
fieldString += field.getType().getSimpleName() + " "; // `String`
fieldString += field.getName(); // `userName`
fieldString += ";";
System.out.println(fieldString);
//打印結果
// public String userName;
// protected int i;
// static int j;
// private int l;
// private long userId;
}
}
}
2.1.7 調用方法
當我們從類中獲取了一個方法後,我們就可以用invoke()方法來調用這個方法。invoke方法的原型爲:
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
2.2、利用反射創建數組
數組在Java裏是比較特殊的一種類型,它可以賦值給一個Object Reference。下面我們看一看利用反射創建數組的例子:
public class 用反射創建數組 {
public static void main(String[] args) {
Class<?> cls = null;
try {
cls = Class.forName("java.lang.String");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Object array = Array.newInstance(cls,25);
//往數組裏添加內容
Array.set(array,0,"hello");
Array.set(array,1,"Java");
Array.set(array,2,"fuck");
Array.set(array,3,"Scala");
Array.set(array,4,"Clojure");
//獲取某一項的內容
System.out.println(Array.get(array,3));
//Scala
}
}
其中的Array類爲java.lang.reflect.Array類。我們通過Array.newInstance()創建數組對象,它的原型是:
public static Object newInstance(Class<?> componentType, int length)
throws NegativeArraySizeException {
return newArray(componentType, length);
}
而newArray()方法是一個Native方法, 源碼如下:
private static native Object newArray(Class<?> componentType, int length)
throws NegativeArraySizeException;
3、Java反射常見問題總結
3.1 什麼是反射?
反射是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲 Java 語言的反射機制。
3.2 哪裏用到反射機制?
JDBC中,利用反射動態加載了數據庫驅動程序。 Web服務器中利用反射調用了Sevlet的服務方法。IDEA等開發工具利用反射動態刨析對象的類型與結構,動態提示對象的屬性和方法。 很多框架都用到反射機制,注入屬性,調用方法,如Spring。
3.3 什麼叫對象序列化,什麼是反序列化,實現對象序列化需要做哪些工作?
(1)對象序列化,將對象中的數據編碼爲字節序列的過程。
(2)反序列化;將對象的編碼字節重新反向解碼爲對象的過程。
JAVA提供了API實現了對象的序列化和反序列化的功能,使用這些API時需要遵守如下約定:
-
被序列化的對象類型需要實現序列化接口,此接口是標誌接口,沒有聲明任何的抽象方法,JAVA編譯器識別這個接口,自動的爲這個類添加序列化和反序列化方法。
-
爲了保持序列化過程的穩定,建議在類中添加序列化版本號。
-
不想讓字段放在硬盤上就加transient
以下情況需要使用 Java 序列化:
-
想把的內存中的對象狀態保存到一個文件中或者數據庫中時候;
-
想用套接字在網絡上傳送對象的時候;
-
想通過RMI(遠程方法調用)傳輸對象的時候。
3.4 反射機制的優缺點?
(1)優點:
可以動態執行,在運行期間根據業務功能動態執行方法、訪問屬性,最大限度發揮了java的靈活性。
(2)缺點:
對性能有影響,這類操作總是慢於直接執行java代碼。
3.5 動態代理是什麼?有哪些應用?
動態代理是運行時動態生成代理類。 動態代理的應用有 Spring AOP數據查詢、測試框架的後端 mock、rpc,Java註解對象獲取等。
3.6 怎麼實現動態代理?
JDK 原生動態代理和 cglib 動態代理。 JDK 原生動態代理是基於接口實現的,而 cglib 是基於繼承當前類的子類實現的。
3.7 Java反射機制的作用
- 在運行時判斷任意一個對象所屬的類;
- 在運行時構造任意一個類的對象 ;
- 在運行時判斷任意一個類所具有的成員變量和方法;
- 在運行時調用任意一個對象的方法
3.8 如何使用Java的反射?
(1)通過一個全限類名創建一個對象
Class.forName(“全限類名”);
例如:com.mysql.jdbc.Driver Driver類已經被加載到 jvm中,並且完成了類的初始化工作就行了 。
類名.class;
獲取Class<?> clz 對象
對象.getClass();
(2)獲取構造器對象,通過構造器new出一個對象
Clazz.getConstructor([String.class]);
Con.newInstance([參數]);
(3)通過class對象創建一個實例對象(就相當與new類名()無參構造器)
Cls.newInstance();
(4)通過class對象獲得一個屬性對象
Field c=cls.getFields():獲得某個類的所有的公共(public)的字段,包括父類中的字段。
Field c=cls.getDeclaredFields():獲得某個類的所有聲明的字段,即包括public、private和proteced,但是不包括父類的聲明字段。
(5)通過class對象獲得一個方法對象
Cls.getMethod(“方法名”,class……parameaType);(只能獲取公共的)
Cls.getDeclareMethod(“方法名”);(獲取任意修飾的方法,不能執行私有)
M.setAccessible(true);(讓私有的方法可以執行)
(6)調用方法執行
--Method.invoke(obj實例對象,obj可變參數),-----(是有返回值的)。
附言:
本文整理來源於網絡、博客等資源,僅做個人學習筆記複習所用。
如果對你學習有用,請點贊共同學習!
如有侵權,請聯繫我刪!