夯實(shí) Java 基礎(chǔ) - 注解

cbz2w-v6k6p.png

夯實(shí) Java 基礎(chǔ) - 注解

不知道大家有沒有一種感覺,當(dāng)你想要了解某個(gè)知識點(diǎn)的時(shí)候,就會(huì)發(fā)現(xiàn)好多技術(shù)類 APP 或者公眾號在推一些關(guān)于這個(gè)知識點(diǎn)的文章。也許這就是大數(shù)據(jù)的作用,這也說明總有人比你搶先一步。學(xué)習(xí)不能停滯,要不你就會(huì)被別人越落越遠(yuǎn)。

本文接著來回顧和總結(jié) Java 基礎(chǔ)中注解的知識點(diǎn)和簡單的使用,同樣本文將從以下幾個(gè)方面來回顧注解知識:

  1. 注解的定義
  2. 注解的語法
  3. 源碼級別的注解的使用
  4. 運(yùn)行時(shí)注解的使用
  5. 編譯時(shí)注解的使用
  6. Android 預(yù)置的注解

注解的定義

注解(Annotation),也叫元數(shù)據(jù)。一種代碼級別的說明。它是 JDK 1.5 以后版本引入的一個(gè)特性,與類、接口、枚舉是在同一個(gè)層次。它可以聲明在包、類、字段、方法、局部變量、方法參數(shù)等元素上。它提供數(shù)據(jù)用來解釋程序代碼,但是注解并非是所解釋的代碼本身的一部分。注解對于代碼的運(yùn)行效果沒有直接影響

注解有許多用處,主要如下:

  • 提供信息給編譯器: 編譯器可以利用注解來探測錯(cuò)誤和警告信息
  • 編譯階段時(shí)的處理: 軟件工具可以用來利用注解信息來生成代碼、Html 文檔或者做其它相應(yīng)處理。
  • 運(yùn)行時(shí)的處理: 某些注解可以在程序運(yùn)行的時(shí)候接受代碼的提取

如我們所熟知的依賴注入框架 ButterKnife 就是在編譯階段來生成 findViewById 的代碼(文件)的,而我們所見過的 @Deprecated 就是提供信息給編輯器的RetentionPolicy.SOURCE類型注解,說明這個(gè)屬性已經(jīng)過時(shí)的,對于運(yùn)行時(shí)的注解在反射的文章的最后我們也舉了個(gè)小例子,說明了它的作用。

在自定義了一個(gè)編譯或者運(yùn)行階段的注解后,需要一個(gè)開發(fā)者編寫相應(yīng)的代碼來解釋這些注解,從而來發(fā)揮注解的作用。這些用來解釋注解的代碼被統(tǒng)稱為是 APT(Annotation Processing Tool)。換句話說注解其實(shí)是給 APT 或者編輯器來使用的,而對于非框架開發(fā)人員的我們我們只需要關(guān)注注解的使用,并遵守規(guī)則即可,從而我們節(jié)省了很多代碼提高了效率。

但是凡事如果只滿足于用上,就不算是一個(gè)合 (tong) 格 (guo) 程 (mian)序 (shi) 員 (de)! 但是不要慌,當(dāng)你打開這篇文章的時(shí)候你已經(jīng)離 offer 又進(jìn)了一步。

注解的語法

注解的聲明

注解的聲明和聲明一個(gè)接口十分類似,沒錯(cuò)只是名字很類似~ 我們使用@interface 來聲明一個(gè)注解,如我們最常見的Override 注解的聲明

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

注解聲明的修飾符,可以是 private,public, protected 或者默認(rèn) default 這一點(diǎn)跟定義一個(gè)類或者接口相同。

在聲明一個(gè)注解的時(shí)候我們常常需要一些其他注解來修飾和限制該定義注解的使用和運(yùn)行方式。上述的 @Target@Retention 就是如此,我們稱之為元注解,詳細(xì)的元注解在下邊說明。

注解成員

注解跟一個(gè)類相似,它們并不是都是像上面的 @Override一樣只有聲明。一個(gè)類大概可以包含構(gòu)造函數(shù),成員變量,成員函數(shù)等,而一個(gè)注解只能包含注解成員,注解成員的聲明格式為:

類型 參數(shù)名() default 默認(rèn)值;

