Java 註解 (Annotation)淺入深出

Java 註解 (Annotation)淺入深出

本文主要參考與借鑑frank909 文章,但更爲簡單,詳細。java

Annotation 中文譯過來就是註解、標釋的意思。Annotation是一種應用於類、方法、參數、變量、構造器及包聲明中的特殊修飾符。它是一種由JSR-175標準選擇用來描述元數據的一種工具。 在 Java 中註解是一個很重要的知識點,註解目前很是的流行,不少主流框架都支持註解,並且本身編寫代碼的時候也會盡可能的去用註解,一是方便,二是代碼更加簡潔。android

註解語法

由於日常開發少見,相信有很多的人員會認爲註解的地位不高。其實同 classs 和 interface 同樣,註解也屬於一種類型。它是在 Java SE 5.0 版本中開始引入的概念。git

package java.lang;

import java.lang.annotation.*;

/** * Indicates that a method declaration is intended to override a * method declaration in a supertype. If a method is annotated with * this annotation type compilers are required to generate an error * message unless at least one of the following conditions hold: * * <ul><li> * The method does override or implement a method declared in a * supertype. * </li><li> * The method has a signature that is override-equivalent to that of * any public method declared in {@linkplain Object}. * </li></ul> */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
複製代碼

它的形式跟接口很相似,不過前面多了一個 @ 符號。上面的代碼就建立了一個名字爲 Override的註解。github

你能夠簡單理解爲建立了一張名字爲 Override的標籤。api

**Annotations僅僅是元數據,和業務邏輯無關。**理解起來有點困難,但就是這樣。若是Annotations不包含業務邏輯,那麼必須有人來實現這些邏輯。元數據的用戶來作這個事情。Annotations僅僅提供它定義的屬性(類/方法/包/域)的信息。Annotations的用戶(一樣是一些代碼)來讀取這些信息並實現必要的邏輯。數組

元註解

元註解是什麼意思呢?安全

元註解是能夠註解到註解上的註解,或者說元註解是一種基本註解,可是它可以應用到其它的註解上面。網絡

若是難於理解的話,你能夠這樣理解。元註解也是一張標籤,可是它是一張特殊的標籤,它的做用和目的就是給其餘普通的標籤進行解釋說明的。app

元標籤有 @Retention、@Documented、@Target(當一個註解被 @Target 註解時,這個註解就被限定了運用的場景 )、@Inherited、@Repeatable 5 種。框架

@target 表示該註解能夠用於什麼地方,可能的ElementType參數有: CONSTRUCTOR:構造器的聲明 FIELD:域聲明(包括enum實例) LOCAL_VARIABLE:局部變量聲明 METHOD:方法聲明 PACKAGE:包聲明 PARAMETER:參數聲明 TYPE:類、接口(包括註解類型)或enum聲明
@Retention 表示須要在什麼級別保存該註解信息。可選的RetentionPolicy參數包括: SOURCE:註解將被編譯器丟棄; CLASS:註解在class文件中可用,但會被VM丟棄; RUNTIME:VM將在運行期間保留註解,所以能夠經過反射機制讀取註解的信息。
@Document 將註解包含在Javadoc中
@Inherited 容許子類繼承父類中的註解
@Repeatable 可重複 (@Repeatable 是 Java 1.8)

註解的屬性

註解的屬性也叫作成員變量。註解只有成員變量,沒有方法。註解的成員變量在註解的定義中以「無形參的方法」形式來聲明,其方法名定義了該成員變量的名字,其返回值定義了該成員變量的類型。須要注意的是,在註解中定義屬性時它的類型必須是 8 種基本數據類型外加 類、接口、註解及它們的數組。

註解中屬性能夠有默認值,默認值須要用 default 關鍵值指定。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Person {
    int id() default -10998;

    String msg() default "no hello";

}

複製代碼

上面代碼定義了 Person這個註解中擁有 id 和 msg 兩個屬性。在使用的時候,咱們應該給它們進行賦值。賦值的方式是在註解的括號內以 value=」」 形式,多個屬性以前用 ,隔開。 一個註解內僅僅只有一個名字爲 value 的屬性時,這個註解時能夠直接接屬性值填寫到括號內。 還須要注意的一種狀況是一個註解沒有任何屬性 括號能夠省略。。

public @interface NoUse {
}
複製代碼
public @interface Chou {
    String value() default "You";
}
複製代碼
@Person(id = 10758, msg = "hello android")//或者直接默認@Person()
public class Liming {
    @Chou("She")
    String beautiful;

