1. 程式人生 > >Java基礎筆記9——反射、Junit、註解、單例模式

Java基礎筆記9——反射、Junit、註解、單例模式

Java基礎筆記9
十五、反射
通過獲取指定類的Class資訊,剖析該類具有的屬性、方法、構造方法等等資訊。這個過程就是反射的過程。
剖析類——得到類內部資訊來實現特定功能。
能夠實現解耦操作。
Class — 代表位元組碼的類 — 代表類的類——反射的核心
Field — 代表屬性的類
Constructor — 代表構造方法的類
Method — 代表方法的類
Package — 代表包的類
Annotation代表註解的類
Class類
如何去獲取一個Class物件?
①類名.class——當只有類的時用這個方法
Class clz1 = Person.class;
②類的物件.getClass()——當具有類的物件時用這個方法
Person p = new Person();
Class clz2 = p.getClass();
③Class.forName(“類的全路徑名稱”)——當既沒有類也沒有物件,只知道類的全路徑名稱的時候呼叫這個方法。
Class clz3 = Class.forName(“day24.reflect.Person”);
這個方法將會到記憶體中檢查是否存在該類的位元組碼。如果存在,則直接返回代表該類的位元組碼的Class物件;如果不存在,則先載入類到記憶體,返回該類的位元組碼的Class物件。
所以,這個方法也是最簡單的載入類的方法,當需要明確的載入某個類到記憶體時,可以呼叫這個方法。
由於一個類的位元組碼在記憶體中只有一份,而在虛擬機器中會有一個Class物件代表這段位元組碼,所以在一個程式中,對於同一個類獲取多次Class得到的將是同一個物件。
System.out.println(clz1 == clz2);
System.out.println(clz1 == clz3);
獲取例項物件
1.new物件
2.位元組碼物件.newInstance() 注意:必須有無參構造才可以使用
3.如果想使用這個類中含參構造來建立對應的例項物件,那麼需要先獲取這個類中對應形式的建構函式,然後再呼叫newInstance(Object…)來建立例項物件
4.如果在獲取這個類中的屬性/方法/構造方法的時候,這些屬性/方法是非公有的,那麼在執行之前需要進行暴力破解—setAccessiable
Class中提供的方法:
T cast(Object obj) //進行強制型別轉換的方法
Class<?>[] getInterfaces() //獲取這個類實現過的所有的介面
String getName() //獲取Class代表的位元組碼的類或介面的全路徑名稱
String getSimpleName() //獲取Class代表的位元組碼的類或介面的簡單名稱(不帶包名的類名)
Package getPackage() //獲取Class代表的類或介面的包資訊
Class<? super T> getSuperclass() //獲取Class代表的類或介面的超類(父類)
boolean isInterface() //判斷當前Class是否是一個介面
boolean isEnum() //判斷當前Class是否是一個列舉類
T newInstance() //建立本類的一個物件 這個方法的呼叫有一個前提條件 這個類必須有共有的無參建構函式。
Method[] getMethods() //獲取類中的所有方法
Method getMethod(String name, Class<?>… parameterTypes)
Constructor 方法:
String getName() //獲取構造方法的名字
Class<?>[] getParameterTypes() //獲取構造方法引數們的型別
T newInstance(Object… initargs)
構造方法資訊:
Constructor<?>[] getConstructors()
Constructor getConstructor(Class<?>… parameterTypes)
Method方法
String getName() //獲取方法名
Class<?>[] getParameterTypes() //獲取方法的引數型別
Class<?> getReturnType() //獲取方法的返回值型別
Object invoke(Object obj, Object… args) //在傳入的物件上 應用傳入的引數 執行當前方法
屬性的資訊
Field[] getFields() //獲取所有屬性
Field getField(String name) //獲取指定屬性
Field方法
String getName() //獲取屬性名稱
Class<?> getType() //獲取屬性型別
Object get(Object obj) //傳入一個物件 獲取該物件上該屬性的值
boolean getBoolean(Object obj)
byte getByte(Object obj)
char getChar(Object obj)
double getDouble(Object obj)
float getFloat(Object obj)
int getInt(Object obj)
long getLong(Object obj)
short getShort(Object obj) //傳入一個物件 獲取該物件上該屬性的值
void set(Object obj, Object value) //傳入一個物件 和 一個值 在該物件上將該值設定為 傳入的值
setBoolean(Object obj, boolean z)
void setByte(Object obj, byte b)
void setChar(Object obj, char c)
void setDouble(Object obj, double d)
void setFloat(Object obj, float f)
void setInt(Object obj, int i)
void setLong(Object obj, long l)
void setShort(Object obj, short s) //傳入一個物件 和 一個值 在該物件上將該值設定為 傳入的值
操作私有和保護成員
Constructor<?>[] getDeclaredConstructors()
Constructor getDeclaredConstructor(Class<?>… parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, Class<?>… parameterTypes)
Field[] getDeclaredFields()
Field getDeclaredField(String name)
以上方法可以幫我們獲取到私有 保護 預設的成員 但是獲取到後 如果訪問許可權不足 仍然無法使用
如果想要讓沒有訪問許可權的成員可以被訪問 可以呼叫 setAccessible 設定為true 就可以強行訪問 這種方式讓我們反射可以訪問類中的本來沒有訪問許可權的成員 雖然方便 但是會破壞類的封裝的特性 所以在使用時一定要謹慎 充分考慮是否可以承擔這樣的風險。
反射的用處
在正常的程式碼中是用不上反射技術的,更多的是在設計框架的過程中使用反射。