注解成員可以是:

  1. 基本類型 byte,short,int,long,float,double,boolean 八種基本類型及這些類型的數(shù)組, 注意這里沒對應(yīng)基本數(shù)據(jù)類型的包裝類。

  2. String,Enum,Class,annotations 及這些類型的數(shù)組

  3. 注解的成員修飾符只能是 public 或默認(rèn)(default)

  4. 注解元素必須有確定的值,可以在注解中定義默認(rèn)值,也可以使用注解時(shí)指定。即我們在定義注解的時(shí)候聲明的成員,可以不賦值,但是就跟抽象函數(shù)一樣,在使用的時(shí)候就必須指定。

如:

public @interface TestAnnotation {

   String value() default "";

   String[] values();

   int id() default -1;

   int[] ids();

   // 錯(cuò)誤的不能使用包裝類 以及自定義類型
   // Integer idInt();
   // Apple apple();

   enum Color {BULE, RED, GREEN}
   Color testEnum() default Color.BULE;
   Color[] testEnums();
   
   //注解類型成員 注解元素必須有確定的值,可以在注解中定義默認(rèn)值,也可以使用注解時(shí)指定
   FruitName fruitName() default @FruitName("apple");
}
    
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
protected @interface FruitName {
   String value();
   String alias() default "no alias";
}

元注解

我們在 Override 注解的聲明中可以看到還有注解修飾著如@Target(ElementType.METHOD),我們講元注解理解為修飾注解定義的注解。換句話說元注解為 JDK 提供給我們的一些基本注解,我們使用元注解來定義一個(gè)注解是如何工作的。

JDK 1.8 中存在的元注解有以下 5 種:

@Target, @Retention、@Documented、@Inherited、@Repeatable

下面我們依次來說明這幾種類型的注解是如何使用的。

@Target 元注解

@Target 指定了被修飾的注解運(yùn)用的地方,這些 "地方" 定義在 ElementType 類中,包括:

  1. ElementType.ANNOTATION_TYPE 可以給一個(gè)注解進(jìn)行注解
  2. ElementType.CONSTRUCTOR 可以給構(gòu)造方法進(jìn)行注解
  3. ElementType.FIELD 可以給屬性進(jìn)行注解
  4. ElementType.LOCAL_VARIABLE 可以給局部變量進(jìn)行注解
  5. ElementType.METHOD 可以給方法進(jìn)行注解
  6. ElementType.PACKAGE 可以給一個(gè)包進(jìn)行注解
  7. ElementType.PARAMETER 可以給一個(gè)方法內(nèi)的參數(shù)進(jìn)行注解
  8. ElementType.TYPE 可以給一個(gè)類型進(jìn)行注解,比如類、接口、枚舉

其中 METHOD、PARAMETER、FIELD 最為常見,如 Override 注解被 @Target(ElementType.METHOD) 修飾,如果我們想要標(biāo)記一個(gè)參數(shù)不能為空則可以使用 @NonNull 去修飾一個(gè) param, FIELD 用來指定注解只能用來修飾成員變量如我們經(jīng)常使用的 @BindView。

值得注意的是 @Target 元注解定義如下,

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

它內(nèi)部的成員為ElementType[] 數(shù)組也就是說,我們可以同時(shí)指定一個(gè)注解可以用于很多地方。如 @ColorRes 的注解的元注解為@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})。

@Retention 元注解

Retention 翻譯過來是保留期的意思。當(dāng)@Retention 用于修飾一個(gè)注解上的時(shí)候,它規(guī)定了了被修飾的注解應(yīng)用的時(shí)期,或者存活的時(shí)期

它可以有如下 3 種取值:

  • RetentionPolicy.SOURCE 注解只在源碼階段保留,在編譯器進(jìn)行編譯時(shí)它將被丟棄。
  • RetentionPolicy.RUNTIME 注解可以保留到程序運(yùn)行的時(shí)候,它會(huì)被加載進(jìn)入到 JVM 中,所以在程序運(yùn)行時(shí)通過反射獲取到它們,并解釋他們。
  • RetentionPolicy.CLASS 注解只被保留到編譯進(jìn)行的時(shí)候,它并不會(huì)被加載到 JVM 中。

源碼級別注解 RetentionPolicy.SOURCE

對于第一種 RetentionPolicy.SOURCE 注解只在源碼階段保留,更多的效果時(shí)做一些編譯檢查,在 Android 中有個(gè)為 @IntDef 的注解,他可以和常量組合一起 代替枚舉 enum 做參數(shù)限制作用,來優(yōu)化內(nèi)存使用。

