effectiveJava學習筆記:註解
我們在談註解前,順便先談一談Java反射
Java反射:
在執行狀態中,對於任意一個類,都能知道這個類所有的屬性和方法,對於任何一個物件,都能呼叫他的屬性和方法。並且能改變屬性。
反射機制允許程式在執行時取得任何一個已知的名稱的class內部資訊,包括修飾、屬性和方法。並且在執行時改變屬性或者是呼叫方法。那麼我們便可以更靈活的編寫程式碼,程式碼可以在執行時裝配,無需在元件之間進行原始碼連結,降低程式碼的耦合度;還有動態代理的實現等等。
class:
java 類在編譯後會產生一個以.class結尾的位元組碼檔案,該檔案記憶體儲了Class物件的相關資訊,Class物件表示的是類在執行時的型別資訊, Class與java.lang.reflect構成了java的反射技術 。
當我們要使用類時,例如使用new 操作符例項化一個新物件,訪問類的靜態方法,jvm會先檢查該類的有無載入,若有載入了就會直接進行相應的操作,若檢查到沒有載入,jvm就會先去載入這個類的對應的位元組碼檔案(這裡會進行相應的檢查)
當載入成功後,就可以進行相應的操作了。
public class Person implements Serializable { private String name; private int age; // get/set方法 } public static void main(String[] args) { Person person = new Person("ligz", 18); Class clazz = person.getClass(); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { String key = field.getName(); PropertyDescriptor descriptor = new PropertyDescriptor(key, clazz); Method method = descriptor.getReadMethod(); Object value = method.invoke(person); System.out.println(key + ":" + value); } }
最後輸出的結果是
name:ligz
age:18
以上通過getReadMethod()方法呼叫類的get函式,可以通過getWriteMethod()方法來呼叫類的set方法。
註解優先於命名模式
註解的語法比較簡單,除了@符號的使用之外,它基本與Java固有語法一致。Java SE5內建了三種標準註解:
@Override,表示當前的方法定義將覆蓋超類中的方法。
@Deprecated,使用了註解為它的元素編譯器將發出警告,因為註解@Deprecated是不贊成使用的程式碼,被棄用的程式碼。
@SuppressWarnings,關閉不當編譯器警告資訊。
上面這三個註解多少我們都會在寫程式碼的時候遇到。Java還提供了4中註解,專門負責新註解的建立。
@Target | 表示該註解可以用於什麼地方,可能的ElementType引數有: CONSTRUCTOR:構造器的宣告 FIELD:域宣告(包括enum例項) LOCAL_VARIABLE:區域性變數宣告 METHOD:方法宣告 PACKAGE:包宣告 PARAMETER:引數宣告 TYPE:類、介面(包括註解型別)或enum宣告 |
@Retention |
表示需要在什麼級別儲存該註解資訊。可選的RetentionPolicy引數包括: SOURCE:註解將被編譯器丟棄 CLASS:註解在class檔案中可用,但會被VM丟棄 RUNTIME:VM將在執行期間保留註解,因此可以通過反射機制讀取註解的資訊。 |
@Document |
將註解包含在Javadoc中 |
@Inherited |
允許子類繼承父類中的註解 |
使用註解的例子:
package com.ligz.Annotation.two;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author ligz
*/
@Target({ElementType.METHOD,ElementType.FIELD})
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTag {
String name() default "車";
int size() default 10;
}
package com.ligz.Annotation.two;
/**
* @author ligz
*/
public class Car {
private String name;
private int size;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public Car(){
}
public Car(String name, int size){
this.size = size;
this.name = name;
}
@Override
public String toString() {
return "Car [name=" + name + ", size=" + size + "]";
}
}
package com.ligz.Annotation.two;
/**
* 定義一個使用註解的類AnnotationDemo類
* @author ligz
*/
public class AnnotationDemo{
@MyTag(name = "audi", size = 10)
private Car car;
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Annotation [car=" + car + "]";
}
}
package com.ligz.Annotation.two;
import java.lang.reflect.Field;
/**
* 定義一個操作註解即讓註解起作用的類AnnotationProccessor類
* @author ligz
*/
public class AnnotationProccessor {
public static void annoProcess(AnnotationDemo annotation){
for(Field field : annotation.getClass().getDeclaredFields()){
if(field.isAnnotationPresent(MyTag.class)){ //如果存在MyTag標籤
MyTag myTag = field.getAnnotation(MyTag.class);
annotation.setCar(new Car(myTag.name(),myTag.size()));
}
}
}
public static void main(String[] args) {
AnnotationDemo ann = new AnnotationDemo();
annoProcess(ann);
System.out.println(ann.getCar());
}
}
執行結果為
Car [name=audi, size=10]
在effectiveJava中,作者使用junit作為例子來對比了命名模式和註解。
命名模式的缺點:
- 文字拼寫錯誤導致失敗,測試方法沒有執行,也沒有報錯
- 無法確保它們只用於相應的程式元素上,如希望一個類的所有方法被測試,把類命名為test開頭,但JUnit不支援類級的測試,只在test開頭的方法中生效
- 沒有提供將引數值與程式元素關聯起來的好方法。
總的說來,有註解可以不使用命名模式。
結合之前來看,註解通過反射獲取了類的方法名和屬性值。我們發現得到了類似xml的功能。
以前,XML是各大框架的青睞者,它以鬆耦合的方式完成了框架中幾乎所有的配置,但是隨著專案越來越龐大,XML的內容也越來越複雜,維護成本變高。
於是就有人提出來一種標記式高耦合的配置方式,註解。方法上可以進行註解,類上也可以註解,欄位屬性上也可以註解,反正幾乎需要配置的地方都可以進行註解。
關於註解和XML兩種不同的配置模式,爭論了好多年了,各有各的優劣,註解可以提供更大的便捷性,易於維護修改,但耦合度高,而 XML 相對於註解則是相反的。
追求低耦合就要拋棄高效率,追求效率必然會遇到耦合。
我們好是要根據情況來選擇。
堅持使用Override註解
這個沒啥好說的,如果你使用 @Override 告訴編譯器你想要覆蓋它 , 如果你出了錯誤,那麼你將會得到一條編譯錯誤的提示。
學會使用標記註解與標記介面
標識介面是沒有任何方法和屬性的介面,它僅僅表明它的類屬於一個特定的型別,供其他程式碼來測試允許做一些事情。
一些容器例如Ejb容器,servlet
容器或執行時環境依賴標記介面識別類是否需要進行某種處理,比如serialialbe介面標記類需要進行序列化操作。
標記註解是特殊型別的註解,其中不包含成員。標記註解的唯一目的就是標記宣告,因此,這種註解作為註解而存在的理由是充分的。確定標記註解是否存在的最好方式是使用isAnnotationPresent()方法,該方法是由AnnotatedElement介面定義的。
標記註解優點:
1.標記註解可以通過預設的方式新增一個或者多個註解型別元素 , 給已被實用的註解型別新增更多地資訊 .
2.標記註解是更大註解機制的一部分 , 這意味這它在那些支援註解作為程式設計元素之一的框架中同樣具有一致性
標記介面優點:
1.標機介面定義的型別是由被標記類的例項實現的 ; 標記註解則沒有定義這樣的型別 . 這個型別允許你在編譯時捕捉在使用標記註解的情況下要到執行時才能捕捉到的錯誤 .
2.標記介面可以更加精確地進行鎖定
總之 , 標記介面和標記註解各有用處 . 如果想要定義一個任何新方法都不會與之關聯的型別 , 標記介面就是最好的選擇 . 如果想要標記程式元素而非類和介面 , 考慮到未來可能要給標記新增更多地資訊 , 或者標記要適合於已經廣泛使用了註解型別的框架 , 那麼標記註解是正確的選擇