    @NoUse
    public void say() {
    }
}
複製代碼

Java 預置的註解

Java 語言自己已經提供了幾個現成的註解。

@Deprecated

這個元素是用來標記過期的元素,想必你們在平常開發中常常碰到。編譯器在編譯階段遇到這個註解時會發出提醒警告,告訴開發者正在調用一個過期的元素好比過期的方法、過期的類、過期的成員變量。

@Person(id = 10758, msg = "hello android")//或者直接默認@Person()
public class Liming {
    @Chou("She")
    String beautiful;

    @NoUse
    public void say() {
        System.out.println(" say is using ");
    }

    @Deprecated
    public void speak() {
        System.out.println(" speak is out of date ");
    }
}
複製代碼

Liming類,它有兩個方法 say() 和 speak() ,其中 speak() 被 @Deprecated 註解。而後咱們在 IDE 中分別調用它們。

能夠看到,speak() 方法上面被一條直線劃了一條,這其實就是編譯器識別後的提醒效果。

@SuppressWarnings

阻止警告。調用被 @Deprecated 註解的方法後,編譯器會警告提醒,而有時候開發者會忽略這種警告,他們能夠在調用的地方經過 @SuppressWarnings 達到目的。

@SafeVarargs

參數安全類型註解。它的目的是提醒開發者不要用參數作一些不安全的操做,它的存在會阻止編譯器產生 unchecked 這樣的警告,在 Java 1.7 的版本中加入。

上面的代碼中,編譯階段不會報錯,運行時會拋出 ClassCastException 這個異常。

20181205141508.png

@FunctionalInterface

函數式接口註解,這個是 Java 1.8 版本引入的新特性。

函數式接口 (Functional Interface) 就是一個具備一個方法的普通接口。

@FunctionalInterface
public interface Runnable {
    /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */
    public abstract void run();
}
複製代碼

咱們進行線程開發中經常使用的 Runnable 就是一個典型的函數式接口,上面源碼能夠看到它就被 @FunctionalInterface 註解。

可能有人會疑惑,函數式接口標記,函數式接口能夠很容易轉換爲 Lambda 表達式。

註解與反射

  • 註解經過反射獲取。首先能夠經過 Class 對象的 isAnnotationPresent() 方法判斷它是否應用了某個註解

public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}

  • 而後經過 getAnnotation() 方法來獲取 Annotation 對象。

public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}

  • 或者是 getAnnotations() 方法。

public Annotation[] getAnnotations() {}

  • 前一種方法返回指定類型的註解,後一種方法返回註解到這個元素上的全部註解。

若是獲取到的 Annotation 若是不爲 null,則就能夠調用它們的屬性方法了。

屬性、方法上的註解照樣是能夠的。一樣仍是要藉助於反射。

public static void getAnnotation() {
    boolean hasAnnotation = MainActivity.class.isAnnotationPresent(Person.class);
    if (hasAnnotation) {
        Person testPerson = MainActivity.class.getAnnotation(Person.class);
        System.out.println("id is " + testPerson.id() + " msg is " + testPerson.msg());
    }
}
 public static void getField() {
        try {
            Field a = Liming.class.getDeclaredField("beautiful");
            a.setAccessible(true);
            Chou chou = a.getAnnotation(Chou.class);
            if (chou != null) {
                System.out.println("check value:" + chou.value());
            }
            Method noUse = Liming.class.getDeclaredMethod("say");
            if (noUse != null) { // 獲取方法中的註解
                Annotation[] ans = noUse.getAnnotations();
                for (int i = 0; i < ans.length; i++) {
                    System.out.println("method noUse annotation:" + ans[i].annotationType().getSimpleName());
                }
            }

        } catch (NoSuchFieldException e) {
            e.printStackTrace();
            System.out.println("NoSuchFieldException");
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            System.out.println("NoSuchMethodException");
        }

    }
複製代碼
say is using 
 speak is out of date 
id is -10998 msg is this is not default
check value:She
method noUse annotation:NoUse

Process finished with exit code 0
複製代碼

當開發者使用了Annotation 修飾了類、方法、Field 等成員以後,這些 Annotation 不會本身生效,必須由開發者提供相應的代碼來提取並處理 Annotation 信息。這些處理提取和處理 Annotation 的代碼統稱爲 APT(Annotation Processing Tool)。

註解是一系列元數據,它提供數據用來解釋程序代碼,可是註解並不是是所解釋的代碼自己的一部分。註解對於代碼的運行效果沒有直接影響。

