日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java进阶之注解篇

發布時間:2023/12/4 编程问答 56 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java进阶之注解篇 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 注解
  • 基本語法
    • 定義注解
    • 元注解
  • 編寫注解處理器
    • 注解元素
    • 默認值限制
    • 生成外部文件
    • 替代方案
    • 注解不支持繼承
    • 實現處理器

注解

注解(也被稱為元數據)為我們在代碼中添加信息提供了一種形式化的方式,使我們可以在稍后的某個時刻更容易的使用這些數據。
注解在一定程度上是把元數據和源代碼文件結合在一起的趨勢所激發的,而不是保存在外部文檔。這同樣是對像C# 語言對于Java 語言特性壓力的一種回應。
注解是Java 5 所引入的眾多語言變化之一。它們提供了Java 無法表達的但是你需要完整表述程序所需的信息。因此,注解使得我們可以以編譯器驗證的格式存儲程序的額外信息。注解可以生成描述符文件,甚至是新的類定義,并且有助于減輕編寫“樣板”代碼的負擔。通過使用注解,你可以將元數據保存在Java 源代碼中。并擁有如下優勢:
簡單易讀的代碼,編譯器類型檢查,使用annotation API 為自己的注解構造處理工具。
即使Java 定義了一些類型的元數據,但是一般來說注解類型的添加和如何使用完全取決于你。
注解的語法十分簡單,主要是在現有語法中添加@ 符號。Java 5 引入了前三種定義在java.lang 包中的注解:
? @Override:表示當前的方法定義將覆蓋基類的方法。如果你不小心拼寫錯誤,或者方法簽名被錯誤拼寫的時候,編譯器就會發出錯誤提示。
? @Deprecated:如果使用該注解的元素被調用,編譯器就會發出警告信息。
? @SuppressWarnings:關閉不當的編譯器警告信息。
? @SafeVarargs:在Java 7 中加入用于禁止對具有泛型varargs 參數的方法或構造函數的調用方發出警告。
? @FunctionalInterface:Java 8 中加入用于表示類型聲明為函數式接口。
還有5 種額外的注解類型用于創造新的注解。你將會在這一章學習它們。
每當創建涉及重復工作的類或接口時,你通常可以使用注解來自動化和簡化流程。
例如在Enterprise JavaBean(EJB)中的許多額外工作就是通過注解來消除的。
注解的出現可以替代一些現有的系統,例如XDoclet,它是一種獨立的文檔化工具,專門設計用來生成注解風格的文檔。與之相比,注解是真正語言層級的概念,以前構造出來就享有編譯器的類型檢查保護。注解在源代碼級別保存所有信息而不是通過注釋文字,這使得代碼更加整潔和便于維護。通過使用拓展的annotation API 或稍后在本章節可以看到的外部的字節碼工具類庫,你會擁有對源代碼及字節碼強大的檢查與操作能力。

基本語法

在下面的例子中,使用@Test 對testExecute() 進行注解。該注解本身不做任何事情,但是編譯器要保證其類路徑上有@Test 注解的定義。你將在本章看到,我們通過注解創建了一個工具用于運行這個方法:

// annotations/Testable.java package annotations; import onjava.atunit.*; public class Testable {public void execute() {System.out.println("Executing..");}@Testvoid testExecute() { execute(); } }

被注解標注的方法和其他方法沒有任何區別。在這個例子中,注解@Test 可以和任何修飾符共同用于方法,諸如public、static 或void。從語法的角度上看,注解和修飾符的使用方式是一致的。

定義注解

如下是一個注解的定義。注解的定義看起來很像接口的定義。事實上,它們和其他Java 接口一樣,也會被編譯成class 文件。

// onjava/atunit/Test.java // The @Test tag package onjava.atunit; import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Test {}

除了@ 符號之外,@Test 的定義看起來更像一個空接口。注解的定義也需要一些元注解(meta-annotation),比如@Target 和@Retention。@Target 定義你的注解可以應用在哪里(例如是方法還是字段)。@Retention 定義了注解在哪里可用,在源代碼中
(SOURCE),class 文件(CLASS)中或者是在運行時(RUNTIME)。
注解通常會包含一些表示特定值的元素。當分析處理注解的時候,程序或工具可以利用這些值。注解的元素看起來就像接口的方法,但是可以為其指定默認值。
不包含任何元素的注解稱為標記注解(marker annotation),例如上例中的@Test就是標記注解。
下面是一個簡單的注解,我們可以用它來追蹤項目中的用例。程序員可以使用該注解來標注滿足特定用例的一個方法或者一組方法。于是,項目經理可以通過統計已經實現的用例來掌控項目的進展,而開發者在維護項目時可以輕松的找到用例用于更新,或者他們可以調試系統中業務邏輯。