程式碼除錯
1.junit測試框架
@Test //測試方法上標註的註解
@Before //標註的方法將在@Test方法執行前執行
@After //標註的方法將在@Test方法執行後執行
@BeforeClass //在測試類載入後立即執行 – 必須寫在靜態方法上
@AfterClass //在測試類銷燬前立即執行-- 必須寫在靜態方法上
注意:@Test只能用在 公有的 非靜態 無返回值 無引數的方法上

2.Debug除錯模式
	打斷點
	單步執行
	單步鑽入
	單步跳出
	重新執行方法
	放行斷點

進入debug模式,執行到非eclipse系統自帶程式碼時需要匯入原始碼。
十六、註解
頂級父類:Annotation
解釋程式—給機器看的提示資訊。
註釋:給人看的提示資訊
註解可以定義屬性
用@interface來定義註解—jdk1.5出現的(宣告介面是interface,沒有@字元,要注意區分開來)
@Override重寫(介面或父類方法)
@SuppressWarnings—壓制警告
屬性:資料型別 屬性名() default 預設值
可以使用的資料型別:基本型別、String、Class、列舉、註解及以上型別對應的一維陣列。
如果賦值的時候屬性名為value並且只有一個屬性,那麼可以省略value不寫。
元註解:修飾註解的註解,控制註解的行為特性。
@Target—限制註解的使用範圍的— 如果註解沒有指定範圍,則表示任意一個範圍都可以使用
@Retention — 控制註解被保留到什麼時候
RetentionPolicy.SOURCE – 該註解被保留到原始碼階段 在編譯的過程中該註解被刪除 – 這種註解 是給編譯器看的 用來控制 編譯器的行為的
RetentionPolicy.CLASS – 該註解在原始碼中 和 編譯後的.class中都有保留 但是 在.class被載入為位元組碼的過程中 被刪除 – 給類載入器看的 控制類載入器的行為
RetentionPolicy.RUNTIME – 該註解 在原始碼中 .class中 和 記憶體的位元組碼中 都可以找到 – 這種註解 在記憶體的位元組碼中存在 也就意味著 程式設計師 可以通過反射技術 得到註解的資訊 來控制程式的執行 這也是 我們最常用的級別
@Documented — 表示當前這個註解能夠生成到文件中
@Inherited — 控制註解是否具有繼承性
註解中還可以宣告屬性
註解中宣告屬性的過程 非常類似於 在介面中定義方法 只不過 註解中宣告的屬性 必須是public的或者不寫訪問許可權修飾符 預設就是public的。
註解中的屬性 只能是 八種基本資料型別 Class型別 String型別 其他註解型別 及 這些型別的一維陣列
註解中宣告的屬性 需要在使用註解時 為其賦值 賦值的方式 就是使用註解時 在註解後跟一對小括號 在其中 通過 屬性名=屬性值 的方式 指定屬性的值
也可以在宣告註解時 在註解的屬性後 通過default關鍵字 宣告屬性的預設值 宣告過預設值的屬性 可以在使用註解時 不賦值 則預設採用預設值 也可以手動賦值 覆蓋預設值
如果屬性是 一維陣列型別 而 在傳入的陣列中 只有一個值 則包括陣列的大括號可以省略
如果註解的屬性 只有一個需要賦值 且該屬性的名稱叫做value 則在使用註解時 value= 可以不寫