這里說只是替代了參數(shù)限制作用,而 JDK 1.5 為我們帶來的 enum 的作用不只是簡單的參數(shù)限制作用作用,對于 Enum 更多優(yōu)雅使用可以參考 《Effective Java》。

@IntDef 的注解定義如下:

@Retention(SOURCE)
@Target({ANNOTATION_TYPE})
public @interface IntDef {
    long[] value() default {};
    boolean flag() default false;
}

如我們常用的設(shè)置一個(gè) View 的可見屬性就使用了 @IntDef 注解來保證使用者傳入的參數(shù)是對的,如下:

@IntDef({VISIBLE, INVISIBLE, GONE})
@Retention(RetentionPolicy.SOURCE)
public @interface Visibility {}

@RemotableViewMethod
public void setVisibility(@Visibility int visibility) {
        setFlags(visibility, VISIBILITY_MASK);
}

//設(shè)置一個(gè) View 的屬性:
...
toolbar.setVisibility(View.VISIBLE);// it is Ok

//toolbar.setVisibility(1000);// 如果我們隨便寫一個(gè)數(shù)值 那么編輯器將會(huì)報(bào)錯(cuò) 

運(yùn)行期時(shí)的注解 RetentionPolicy.RUNTIME

源碼級別的注解對我們的編碼約束,運(yùn)行期注解與之不同的是,如果要是讓該注解生效,我們必須要編寫一定的代碼去將定義好的注解,在運(yùn)行中"注入"應(yīng)用中,看到運(yùn)行時(shí)注入就可以應(yīng)該能想得起反射,是的注入這個(gè)操作就是需要開發(fā)人員自己編寫的。

另外,我們也都了解,在運(yùn)行反射的時(shí)候效率是無法保證的。因?yàn)榉瓷鋵⒈闅v對應(yīng)類的 Class 文件來獲取相應(yīng)的信息。所以運(yùn)行時(shí)注解,并不是那么廣泛被運(yùn)用,而稍后我們要說明的編譯期注解則不會(huì)對程序的運(yùn)行造成效率的影響,因此應(yīng)用更廣泛一些。

我們來試著寫一個(gè) Dota 英雄名稱的運(yùn)行期注解來了解下他的運(yùn)作方式:

/**
* 定義一個(gè)注解表示英雄的名字
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
private @interface HeroName {
   String value();

   String alias();
}

/**
* 定義一個(gè)類包含英雄名稱的屬性
*/
public class Hero {
    // 定義注解的時(shí)候沒有 deflaut 屬性名稱所以在使用的時(shí)候必須賦值
   @HeroName(value = "Spirit Walker", alias = "SB")
   private String heroName;

   public void setHeroName(String heroName) {
       this.heroName = heroName;
   }

   public String getHeroName() {
       return heroName;
   }
}

ok 聲明就是這么簡單,那么如何讓一個(gè)屬性生效呢,這時(shí)候我們就需要一個(gè)注解處理方法。為了方便觀察運(yùn)行注解的結(jié)果,所以我們這個(gè)處理方法選擇傳遞一個(gè) Hero 對象,不過你為了更通用也可以不用這么做。

/** 運(yùn)行時(shí)注解處理方法*/
public static void getHeroNameInfo(Hero hero) {
   try {
       Class<? extends Hero> clazz = hero.getClass();
       Field field = clazz.getDeclaredField("heroName");
       // Field isAnnotationPresent 判斷一個(gè)屬性是否被對應(yīng)的注解修飾
       if (field.isAnnotationPresent(HeroName.class)) {
           //field.getAnnotation 獲取屬性的注解
           HeroName fruitNameAnno = field.getAnnotation(HeroName.class);
           hero.setHeroName("name = " +fruitNameAnno.value() +" alias = " + fruitNameAnno.alias());
       }
   } catch (NoSuchFieldException e) {
       e.printStackTrace();
   }
}

下面我們來運(yùn)行下程序測試下:

public static void main(String[] args) {
   Hero hero = new Hero();
   getHeroNameInfo(hero);
   System.out.println("hero = " + hero);
}

運(yùn)行結(jié)果:

hero = Hero{heroName='name = Spirit Walker alias = SB'}

通過上述的例子,可以了解運(yùn)行時(shí)注解就是這樣聲明和運(yùn)用的。相信 SB 這個(gè)別名更容易讓大家記得這個(gè)例子(白牛這個(gè)英雄其實(shí)很好玩,只是別名...)。