// annotations/UseCase.java import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface UseCase {int id();String description() default "no description"; }

注意id 和description 與方法定義類似。由于編譯器會對id 進行類型檢查,因此將跟蹤數據庫與用例文檔和源代碼相關聯是可靠的方式。description 元素擁有一個default 值,如果在注解某個方法時沒有給出description 的值。則該注解的處理器會使用此元素的默認值。
在下面的類中,有三個方法被注解為用例:

// annotations/PasswordUtils.java import java.util.*; public class PasswordUtils {@UseCase(id = 47, description ="Passwords must contain at least one numeric")public boolean validatePassword(String passwd) {return (passwd.matches("\\w*\\d\\w*"));}@UseCase(id = 48)public String encryptPassword(String passwd) {return new StringBuilder(passwd).reverse().toString();}@UseCase(id = 49, description ="New passwords can't equal previously used ones")public boolean checkForNewPassword(List<String> prevPasswords, String passwd) {return !prevPasswords.contains(passwd);} }

注解的元素在使用時表現為名-值對的形式,并且需要放置在@UseCase 聲明之后的括號內。在encryptPassword() 方法的注解中,并沒有給出description 的值,所以在@interface UseCase 的注解處理器分析處理這個類的時候會使用該元素的默認值。你應該能夠想象到如何使用這套工具來“勾勒” 出將要建造的系統,然后在建造的過程中逐漸實現系統的各項功能。

元注解

Java 語言中目前有5 種標準注解(前面介紹過),以及5 種元注解。元注解用于注解其他的注解

注解解釋
@Target表示注解可以用于哪些地方。可能的ElementType 參數包括:
CONSTRUCTOR:構造器的聲明
FIELD:字段聲明(包括enum實例)
LOCAL_VARIABLE:局部變量聲明
METHOD:方法聲明
PACKAGE:包聲明
PARAMETER:參數聲明
TYPE:類、接口(包括注解類型)或者enum 聲明
@Retention表示注解信息保存的時長。可選的RetentionPolicy 參數包括:
SOURCE:注解將被編譯器丟棄
CLASS:注解在class 文件中可用,但是會被VM 丟棄。
RUNTIME:VM 將在運行期也保留注解,因此可以通過反射機制讀取注解的信息。
@Documented將此注解保存在Javadoc 中
@Inherited允許子類繼承父類的注解
@Repeatable允許一個注解可以被使用一次或者多次(Java 8)。

大多數時候,程序員定義自己的注解,并編寫自己的處理器來處理他們。

編寫注解處理器

如果沒有用于讀取注解的工具,那么注解不會比注釋更有用。使用注解中一個很重要的部分就是,創建與使用注解處理器。Java 拓展了反射機制的API 用于幫助你創造這類工具。同時他還提供了javac 編譯器鉤子在編譯時使用注解。
下面是一個非常簡單的注解處理器,我們用它來讀取被注解的PasswordUtils 類,并且使用反射機制來尋找@UseCase 標記。給定一組id 值,然后列出在PasswordUtils中找到的用例,以及缺失的用例。

// annotations/UseCaseTracker.java import java.util.*; import java.util.stream.*; import java.lang.reflect.*; public class UseCaseTracker {public static void trackUseCases(List<Integer> useCases, Class<?> cl) {for(Method m : cl.getDeclaredMethods()) {UseCase uc = m.getAnnotation(UseCase.class);if(uc != null) {System.out.println("Found Use Case " +uc.id() + "\n " + uc.description());useCases.remove(Integer.valueOf(uc.id()));}}useCases.forEach(i ->System.out.println("Missing use case " + i));}public static void main(String[] args) {List<Integer> useCases = IntStream.range(47, 51).boxed().collect(Collectors.toList());trackUseCases(useCases, PasswordUtils.class);} }

輸出為:
Found Use Case 48
no description
Found Use Case 47
Passwords must contain at least one numeric
Found Use Case 49
New passwords can’t equal previously used ones
Missing use case 50