註解有許多用處,主要以下:

- 提供信息給編譯器: 編譯器能夠利用註解來探測錯誤和警告信息
- 編譯階段時的處理: 軟件工具能夠用來利用註解信息來生成代碼、Html文檔或者作其它相應處理。
- 運行時的處理: 某些註解能夠在程序運行的時候接受代碼的提取 
複製代碼

親手自定義註解完成某個目的

需求:自定義註解與實現,檢查MaSaGei類中的錯誤並反饋

@Retention(RetentionPolicy.RUNTIME)
public @interface CheckOut {
}
複製代碼
public class MaSaGei {
    @CheckOut
    public void testOne() {
        System.out.println(" 1 + 0 = " + ((1 + 1) / 2));
    }

    @CheckOut
    public void testTwo() {
        System.out.println(" 1 + 1 = " + (8 / 4));
    }

    @CheckOut
    public void testThree() {
        System.out.println(" 1 + 2 = " + (6 / 2));
    }

    @CheckOut
    public void testFour() {
        System.out.println(" 1 / 3 = " + (6 / 0));
    }
}
複製代碼
public class CheckOutTool {
    public static void checkAll() {
        MaSaGei maSaGei = new MaSaGei();
        Class clazz = maSaGei.getClass();
        Method[] method = clazz.getDeclaredMethods();
        StringBuilder log = new StringBuilder();
        // 記錄異常的次數
        int errornum = 0;
        for (Method m : method) {
            if (m.isAnnotationPresent(CheckOut.class)) {
                m.setAccessible(true);
                try {
                    m.invoke(maSaGei, null);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                    errornum++;
                    log.append(m.getName());
                    log.append(" ");
                    log.append("has error ");
                    log.append("\\n\\r caused by ");
                    log.append(e.getCause().getClass().getSimpleName());
                    log.append("\n\r");
                    log.append(e.getCause().getMessage());
                    log.append("\n\r");
                }
            }
        }
        log.append(clazz.getSimpleName());
        log.append(" has ");
        log.append(errornum);
        log.append(" error."); // 生成測試報告
        System.out.println(log.toString());
    }
}
複製代碼

結果以下:

testFour  has error \n\r  caused by ArithmeticException
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.heng.subhey.annotation.CheckOutTool.checkAll(CheckOutTool.java:18)
	at com.heng.subhey.MainActivity.main(MainActivity.java:22)
Caused by: java.lang.ArithmeticException: / by zero
/ by zero
	at com.heng.subhey.annotation.MaSaGei.testFour(MaSaGei.java:21)
	... 6 more
MaSaGei has  1 error.

Process finished with exit code 0
複製代碼

註解應用實例

JUnit

/** * Example local unit test, which will execute on the development machine (host). * * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> */
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() throws Exception {
       assertEquals(4, 2 + 2);

    }
}
複製代碼

@Test 標記了要進行測試的方法 addition_isCorrect()。

ButterKnife

ButterKnife 是 Android 開發中大名鼎鼎的 IOC 框架,它減小了大量重複的代碼。

public class GoPayActivity extends BaseActivity {
    @BindView(R.id.title_tv)
    TextView title_tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        title_tv.setText("支付");
    }

    @Override
    protected int getLayoutId() {
        return R.layout.activity_go_pay;
    }

    @OnClick({R.id.title_back, R.id.pay_confirm})
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.title_back:
                finish();
                break;
            case R.id.pay_confirm:
                ToastUtils.showLong("支付環境安全檢測中...");
                title_tv.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        ToastUtils.showShort("即將跳轉至支付頁面");
                        Intent charge = new Intent(GoPayActivity.this, KeyWriteActivity.class);
                        startActivity(charge);
                    }
                }, 2 * 1000);
                break;
        }
    }
}
複製代碼

Retrofit

Http 網絡訪問框架

public interface GitHubService { @GET("users/{user}/repos") 
Call<List<Repo>> listRepos(@Path("user") String user); } 
Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com/") .build(); GitHubService service = retrofit.create(GitHubService.class);
複製代碼

總結

  • 註解難於理解,註解爲了解釋代碼。
  • 註解的基本語法,多了個 @ 符號。
  • 註解的元註解。
  • 註解的屬性。
  • 註解主要給編譯器及工具類型的軟件用的。
  • 註解的提取須要藉助於 Java 的反射技術,反射比較慢,因此註解使用時也須要謹慎計較時間成本。
相關文章
相關標籤/搜索