【Java學習記錄】 三十六 註解

1.什麼是註解

1.1概念

說明程序的,給計算機看的。

什麼是註釋:用文字描述程序的,給程序員看的。

百度上的解釋:

Java 註解(Annotation)又稱 Java 標註,是 JDK5.0 引入的一種註釋機制,是一種代碼級別的說明。Java 語言中的類、方法、變量、參數和包等都可以被標註。和 Javadoc 不同,Java 標註可以通過反射獲取標註內容。在編譯器生成類文件時,標註可以被嵌入到字節碼中。Java 虛擬機可以保留標註內容,在運行時可以獲取到標註內容 。 當然它也支持自定義 Java 標註。

概念描述:

  • JDK1.5 之後的新特性
  • 用來說明程序的
  • 使用註解:@註解名稱

1.2 作用分類

  1. 編譯檢查:通過代碼裏標識的註解讓編譯器能夠實現基本的編譯檢查【Override】
  2. 編寫文檔:通過代碼裏標識的註解生成文檔【生成文檔doc文檔】,API文檔是通過抽取代碼中的文檔註釋生成的。

例如新建一個類:

 打開命令行窗口,用  javadoc AnnoDemo1.java  這個命令進行抽取,點開生成文件中的index.html:

3. 代碼分析:通過代碼裏標識的註解對代碼進行分析【使用反射】

2. JDK中預定義的一些註解

@Override : 檢測被該註解標註的方法是否蒐集繼承自父類(接口)的

@Deprecated :將該註解標註的內容,表示已過時

@SuppressWarnings :壓制警告,一般傳遞參數all @SuppressWarnings("all")

3. 自定義註解

3.1 格式

    元註解:   public  @interface 註解名稱{}

3.2 註解的本質

註解本質上就是一個接口,該接口默認繼承Annotation接口

將以下註解編譯過後進行反編譯,得到結果:

編譯前:

public interface MyAnno{

}

反編譯後:

public interface MyAnno extends java.lang.annotation.Annotation{

}

3.3 屬性

可以理解爲接口中可以定義的抽象方法。

要求: 

1.屬性的返回值類型只能爲以下幾種:

  • 基本數據類型
  • String
  • 枚舉
  • 註解
  • 以上類型的數組

2. 定義了的屬性(本質上是抽象方法),在使用時需要進行賦值

  • 如果定義屬性時,使用default關鍵字給屬性默認初始化值,則使用註解時,可以不進行屬性的賦值。
  • 如果只有一個屬性需要賦值,並且這個屬性的名稱是value,那麼value可以省略,直接賦值即可。
  • 數組賦值時,值使用大括號包裹。如果數組中只有一個值,那麼{}可以省略

3.4 元註解

概念:用於描述註解的註解。

@Target:描述能夠作用的位置

@Target(value = {ElementType.TYPE}) //表示該MyAnno註解只能作用於類上
public @interface MyAnno {
}

 其中value中ElementType取值可以有以下幾種情況:

  • TYPE:可以作用在類上
  • METHOD:可以作用在方法上
  • FIELD:可以作用於成員變量上
@Target(value = {ElementType.TYPE,ElementType.METHOD,ElementType.FIELD}) 
//表示該MyAnno註解可以同時作用於類上,方法上和成員變量上
public @interface MyAnno {
}

@Retention:描述註解被保留的階段

    @Retention(RetentionPolicy.RUNTIME):當前被描述的註解,會保留到字節碼文件中,並被JVM讀取到,一般自己定義的註解都加RUNTIME

@Documented:描述該註解是否會被抽取到api文檔中

@Inherited:描述註解是否被子類繼承

4. 在程序中使用註解

註解在程序中經常和反射一起使用,註解大多數來說都是用來替換配置文件的,拿之前反射的程序來舉例:

被測試的類AnnoTest1:

public class AnnoTest1 {
    public void play(){
        System.out.println("AnnoTest1 method play()");
    }
}

原始的反射代碼:

package cn.other.reflect;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;

/**
 * @author liwenlong
 * @data 2020/3/6
 */
public class ReflectTest {
    public static void main(String[] args) throws Exception {

        /**
         * 前提:不能改變該類的任何代碼。可以創建任意類的對象,可以執行任意方法
         * 即:拒絕硬編碼
         */

        //1.加載配置文件
        //1.1創建Properties對象
        Properties pro = new Properties();
        //1.2加載配置文件,轉換爲一個集合
        //1.2.1獲取class目錄下的配置文件  使用類加載器
        ClassLoader classLoader = ReflectTest.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("pro.properties");
        pro.load(is);

        //2.獲取配置文件中定義的數據
        String className = pro.getProperty("className");
        String methodName = pro.getProperty("methodName");

        //3.加載該類進內存
        Class cls = Class.forName(className);
        //4.創建對象
        Object obj = cls.newInstance();
        //5.獲取方法對象
        Method method = cls.getMethod(methodName);
        //6.執行方法
        method.invoke(obj);
    }

}

對應的配置文件pro:

className=cn.other.annotation.AnnoTest1
methodName=play

新建註解AnnoReflect :