這個程序用了兩個反射的方法:getDeclaredMethods() 和getAnnotation(),它們都屬于AnnotatedElement 接口(Class,Method 與Field 類都實現了該接口)。
getAnnotation() 方法返回指定類型的注解對象,在本例中就是“UseCase”。如果被注解的方法上沒有該類型的注解,返回值就為null。我們通過調用id() 和description()方法來提取元素值。注意encryptPassword() 方法在注解的時候沒有指定description的值,因此處理器在處理它對應的注解時,通過description() 取得的是默認值“no description”。

注解元素

在UseCase.java 中定義的@UseCase 的標簽包含int 元素id 和String 元素description。注解元素可用的類型如下所示:
? 所有基本類型(int、float、boolean 等)
? String
? Class
? enum
? Annotation
? 以上類型的數組
如果你使用了其他類型,編譯器就會報錯。注意,也不允許使用任何包裝類型,但是由于自動裝箱的存在,這不算是什么限制。注解也可以作為元素的類型。稍后你會看到,注解嵌套是一個非常有用的技巧。

默認值限制

編譯器對于元素的默認值有些過于挑剔。首先,元素不能有不確定的值。也就是說,元素要么有默認值,要么就在使用注解時提供元素的值。
這里有另外一個限制:任何非基本類型的元素,無論是在源代碼聲明時還是在注解接口中定義默認值時,都不能使用null 作為其值。這個限制使得處理器很難表現一個元素的存在或者缺失的狀態,因為在每個注解的聲明中,所有的元素都存在,并且具有相應的值。為了繞開這個約束,可以自定義一些特殊的值,比如空字符串或者負數用于表達某個元素不存在。

// annotations/SimulatingNull.java import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface SimulatingNull {int id() default -1;String description() default ""; }

這是一個在定義注解的習慣用法。

生成外部文件

當有些框架需要一些額外的信息才能與你的源代碼協同工作,這種情況下注解就會變得十分有用。像Enterprise JavaBeans (EJB3 之前)這樣的技術,每一個Bean 都需要大量的接口和部署描述文件,而這些就是“樣板” 文件。Web Service,自定義標簽庫以及對象/關系映射工具(例如Toplink 和Hibernate)通常都需要XML 描述文件,而這些文件脫離于代碼之外。除了定義Java 類,程序員還必須忍受沉悶,重復的提供某些信息,例如類名和包名等已經在原始類中提供過的信息。每當你使用外部描述文件時,他就擁有了一個類的兩個獨立信息源,這經常導致代碼的同步問題。同時這也要求了為項目工作的程序員在知道如何編寫Java 程序的同時,也必須知道如何編輯描述文件。假設你想提供一些基本的對象/關系映射功能,能夠自動生成數據庫表。你可以使用XML 描述文件來指明類的名字、每個成員以及數據庫映射的相關信息。但是,通過使用注解,你可以把所有信息都保存在JavaBean 源文件中。為此你需要一些用于定義數據庫表名稱、數據庫列以及將SQL 類型映射到屬性的注解。
以下是一個注解的定義,它告訴注解處理器應該創建一個數據庫表:

// annotations/database/DBTable.java package annotations.database; import java.lang.annotation.*; @Target(ElementType.TYPE) // Applies to classes only @Retention(RetentionPolicy.RUNTIME) public @interface DBTable {String name() default ""; }

在@Target 注解中指定的每一個ElementType 就是一個約束,它告訴編譯器,這個自定義的注解只能用于指定的類型。你可以指定enum ElementType 中的一個值,或者以逗號分割的形式指定多個值。如果想要將注解應用于所有的ElementType,那么可以省去@Target 注解,但是這并不常見。
注意@DBTable 中有一個name() 元素,該注解通過這個元素為處理器創建數據庫時提供表的名字。
如下是修飾字段的注解:

// annotations/database/Constraints.java package annotations.database; import java.lang.annotation.*; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Constraints {boolean primaryKey() default false;boolean allowNull() default true;boolean unique() default false; } // annotations/database/SQLString.java package annotations.database; import java.lang.annotation.*; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface SQLString {int value() default 0;String name() default "";Constraints constraints() default @Constraints; } // annotations/database/SQLInteger.java package annotations.database; import java.lang.annotation.*; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface SQLInteger {String name() default "";Constraints constraints() default @Constraints; }