反射註解:
	在Class Method Field Constructor..等類上 都有獲取註解資訊的方法
		Annotation[] getAnnotations()  //獲取所有註解
		<A extends Annotation> A getAnnotation(Class<A> annotationClass)  //獲取指定註解
		boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)  //是否在該元素上 標註過指定註解
	可以通過這些方法 反射得到 類上 方法 屬性 構造器..等位置上標註過的註解 從而 通過 是否有註解 或 註解上屬性的取值不同控制程式的執行過程。

	**只有@Retention 是 RetentionPolicy.RUNTIME級別的註解才可以被反射

**註解常用作 輕量級的配置 在後續學習的框架中 有非常廣泛 而 靈活的運用

列舉
頂級父類:Enum
用在取值情況相對固定並且能夠一一列舉的情況下使用。
列舉常量必須定義在列舉類的第一行。
是一種特殊的類,無法直接建立物件,只能在列舉內部宣告的固定個數的值。本質上是一個私有化構造方法的抽象類,在內部建立這個類被final的類供外界使用。
**列舉類可以包含屬性
**列舉類也可以自己宣告構造方法 必須是私有的
**列舉類也可以包含普通方法
**列舉類也可以包含抽象方法
ordinal() 返回列舉常量的序數(它在列舉宣告中的位置,其中初始常量序數為零)。
從JDK1.5開始,switch case支援列舉。
泛型
JDK1.5特性之一。
就是一種不確定的型別。
引數化型別——對應介面ParameterizedType
早期的集合List list=new ArrayList();元素型別是Object,可以新增的元素型別多樣,使用不方便,容易混淆。
**泛型的使用:可以兩邊沒有 也可以一邊有一邊沒有 也可以兩邊都有 但是一旦兩邊都有 必須一模一樣 泛型中沒有繼承關係的概念!如下例:
List list=new ArrayList();//前後泛型型別不一致,編譯報錯
後來用泛型規範元素型別List list=new ArrayList(),E代表泛型,可以是String類、Integer包裝類、可以是物件類。
在類名或介面名後新增<>,<>中定義泛型的名字。
泛型在命名的時候只要是遵循識別符號命名原則就行。如。但是習慣上用一個大寫字母表示,常用、、、、(分別表示:type、element、value、key、result)
泛型變數在定義的時候不允許初始化。
如果一個類要定義多個泛型,用,號隔開,如<K,V>,理論上不限制泛型的個數。
自定義泛型
方法上的泛型
方法上的泛型的作用範圍是當前方法
方法上的泛型 需要先定義再使用
方法上的泛型通過尖括號 在方法的返回值前進行宣告
泛型的名稱可以隨意的取 但是一般都取為一個大寫字母 也可以在一個方法中定義多個泛型 只需要在尖括號中通過逗號分割依次宣告就可以了
方法上的泛型 的 具體型別 是在方法被呼叫的時啟動推測而知

類上的泛型
		類上定義的泛型的作用範圍是當前類的內部
		類上定義的泛型 需要先定義再使用
		類上的泛型通過尖括號 在類名後進行宣告
		泛型的名稱可以隨意的取 但是一般都取為一個大寫字母 也可以在一個方法中定義多個泛型 只需要在尖括號中通過逗號分割依次宣告就可以了
		類上定義的泛型 需要在建立類的物件或引用時 明確的指定泛型的具體型別		
		注意,類上定義的泛型,不能在靜態方法中使用,如果非要在靜態方法上使用泛型,可以在該方法上自己定義自己使用。

		**集合泛型其實一點都不特殊,本質上就是在集合類上定義的類上的泛型

	上邊界
		泛型是一種不確定的型別 可以在使用時為其指定為任意型別 這種靈活的特性 使我們的程式更加的靈活 但是過於靈活可能也會帶來問題 有時我們需要限定這種過於靈活的特點 此時可以使用上邊界 在泛型定義時 通過extends關鍵字 限定 泛型 必須是指定型別 或其子孫型別
		而如果不指定泛型的上邊界,泛型的上邊界預設為Object	