編譯時(shí)期的注解 RetentionPolicy.CLASS

經(jīng)過運(yùn)行時(shí)注解的了解,相比對于注解應(yīng)該都有一個(gè)大概的了解了。接下來到了編譯時(shí)注解,這個(gè)注解類型,便是眾多工具庫中應(yīng)用的注解類型,它不會(huì)影響運(yùn)行時(shí)的效率問題,而是在編譯期,或者打包過程中就生成了對應(yīng)的代碼,在運(yùn)行時(shí)將會(huì)生效。如我們常見的 ButterKnifeEventBus。

編譯時(shí)注解與運(yùn)行時(shí)注解不同,編譯時(shí)注解主要是幫助我們在編譯器編譯期使用注解處理器生成相應(yīng)的代碼,幫我們解放勞動(dòng)力。

我們知道運(yùn)行時(shí)注解是通過反射來解釋對應(yīng)注解并使注解生效的,那么編譯時(shí)如何解釋對應(yīng)的注解呢?這里就需要用到注解處理器的知識了。

注解處理器(Annotation Processor)是javac的一個(gè)工具,它用來在編譯時(shí)掃描和處理注解(Annotation)。你可以自定義注解,并注冊相應(yīng)的注解處理器(自定義的注解處理器需繼承自AbstractProcessor)。

Java 中提供給我們了注解處理器實(shí)現(xiàn)方法,主要是通過實(shí)現(xiàn)一個(gè)名為 AbstractProcessor 的注解處理器基類。該抽象類要求我們必須實(shí)現(xiàn) process 方法來定義處理邏輯。下邊我們來看下注解處理器中的幾個(gè)方法的作用:

public class NameProcessor extends AbstractProcessor {
    
    //會(huì)被注解處理工具調(diào)用,并輸入ProcessingEnviroment參數(shù)。ProcessingEnviroment提供很多有用的工具類如Elements, Types和Filer等
    @Override
    public synchronized void init(ProcessingEnvironment env){ }
    
   //返回最高所支持的java版本, 如返回 SourceVersion.latestSupported();
    @Override
    public SourceVersion getSupportedSourceVersion() { }
    
    //一個(gè)注解處理器可能會(huì)處理多個(gè)注解邏輯,這個(gè)方法將返回待處理的注解類型集合,返回值作為參數(shù)傳遞給 process 方法。
    @Override
    public Set<String> getSupportedAnnotationTypes() { }

    //process 函數(shù)就是我們處理待處理注解的地方了,我們需要在這里編寫生成 java 文件的具體邏輯。 方法返回布爾值類型,表示注解是否已經(jīng)處理完成。一般情況下我們返回 true 即可。
    @Override
    public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
}

注解處理器的處理步驟主要有以下:

  1. 編譯器開始執(zhí)行注解處理器
  2. process 方法中循環(huán)處理注解元素(Element),找到被該注解所修飾的類,方法,或者屬性
  3. 拿上一步得到的備注注解修飾的類或者屬性,方法,生成對應(yīng)的輔助類,并寫入 java 文件
  4. 生成 java 文件后就可以在運(yùn)行時(shí),在程序中獲取并調(diào)用對應(yīng)的輔助方法,如 ButterKnife.bind(this); 方法就是獲取對應(yīng) Activity 的注解處理器生成的java 文件,并執(zhí)行了構(gòu)造函數(shù)。

自定義一個(gè)編譯時(shí)注解

自定義編譯時(shí)注解要比運(yùn)行時(shí)注解要繁瑣一些。下面我們來舉一個(gè)簡單的例子,意在說明編譯時(shí)注解是如何工作的。

在 Android 中為了實(shí)現(xiàn)一個(gè)編譯時(shí)注解我們一般需要借助兩個(gè)三方庫:

  1. com.google.auto.service:auto-service:1.0-rc2 這是谷歌官方提供的一個(gè)注解處理注冊插件可以幫助我們更方便的注冊注解處理器,只需要在自定義的 Processor 類上方添加@AutoService(Processor.class)即可,不用自己動(dòng)手執(zhí)行注解處理器的注冊工作(即編寫 resource/META-INF/services/javax.annotation.processing.Processor文件)。

  2. 為了更方便的在 process 文件中生成 Java 類,需要依賴一個(gè) Square 公司開源的 javapoet 庫,com.squareup:javapoet:1.9.0 這個(gè)庫中包裝提供了一些好用的 API 幫助我們更快更準(zhǔn)確的構(gòu)建 .java 文件。當(dāng)然你也可以自己手寫拼接字符串然后寫入文件(如果你能保證正確)。