@Constraints 注解允許處理器提供數據庫表的元數據。@Constraints 代表了數據庫通常提供的約束的一小部分,但是它所要表達的思想已經很清楚了。primaryKey(),allowNull() 和unique() 元素明顯的提供了默認值,從而使得在大多數情況下,該注解的使用者不需要輸入太多東西。
另外兩個@interface 定義的是SQL 類型。如果希望這個框架更有價值的話,我們應該為每個SQL 類型都定義相應的注解。不過作為示例,兩個元素足夠了。
這些SQL 類型具有name() 元素和constraints() 元素。后者利用了嵌套注解
的功能,將數據庫列的類型約束信息嵌入其中。注意constraints() 元素的默認值是@Constraints。由于在@Constraints 注解類型之后,沒有在括號中指明@Constraints元素的值,因此,constraints() 的默認值為所有元素都為默認值的@Constraints注解。如果要使得嵌入的@Constraints 注解中的unique() 元素為true,并作為constraints() 元素的默認值,你可以像如下定義:

// annotations/database/Uniqueness.java // Sample of nested annotations package annotations.database; public @interface Uniqueness {Constraints constraints()default @Constraints(unique = true); }

下面是一個簡單的,使用了如上注解的類:

// annotations/database/Member.java package annotations.database; @DBTable(name = "MEMBER") public class Member {@SQLString(30) String firstName;@SQLString(50) String lastName;@SQLInteger Integer age;@SQLString(value = 30,constraints = @Constraints(primaryKey = true))String reference;static int memberCount;public String getReference() { return reference; }public String getFirstName() { return firstName; }public String getLastName() { return lastName; }@Overridepublic String toString() { return reference; }public Integer getAge() { return age; } }

類注解@DBTable 注解給定了元素值MEMBER,它將會作為表的名字。類的屬性firstName 和lastName 都被注解為@SQLString 類型并且給了默認元素值分別為30 和50。這些注解都有兩個有趣的地方:首先,他們都使用了嵌入的@Constraints注解的默認值;其次,它們都是用了快捷方式特性。如果你在注解中定義了名為value的元素,并且在使用該注解時,value 為唯一一個需要賦值的元素,你就不需要使用名—值對的語法,你只需要在括號中給出value 元素的值即可。這可以應用于任何合法類型的元素。這也限制了你必須將元素命名為value,不過在上面的例子中,這樣的注解語句也更易于理解:
@SQLString(30)
處理器將在創建表的時候使用該值設置SQL 列的大小。
默認值的語法雖然很靈巧,但是它很快就變的復雜起來。以reference 字段的注解為例,上面擁有@SQLString 注解,但是這個字段也將成為表的主鍵,因此在嵌入的@Constraint 注解中設定primaryKey 元素的值。這時事情就變的復雜了。你不得不為這個嵌入的注解使用很長的鍵—值對的形式,來指定元素名稱和@interface 的名稱。同時,由于有特殊命名的value 也不是唯一需要賦值的元素,因此不能再使用快捷方式特性。如你所見,最終結果不算清晰易懂。

替代方案

可以使用多種不同的方式來定義自己的注解用于上述任務。例如,你可以使用一個單一的注解類@TableColumn,它擁有一個enum 元素,元素值定義了STRING,INTEGER,FLOAT 等類型。這消除了每個SQL 類型都需要定義一個@interface的負擔,不過也使得用額外信息修飾SQL 類型變的不可能,這些額外的信息例如長度或精度等,都可能是非常有用的。
你也可以使用一個String 類型的元素來描述實際的SQL 類型,比如“VARCHAR(30)” 或者“INTEGER”。這使得你可以修飾SQL 類型,但是這也將Java 類型到SQL 類型的映射綁在了一起,這不是一個好的設計。你并不想在數據庫更改之后重新編譯你的代碼;如果我們只需要告訴注解處理器,我們正在使用的是什么“口味(favor)” 的SQL,然后注解處理器來為我們處理SQL 類型的細節,那將是一個優雅的設計。
第三種可行的方案是一起使用兩個注解,@Constraints 和相應的SQL 類型(例如,@SQLInteger)去注解同一個字段。這可能會讓代碼有些混亂,但是編譯器允許你對同一個目標使用多個注解。在Java 8,在使用多個注解的時候,你可以重復使用同一個注解。

注解不支持繼承

你不能使用extends 關鍵字來繼承@interfaces。這真是一個遺憾,如果可以定義@TableColumn 注解(參考前面的建議),同時嵌套一個@SQLType 類型的注解,將成為一個優雅的設計。按照這種方式,你可以通過繼承@SQLType 來創造各種SQL類型。例如@SQLInteger 和@SQLString。如果支持繼承,就會大大減少打字的工作量并且使得語法更整潔。在Java 的未來版本中,似乎沒有任何關于讓注解支持繼承
的提案,所以在當前情況下,上例中的解決方案可能已經是最佳方案了。