​package cn.other.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/** 描述需要執行的類名和方法名
 * @author liwenlong
 * @data 2020/3/6
 */
@Target(ElementType.TYPE) //可以被作用在類上
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnoReflect {
    String className();
    String methodName();
}

 使用註解的方式來淘汰配置文件(註釋很重要):

package cn.other.annotation;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;

/**
 * @author liwenlong
 * @data 2020/3/6
 */

@AnnoReflect(className = "cn.other.annotation.AnnoTest1",methodName = "play")
public class ReflectAnnotationTest {
    public static void main(String[] args) throws Exception {

        /**
         * 前提:不能改變該類的任何代碼。可以創建任意類的對象,可以執行任意方法
         * 即:拒絕硬編碼
         */

        //1. 解析註解
        //1.1 獲取該類的字節碼文件對象
        Class<ReflectAnnotationTest> rac = ReflectAnnotationTest.class;
        //1.2 獲取上面的註解對象,其實就是在內存中生成了一個該註解接口的子類實現對象
        AnnoReflect an = rac.getAnnotation(AnnoReflect.class);
        /*
        相當於
        public class AnnotationReflect implements AnnoReflect{
            public String className(){
                return "cn.other.annotation.AnnoTest1";
            }
            public String methodName(){
                return "play";
            }
        }
        */
        //2. 調用註解對象中定義的抽象方法,獲取返回值
        String className = an.className();
        String methodName = an.methodName();

        //3.加載該類進內存
        Class cls = Class.forName(className);
        //4.創建對象
        Object obj = cls.newInstance();
        //5.獲取方法對象
        Method method = cls.getMethod(methodName);
        //6.執行方法
        method.invoke(obj);

    }
}

運行結果: 

 

 使用總結:

在程序中使用註解:獲取註解中定義的屬性值

  1. 獲取註解定義的位置的對象  (Class, Method, Field)
  2. 獲取指定的註解:getAnnotation(Class)
  3. 調用註解中的抽象方法獲取配置的屬性值

案例:簡單的測試框架

需求:設計一個框架,檢測一個類中的方法使用有異常,並進行統計。

待測試的類

package cn.other.annotation.demo;

/** 計算器類
 * @author liwenlong
 * @data 2020/3/6
 */
public class calculator {

    public void add(){
        System.out.println("1+0="+(1+0));
    }

    public void sub(){
        System.out.println("1-0="+(1-0));
    }

    public void mul(){
        System.out.println("1*0="+(1*0));
    }

    public void div(){
        System.out.println("1/0="+(1/0));
    }
    
    public void show(){
        System.out.println("今天天氣真不錯!");
    }
}

如何實現

首先自定義一個註解:

@Retention(RetentionPolicy.RUNTIME) //運行時
@Target(ElementType.METHOD) //加在方法前面
public @interface Check {
}

然後編寫一個類專門用於檢查(注意註釋):

package cn.other.annotation.demo;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/** 簡單的測試框架
 * 當主方法執行後,會自動自行檢測所有方法(加了check註解的方法),判斷方法是否有異常並記錄
 * @author liwenlong
 * @data 2020/3/6
 */
public class TestCheck {
    public static void main(String[] args) throws IOException {
        //1. 創建計算機對象
        calculator c = new calculator();
        //2. 獲取字節碼文件對象
        Class cls = c.getClass();
        //3. 獲取所有方法
        Method[] methods = cls.getMethods();

        int num = 0; //記錄出現異常的次數
        BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));

        for(Method method:methods){
            //4. 判斷方法上是否有Check註解
            if(method.isAnnotationPresent(Check.class)){
                //5. 有註解就執行,捕獲異常
                try {
                    method.invoke(c);
                } catch (Exception e) {
                    e.printStackTrace();
                    //6.將異常記錄在文件中
                    num++;
                    bw.write(method.getName()+"方法出異常了");
                    bw.newLine();
                    bw.write("異常的名稱是:"+e.getCause().getClass().getSimpleName());
                    bw.newLine();
                    bw.write("異常原因:"+e.getCause().getMessage());
                    bw.newLine();
                    bw.write("=====================");
                    bw.newLine();
                }
            }
        }
        bw.write("本次測試一共出現"+num+"次異常");
        bw.flush();
        bw.close();

    }

}

在待測試的類中每個需要測試的方法前面都加上@Check

package cn.other.annotation.demo;

/** 計算器類
 * @author liwenlong
 * @data 2020/3/6
 */
public class calculator {
    @Check
    public void add(){
        System.out.println("1+0="+(1+0));
    }
    @Check
    public void sub(){
        System.out.println("1-0="+(1-0));
    }
    @Check
    public void mul(){
        System.out.println("1*0="+(1*0));
    }
    @Check
    public void div(){
        System.out.println("1/0="+(1/0));
    }

    public void show(){
        System.out.println("今天天氣真不錯!");
    }
}

運行TestCheck類中的主方法,就會自動檢查所有註解@Check的方法是否異常:

 

小結 :

  • 大多數時候,我們會使用註解而不是自定義註解
  • 註解給編譯器和解析程序用
  • 註解不是程序的一部分,可以理解爲表情
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章