仿照 ButterKnife 的實(shí)現(xiàn),我們建立一個(gè)新的 Android project ,然后創(chuàng)建兩個(gè) Java Moudle,其中 processor 用來存放注解處理器,processor-lib 用來存放對應(yīng)的注解,如下圖所示:

WX20180513-110801@2x.png

在注解處理器存在的lib的 build.gradle 中添加依賴關(guān)系:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    compile project(':processor-lib')
    compile 'com.squareup:javapoet:1.9.0'
    compile 'com.google.auto.service:auto-service:1.0-rc3'
}

主 moudle 中也需要添加對 processor 和 processor -lib 的依賴:

dependencies {
    ....
   implementation project(':processor-lib')
   // 注意這里的注解處理器的依賴方式
   annotationProcessor project(':processor')
}

好了經(jīng)過上述的準(zhǔn)備我們終于能夠編寫我們的編譯時(shí)注解了:

  1. 在 processor-lib 定義一個(gè) Name 注解如下:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface Name {
    String name();
    String alias();
}
  1. 編寫兩個(gè)類使用我們定義的注解:
public class SBHero {
    @Name(name = "Spirit Walker", alias = "SB")
    private String heroName;
}

public class PAHero {
    @Name(name = "Phantom Assassin", alias = "PA")
    private String heroName;
}
  1. 在 processor 注解處理lib 下定義一個(gè) NamePorcessor
// @AutoService(Processor.class) 幫助我們生成對應(yīng)的注解處理器配置
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.wangshijia.www.processor.Name")
public class NamePorcessor extends AbstractProcessor {
    //文件寫入工具類
    private Filer filer;
    //可以幫助我們在 gradle 控制臺打印信息的類
    private Messager messager;
    // 元素操作的輔助類
    private Elements elementUtils;
    //自定義文件名的后綴
    private static final String SUFFIX = "AutoGenerate";

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        filer = processingEnv.getFiler();
        messager = processingEnv.getMessager();
        elementUtils = processingEnv.getElementUtils();
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    /**
     * @return 你所需要處理的所有注解,該方法的返回值會(huì)被 process()方法所接收, 這里其實(shí)只有Name 注解,
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        HashSet<String> set = new HashSet<>();
        set.add(Name.class.getCanonicalName());
        return set;
    }
    ... 
}
  1. 最后我們要編輯我們的 process 方法了,process 方法中一共進(jìn)行了下面這幾件事:

    • 遍歷程序中所有被該注解修飾器處理注解修飾的元素 存放進(jìn)創(chuàng)建的Map集合
    • 依次取出map 中的元素構(gòu)建對應(yīng)的類和方法
    • 構(gòu)建對應(yīng)的方法內(nèi)容
    • 生成.java 文件 位置在 ~/app/build/generated/source/apt 目錄下
 @Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

   String packageName= "";
   // 獲得被該注解聲明的元素
   Set<? extends Element> elememts = roundEnv.getElementsAnnotatedWith(Name.class);
   // 聲明一個(gè)存放成員變量的列表
   List<VariableElement> fields;
   //key 對應(yīng)包含注解修飾元素的類的全類名 vaule 代表所有被注解修飾的變量
   Map<String, List<VariableElement>> maps = new HashMap<>();
   
   // 遍歷程序中所有被該注解修飾器處理注解修飾的元素
   for (Element ele : elememts) {
       //  ele.getKind() 獲取注解修飾的成員的類型,判斷該元素是否為成員變量
       if (ele.getKind() == ElementKind.FIELD) {
           VariableElement varELe = (VariableElement) ele;
           // 獲取該元素封裝類型
           TypeElement enclosingElement = (TypeElement) varELe.getEnclosingElement();
           // 拿到包含 enclosingElement 元素的類的名稱 樣式如 com.wangshijia.www.annotationapplication.Hero
           String key = enclosingElement.getQualifiedName().toString();
           messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + key);
           packageName = elementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
           fields = maps.get(key);
           if (fields == null) {
               maps.put(key, fields = new ArrayList<>());
           }
           fields.add(varELe);
       }
   }

   /*
    * maps 包含有所有被 @Name 修飾的類
    */
   for (String key : maps.keySet()) {
       List<VariableElement> elementFileds = maps.get(key);
       messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + key);
       messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + elementFileds);

       String className = key.substring(key.lastIndexOf(".") + 1);
       className += SUFFIX;
       // 創(chuàng)建 className 類
       TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className)
               .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
       // 創(chuàng)建方法
       MethodSpec.Builder methodBuild = MethodSpec.methodBuilder("printNameAnnotation")
               .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
               .returns(void.class);
       //創(chuàng)建方法中的打印語句
       for (VariableElement e : elementFileds) {
           Name annotation = e.getAnnotation(Name.class);
           // 創(chuàng)建 printNameAnnotation 方法
           methodBuild
                   .addStatement("$T.out.println($S)", System.class, e.getSimpleName() + " = " + annotation.name())
                   .addStatement("$T.out.println($S)", System.class, e.getSimpleName() + " = " + annotation.alias());

       }

       //將方法中添加到類中
       MethodSpec printNameMethodSpec = methodBuild.build();
       TypeSpec classTypeSpec = classBuilder.addMethod(printNameMethodSpec).build();

       try {
           //構(gòu)造的 java 文件 參數(shù)一 包名,參數(shù)二 上述構(gòu)建的類描述 TypeSpec
           JavaFile javaFile = JavaFile.builder(packageName, classTypeSpec)
                   .addFileComment(" This codes are generated automatically. Do not modify!")
                   .build();
           javaFile.writeTo(filer);
       } catch (IOException exception) {
           exception.printStackTrace();
       }
   }