實現處理器

下面是一個注解處理器的例子,他將讀取一個類文件,檢查上面的數據庫注解,并生成用于創建數據庫的SQL 命令:

// annotations/database/TableCreator.java // Reflection-based annotation processor // {java annotations.database.TableCreator // annotations.database.Member} package annotations.database; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; public class TableCreator { public static void main(String[] args) throws Exception { if (args.length < 1) {System.out.println( "arguments: annotated classes"); System.exit(0); } for (String className : args) { Class<?> cl = Class.forName(className); DBTable dbTable = cl.getAnnotation(DBTable.class); if (dbTable == null) { System.out.println( "No DBTable annotations in class " + className); continue; } String tableName = dbTable.name(); // If the name is empty, use the Class name: if (tableName.length() < 1) tableName = cl.getName().toUpperCase(); List<String> columnDefs = new ArrayList<>(); for (Field field : cl.getDeclaredFields()) { String columnName = null; Annotation[] anns = field.getDeclaredAnnotations(); if (anns.length < 1) continue; // Not a db table column if (anns[0] instanceof SQLInteger) { SQLInteger sInt = (SQLInteger) anns[0]; // Use field name if name not specified if (sInt.name().length() < 1) columnName = field.getName().toUpperCase(); else columnName = sInt.name(); columnDefs.add(columnName + " INT" + getConstraints(sInt.constraints())); } if (anns[0] instanceof SQLString) {SQLString sString = (SQLString) anns[0]; // Use field name if name not specified. if (sString.name().length() < 1) columnName = field.getName().toUpperCase(); else columnName = sString.name(); columnDefs.add(columnName + " VARCHAR(" + sString.value() + ")" + getConstraints(sString.constraints())); } StringBuilder createCommand = new StringBuilder( "CREATE TABLE " + tableName + "("); for (String columnDef : columnDefs) createCommand.append( "\n " + columnDef + ","); // Remove trailing comma String tableCreate = createCommand.substring( 0, createCommand.length() - 1) + ");"; System.out.println("Table Creation SQL for " + className + " is:\n" + tableCreate); } } } private static String getConstraints(Constraints con) { String constraints = ""; if (!con.allowNull()) constraints += " NOT NULL"; if (con.primaryKey()) constraints += " PRIMARY KEY"; if (con.unique()) constraints += " UNIQUE"; return constraints; } }

輸出為:
Table Creation SQL for annotations.database.Member is:
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30));
Table Creation SQL for annotations.database.Member is:
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30),
LASTNAME VARCHAR(50));
Table Creation SQL for annotations.database.Member is:
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30),
LASTNAME VARCHAR(50),
AGE INT);
Table Creation SQL for annotations.database.Member is:
CREATE TABLE MEMBER(
FIRSTNAME VARCHAR(30),
LASTNAME VARCHAR(50),
AGE INT,
REFERENCE VARCHAR(30) PRIMARY KEY);

主方法會循環處理命令行傳入的每一個類名。每一個類都是用forName()
方法進行加載, 并使用getAnnotation(DBTable.class) 來檢查該類是否帶有
@DBTable 注解。如果存在,將表名存儲起來。然后讀取這個類的所有字段,并使用getDeclaredAnnotations() 進行檢查。這個方法返回一個包含特定字段上所有注解的數組。然后使用instanceof 操作符判斷這些注解是否是@SQLInteger 或者@SQLString 類型。如果是的話,在對應的處理塊中將構造出相應的數據庫列的字符串片段。注意,由于注解沒有繼承機制,如果要獲取近似多態的行為,使用getDeclaredAnnotations() 似乎是唯一的方式。
嵌套的@Constraint 注解被傳遞給getConstraints() 方法,并用它來構造一個包含SQL 約束的String 對象。
需要提醒的是,上面演示的技巧對于真實的對象/映射關系而言,是十分幼稚的。使用@DBTable 的注解來獲取表的名稱,這使得如果要修改表的名字,則迫使你重新編譯Java 代碼。這種效果并不理想。現在已經有了很多可用的框架,用于將對象映射到數據庫中,并且越來越多的框架開始使用注解了。

總結

以上是生活随笔為你收集整理的java进阶之注解篇的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。