==============
	下邊界
		下邊界通過super關鍵字來指定 
		限定泛型的取值為指定型別或其祖先型別
		但是super不是用來在泛型定義時使用的,而是用在泛型萬用字元中

	泛型萬用字元
		泛型的萬用字元 使用在 類上泛型指定具體型別時 如果無法確定 泛型的具體取值 可以使用?來替代
		而泛型萬用字元 可以通過 extends關鍵字宣告?取值的上邊界
==============

	泛型的原理 - 泛型擦除:
		.java -- 編譯器 編譯 --> .class --> jvm 執行	
	泛型只在編譯階段起作用 在編譯的過程中 編譯期 根據泛型的宣告進行語法檢查 在檢查完成 真正將程式碼轉換為.class檔案的過程中 會將泛型相關的資訊 進行擦除 所謂 的擦除 具體過程是 將所有的泛型替代為 其 上邊界型別 並在必要的地方 加上必須的 型別強轉 和 型別檢查。所有 一個.java 編譯為 .class之後 所有泛型的資訊將會丟失 這個過程稱之為 泛型的擦除。

1.給當前方法定義一個獨有的泛型。
public void m(E e) {}
2.不確定返回值型別,用泛型表示。
public R m2() {
return null;
}
泛型不存在向上造型。
? extends Number 表示元素型別可以是Number及其子類——最大型別是Number,規定了泛型的上限。預設上邊界為Object。
? extends 類/介面 表示傳入這個類/介面的子類/子介面物件
? super String 表示元素型別可以是String及其父類——最小型別是String,規定了泛型的下限。
? super類/介面 表示傳入這個類/介面的父類/父介面物件
super 不是用在泛型定義的時候使用,而是用在泛型萬用字元。
可以新增下限對應的型別的物件,不能新增上限對應的型別的物件。
不允許同時規定上限和下限。
?表示萬用字元
List <?> list 用於限定型別,除了null,不能add任何元素。
動態代理
利用Proxiy類產生物件的代理物件。
是一種設計模式。
代理分靜態代理和動態代理。
改變已有方法的方式:
①繼承——在物件還未建立之前就繼承,直接建立的是子類才可以改變方法。如果已經有物件創建出來了,繼承並重寫對該物件無作用。
②裝飾——可以改變已有物件身上的方法,但是編寫的方式比較麻煩,特別是原來的類方法比較多的情況下,冗餘的程式碼會比較多。
③動態代理——java中為了實現代理 專門提供了一個 java.lang.reflect.Proxy類 幫我們生成代理者
通過呼叫Proxy的 static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 就可以為原有物件生成對應的代理物件了
之後不允許直接使用被代理物件,所有的操作都找代理物件,而呼叫代理物件的任何方法都會走到InvocationHandler中的invoke方法中 可以在invoke方法中編寫程式碼 處理代理的邏輯 對於不想改造的方法 呼叫原有物件身上的方法 對於想改造的方法 根據自己的意願改造。

靜態匯入
import static 包名.類名.方法名 表示匯入的靜態方法。
好處:靜態匯入可以在一定程度上提高載入速率。
缺點:但是寫起來麻煩而且降低程式的可讀性。如果本類中含有同名方法,會導致這個靜態匯入無效。不建議使用。
單例設計模式
單例模式:在全域性過程中只存在一個例項。
餓漢式的特點:無論是否需要這個物件,都會建立這個物件,這就會導致如果不需要這個物件時會使載入時間變長。在類載入時就已經建立好了。不存在併發安全問題。效率比較高,推薦此種方式。
懶漢式的特點:在物件需要的時候再建立,從而節省載入時間。但是會導致多執行緒的併發安全問題。需要用synchronized來同步。
單例模式共有7種實現:
第一種(懶漢,執行緒不安全):
Java程式碼  
1.public class Singleton {  
2.    private static Singleton instance;  
3.    private Singleton (){}  
4.  
5.    public static Singleton getInstance() {  
6.    if (instance == null) {  
7.        instance = new Singleton();  
8.    }  
9.    return instance;  
10.    }  
11.}  
  這種寫法lazy loading很明顯,但是致命的是在多執行緒不能正常工作。