上述注釋寫的很詳細(xì)了,這里希望不熟悉的朋友,自己動(dòng)手實(shí)現(xiàn)下,才能更好的理解是如何構(gòu)建對應(yīng)的文件的。生成的文件位于指定目錄下:

WX20180513-122856@2x.png
  1. 使用我們定義好的注解生成文件

    使用注解生成器生成的 java 文件和普通的類沒什么區(qū)別,通過編譯后就放在上述文件夾中,我們可以正常調(diào)用我們構(gòu)造類的方法,ButterKnife.bind(this) 實(shí)際上就是調(diào)用生成類的方法的過程。我們是一個(gè)簡單的 demo 就不這么復(fù)雜的調(diào)用了。直接在 App 目錄下的任意文件調(diào)用,如在一個(gè) Activity 中:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        PAHeroAutoGenerate.printNameAnnotation();
        SBHeroAutoGenerate.printNameAnnotation();
    }
}

對于 JavaPoet 生成 java 文件的過程如果想深入了解的話可以查看該博客:JavaPoet - 優(yōu)雅地生成代碼

Android 預(yù)置的注解

日常開發(fā)中,注解能夠幫助我們寫出更好更優(yōu)秀的代碼,為了更好地支持 Android 開發(fā),在已有的 android.annotation 基礎(chǔ)上,Google 開發(fā)了 android.support.annotation 擴(kuò)展包,共計(jì)50個(gè)注解,幫助開發(fā)者們寫出更優(yōu)秀的程序,這五十多種注解得以應(yīng)用場景各不相同,常見的如 @IntDef @ColorInt @Nullable。

對于這些注解的用途這里不再詳細(xì)說明,感興趣的可以去查看下一個(gè)朋友寫的關(guān)于 Android 中注解的作用的文章: Android 注解指南

總結(jié)

這篇文章寫的時(shí)候遇到很多的困難,因?yàn)楸救藢τ谧⒔庵傲私馇闆r和大多數(shù)人一樣,只停留在很少的使用階段,在文章的構(gòu)成方面也是一改再改。但是功夫不負(fù)有心人,在查閱了大量的資料后,學(xué)習(xí)到了很多注解的使用和原理的知識。也發(fā)現(xiàn)自己的知識掌握程度已經(jīng)落下不少,比如鴻洋大神寫的 Android 打造編譯時(shí)注解解析框架 這只是一個(gè)開始 這篇文章在15年的時(shí)候就有了,想想當(dāng)時(shí)剛畢業(yè),與大神的距離整整拉開了進(jìn)3年,讓我去哭一會(huì)。但是個(gè)人認(rèn)為這是件好事??偙纫恢蓖A粼谟蒙虾靡恍?,每次深一步了解,就感覺我跟大神之間的差距少了一些。

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容