第二種(懶漢,執行緒安全):
Java程式碼  
1.public class Singleton {  
2.    private static Singleton instance;  
3.    private Singleton (){}  
4.    public static synchronized Singleton getInstance() {  
5.    if (instance == null) {  
6.        instance = new Singleton();  
7.    }  
8.    return instance;  
9.    }  
10.}  
 
 這種寫法能夠在多執行緒中很好的工作,而且看起來它也具備很好的lazy loading,但是,遺憾的是,效率很低,99%情況下不需要同步。
第三種(餓漢):
Java程式碼  
1.public class Singleton {  
2.    private static Singleton instance = new Singleton();  
3.    private Singleton (){}  
4.    public static Singleton getInstance() {  
5.    return instance;  
6.    }  
7.}  
 
 這種方式基於classloder機制避免了多執行緒的同步問題,不過,instance在類裝載時就例項化,雖然導致類裝載的原因有很多種,在單例模式中大多數都是呼叫getInstance方法, 但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化instance顯然沒有達到lazy loading的效果。
第四種(餓漢,變種):
Java程式碼  
1.public class Singleton {  
2.    private Singleton instance = null;  
3.    static {  
4.    instance = new Singleton();  
5.    }  
6.    private Singleton (){}  
7.    public static Singleton getInstance() {  
8.    return this.instance;  
9.    }  
10.}  
 
 表面上看起來差別挺大,其實更第三種方式差不多,都是在類初始化即例項化instance。
第五種(靜態內部類):
Java程式碼  
1.public class Singleton {  
2.    private static class SingletonHolder {  
3.    private static final Singleton INSTANCE = new Singleton();  
4.    }  
5.    private Singleton (){}  
6.    public static final Singleton getInstance() {  
7.    return SingletonHolder.INSTANCE;  
8.    }  
9.}  
 
這種方式同樣利用了classloder的機制來保證初始化instance時只有一個執行緒,它跟第三種和第四種方式不同的是(很細微的差別):第三種和第四種方式是隻要Singleton類被裝載了,那麼instance就會被例項化(沒有達到lazy loading效果),而這種方式是Singleton類被裝載了,instance不一定被初始化。因為SingletonHolder類沒有被主動使用,只有顯示通過呼叫getInstance方法時,才會顯示裝載SingletonHolder類,從而例項化instance。想象一下,如果例項化instance很消耗資源,我想讓他延遲載入,另外一方面,我不希望在Singleton類載入時就例項化,因為我不能確保Singleton類還可能在其他的地方被主動使用從而被載入,那麼這個時候例項化instance顯然是不合適的。這個時候,這種方式相比第三和第四種方式就顯得很合理。
第六種(列舉):
Java程式碼  
1.public enum Singleton {  
2.    INSTANCE;  
3.    public void whateverMethod() {  
4.    }  
5.}  
 
 這種方式是Effective Java作者Josh Bloch 提倡的方式,它不僅能避免多執行緒同步問題,而且還能防止反序列化重新建立新的物件,可謂是很堅強的壁壘啊,不過,個人認為由於1.5中才加入enum特性,用這種方式寫不免讓人感覺生疏,在實際工作中,我也很少看見有人這麼寫過。
第七種(雙重校驗鎖):
Java程式碼  
1.public class Singleton {  
2.    private volatile static Singleton singleton;  
3.    private Singleton (){}  
4.    public static Singleton getSingleton() {  
5.    if (singleton == null) {  
6.        synchronized (Singleton.class) {  
7.        if (singleton == null) {  
8.            singleton = new Singleton();  
9.        }  
10.        }  
11.    }  
12.    return singleton;  
13.    }  
14.}