Annotation (chú thích) trong Java
- 20-10-2023
- Toanngo92
- 0 Comments
Annotations hoặc xử lý siêu dữ liệu (meta data processing) trong mã nguồn Java được giới thiệu lần đầu trong Java phiên bản 5.
Annotations là ghi chú, lời giải thích hoặc chú thích. Trong Java, annotations giúp gắn thêm thông tin bổ sung (còn được gọi là metadata) cho các thành phần chương trình. Annotations ảnh hưởng đến cách chương trình được xử lý bởi các công cụ và thư viện, điều này có thể ảnh hưởng đến ngữ nghĩa của chương trình. Annotations có thể được đọc từ các tệp nguồn hoặc tệp lớp. Comment trong mã nguồn được thay thế bằng annotations ở nhiều nơi.
Annotation đại diện cho việc sử dụng cụ thể của kiểu dữ liệu. Nói cách khác, annotation có thể được so sánh với một thể hiện của lớp. Một khai báo annotation bao gồm ‘@’ (at) theo sau là kiểu annotation.
Kiểu annotation giúp xác định một annotation. Điều này được sử dụng bởi lập trình viên khi tạo một annotation tùy chỉnh. Một định nghĩa kiểu annotation có thể được so sánh với một lớp. Định nghĩa kiểu annotation bao gồm ký hiệu ‘@’ (at), theo sau là từ khóa giao diện (interface) và tên annotation.
Annotations có thể được thêm ở cấp lớp, cấp trường và cấp phương thức. Java 5 cho phép xử lý annotation trong quá trình biên dịch mã và cả trong quá trình thực thi.
Java SE 6 giới thiệu một tính năng nâng cao trong annotations, gọi là API xử lý annotation có khả năng cắm (pluggable annotation). API này giúp người phát triển ứng dụng viết Trình xử lý Annotation Tùy chỉnh có thể cắm vào mã nguồn động để hoạt động trên tập hợp các annotations xuất hiện trong tệp nguồn. Do đó, API này còn được gọi là có khả năng cắm (pluggable).
Java 8 giới thiệu các thay đổi trong annotations sẽ giúp người phát triển tạo mã chất lượng hơn và chính xác hơn cho việc phân tích mã tự động để đảm bảo chất lượng.
Mục lục
Sử dụng annotation
Có thể cung cấp hướng dẫn cho trình biên dịch Java bằng cách sử dụng các annotation có sẵn. Ví dụ, bạn có thể cung cấp các annotation thời gian biên dịch sẽ được sử dụng để xây dựng dự án phần mềm. Nếu bạn sử dụng các công cụ xây dựng tự động để xây dựng dự án, chúng có thể quét mã của bạn để tìm các annotation cụ thể cụ thể và tạo mã nguồn (hoặc các tệp khác) dựa trên các chú thích.
Mặc dù các annotation Java sẽ không xuất hiện trong mã nguồn Java sau khi biên dịch, bạn có thể khai báo các annotation cụ thể để có sẵn khi chạy thông qua Reflection API (phản xạ API) và do đó, cung cấp hướng dẫn cho các chương trình.
Các ứng dụng phổ biến của Annotations bao gồm:
- Thông tin cho trình biên dịch: Trình biên dịch sử dụng các annotation để tạo thông báo hoặc lỗi dựa trên các quy tắc khác nhau. Ví dụ, hãy xem xét annotation @FunctionalInterface. Annotation này giúp trình biên dịch xác nhận lớp được chú thích và kiểm tra xem nó có phải là một giao diện chức năng hay không.
- Tài liệu: Các ứng dụng phần mềm (ví dụ: FindBugs) sử dụng các annotation để đánh giá chất lượng mã hoặc tạo các báo cáo tự động như Jenkins.
- Tạo mã: Annotations có thể được sử dụng để tạo mã hoặc tệp XML tự động bằng cách sử dụng thông tin metadata có sẵn trong mã (ví dụ: thư viện JAX).
- Xử lý thời gian chạy: Các annotation quan sát được trong thời gian chạy được sử dụng cho mục đích khác nhau như kiểm tra đơn vị, tiêm phụ thuộc, xác nhận, ghi log, truy cập dữ liệu và nhiều mục đích khác.
Khai báo annotation
Cách ngắn nhất để khai báo hoặc áp dụng một annotation trong Java là bằng cách thêm ký tự @ như sau:
Cú pháp:
@<name>
Ở đây:
Ký tự @ (at) sẽ báo cho trình biên dịch rằng đây là một Annotations. Tên ngay sau ký tự @ là tên của Annotations.
Ví dụ:
@Item
Ở đây tên annotation là Item
Các Loại Annotations Được Định Sẵn
Java chứa 3 Annotations tích hợp sẵn được sử dụng để cung cấp hướng dẫn cho trình biên dịch Java. Các Annotations này bao gồm:
- @Deprecated
- @Override
- @SuppressWarnings
@Deprecated
Annotations @Deprecated được sử dụng để đánh dấu một lớp, phương thức hoặc trường là không còn được sử dụng nữa, đồng thời biểu thị rằng phần mã này sẽ không còn sử dụng nữa. Nếu bất kỳ mã nào sử dụng các lớp, phương thức hoặc trường đã bị đánh dấu là không còn sử dụng sẽ được biên dịch, trình biên dịch sẽ tạo ra một thông báo cảnh báo.
Ví dụ:
class DeprecatedExample {
/**
* This method is marked as deprecated and should not be used.
* It is kept for backward compatibility but may be removed in the future.
*/
@Deprecated
public void deprecatedMethod() {
System.out.println("This is a deprecated method.");
}
public void newMethod() {
System.out.println("This is a new method.");
}
public static void main(String[] args) {
DeprecatedExample example = new DeprecatedExample();
// Calling the deprecated method
example.deprecatedMethod();
// Calling the new method
example.newMethod();
}
}
Sử dụng Annotations Java @Deprecated trực tiếp trước khai báo lớp sẽ đánh dấu lớp đó là lớp bị loại bỏ. Do đó, khi bạn cố gắng tạo một thể hiện của lớp đó trong phương thức main, bạn sẽ nhận được một thông báo lỗi từ trình biên dịch.
Trong một môi trường phát triển tích hợp (IDE) như NetBeans, bạn có thể xem thông báo cảnh báo từ trình biên dịch cho các thành phần đã bị loại bỏ chỉ khi tùy chọn này được bật. Hình minh họa phía dưới cho thấy cách thiết lập tùy chọn trong NetBeans để bật chế độ hiển thị thông báo cảnh báo về các thành phần đã bị loại bỏ.
Ví dụ interface deprecated:
@Deprecated
interface interfacetest{
// todo
}
Ở ví dụ phía trên, nếu chúng ta cố tình sử dụng phương thức deprecatedMethod(), trình biên dịch sẽ trả ra cảnh báo cho người dùng.
Ví dụ biến thành viên bị loại bỏ (member variable deprecated):
public class SampleConstant {
/**
* @deprecated This constant is deprecated. Please use MAX_UPLOAD_SIZE instead.
*/
@Deprecated
public static final int MAX_SIZE = 2048;
public static final int MAX_UPLOAD_SIZE = 2048;
}
Ví dụ hàm khởi tạo bị loại bỏ (constructor deprecated):
public class Gadget {
/**
* @deprecated This constructor is deprecated. Please use the other constructor.
*/
@Deprecated
public Gadget(String color, int height, int width) {
// Your implementation here
}
public Gadget(Style style) {
// Your implementation here
}
}
@Override
Chú thích @Override trong Java được sử dụng để tạo một kiểm tra thời gian biên dịch để chỉ ra rằng một phương thức đang bị ghi đè.
Trình biên dịch sẽ trả về một lỗi nếu phương thức không khớp với chương trình trong lớp cha.
Chú thích @Override không bắt buộc để ghi đè một phương thức trong một lớp cha. Nếu tên phương thức đã bị ghi đè thay đổi trong lớp cha, thì phương thức của lớp con sẽ không ghi đè lên nó nữa.
Dựa trên chú thích @Override, trình biên dịch cho biết rằng phương thức trong lớp con không ghi đè bất kỳ phương thức nào trong lớp cha.
Ví dụ phía dưới mô phỏng mã trong đó ClassOne có một phương thức có tên show() và lớp con của nó cũng có một phương thức show(), nhưng lớp con không ghi đè phương thức của lớp cha, do nó có kiểu tham số khác nhau (chữ kí phương thức khác nhau).
public class Classone {
public void show(String testmsg) {
System.out.println(testmsg);
}
public static void main(String[] args) {
SubClass obj = new SubClass();
obj.show("Good day!!");
}
}
class SubClass extends Classone {
@Override
public void show(String testmsg) {
System.out.println("I want to say: " + testmsg);
}
}
Khi mã nguồn được biên dịch, nó sẽ tạo ra một lỗi trình biên dịch với thông báo “phương thức không ghi đè hoặc thực hiện một phương thức từ lớp cha.” Điều này là do chú thích @Override. Chú thích này bắt buộc việc ghi đè và thông báo lỗi khi không có sự ghi đè nào.
Ví dụ trên thể hiện phiên bản sửa đổi của đoạn mã phía trên, trong trường hợp này, phương thức có tên show() trong lớp con thực sự đang ghi đè một phương thức show() được định nghĩa trong lớp cha.
Output:
I want to say: Good day!!
@Override annotation không bắt buộc phải sử dụng, kể cả khi đang ghi đè phương thức nhưng nó có hai ưu điểm:
- Nếu một lập trình viên mắc phải bất kỳ lỗi nào đó, chẳng hạn như sai tên phương thức, kiểu tham số hoặc điều gì đó trong quá trình ghi đè, thì sẽ gây ra một lỗi tại thời điểm biên dịch. Sử dụng chú thích này sẽ thông báo cho trình biên dịch rằng đây là việc ghi đè phương thức. Nếu không sử dụng chú thích này, phương thức trong lớp con sẽ được coi là một phương thức mới (không phải là phương thức ghi đè) trong lớp con.
- Chú thích @Override giúp cải thiện khả năng đọc mã nguồn. Vì vậy, nếu chữ ký của phương thức gốc bị thay đổi, tất cả các lớp con ghi đè phương thức cụ thể sẽ gây ra lỗi biên dịch, và điều này sẽ cho bạn biết rằng bạn cần phải thay đổi chữ ký trong các lớp con. Nếu có nhiều lớp trong ứng dụng, thì chú thích này giúp xác định các lớp cần thay đổi khi chữ ký của một phương thức bị thay đổi.
@SuppressWarnings
Chú thích @SuppressWarnings được sử dụng để tắt cảnh báo của trình biên dịch trong bất kỳ phương thức nào. Ví dụ, một cảnh báo sẽ được tạo ra khi một phương thức đã bị khuyến nghị không sử dụng (deprecated) được gọi bởi một phương thức khác. Những cảnh báo này có thể bị tắt bằng cách sử dụng chú thích @SuppressWarnings.
Ví dụ:
import java.util.ArrayList;
public class Example {
@SuppressWarnings("unchecked") // Suppress warnings related to unchecked operations
public static void main(String[] args) {
ArrayList list = new ArrayList(); // This line would normally generate a warning
list.add("Hello");
list.add("World");
for (Object item : list) {
String str = (String) item; // This line would also generate a warning without @SuppressWarnings
System.out.println(str);
}
}
}
@SuppressWarnings có thể được sử dụng với một hoặc nhiều cảnh báo dưới dạng đối số như được thể hiện trong đoạn mã dưới. Những cảnh báo này được định nghĩa trước bởi trình biên dịch.
@SuppressWarnings("unmarked")
@SuppressWarnings({"unmarked", "deprecation"})
Ví dụ dưới thể hiện cách sử dụng chú thích này. Nếu bạn có một lớp mà triển khai giao diện Serializable và bạn quên khai báo SerialVersionUID, điều này dẫn đến một cảnh báo từ trình biên dịch. Để tắt cảnh báo này, bạn có thể sử dụng @SuppressWarnings(“serial”) ở tầng class.
import java.io.Serializable;
@SuppressWarnings("serial")
public class MyAlert implements Serializable {
private String edition;
public String getEdition() {
return edition;
}
public static void main(String[] args) {
// Your main method code here
}
}
Không có cảnh báo nào xuất hiện trong quá trình biên dịch vì ở đây đã sử dụng @SuppressWarnings(“serial”).
Tạo annotation tùy chỉnh
Có thể tạo ra các annotation tùy chỉnh trong Java, và tương tự như một lớp hoặc giao diện Java, các annotation tùy chỉnh này được định nghĩa trong riêng của nó. Ví dụ:
@interface SampleAnnotation {
String name();
int age();
float mark();
String[] address();
}
Đoạn mã trên xác định một annotation có tên là Sampleannotate, bao gồm bốn phần tử. Từ khóa @interface cho biết đối với trình biên dịch Java rằng đây là một định nghĩa annotation trong Java.
Mỗi phần tử trong một annotation được định nghĩa giống như một phương thức trong một giao diện. Nó bao gồm một kiểu dữ liệu và một tên.
Ví dụ đưa vào sử dụng:
/*
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
* Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
*/
package com;
/**
*
* @author toan1
*/
@interface SampleAnnotation {
String name();
int age();
float mark();
String[] address();
}
@SampleAnnotation(
name = "toanngo92",
age = 18,
mark = (float) 8.0,
address = {"address 1", "address 2"}
)
public class MyClass {
String name;
Integer age;
Float mark;
String[] address;
public MyClass(String name, Integer age, Float mark, String[] address) {
this.name = name;
this.age = age;
this.mark = mark;
this.address = address;
}
@SuppressWarnings("empty-statement")
public MyClass() {
this.name = "toanngo92";
this.age = 20;
this.mark = Float.parseFloat("5.5");
this.address = new String[2];
this.address[0] = "toanngo";
this.address[1] = "string 2";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Float getMark() {
return mark;
}
public void setMark(Float mark) {
this.mark = mark;
}
public String[] getAddress() {
return address;
}
public void setAddress(String[] address) {
this.address = address;
}
@Override
public String toString() {
return "MyClass{" + "name=" + name + ", age=" + age + ", mark=" + mark + ", address=" + address + '}';
}
public static void main(String[] args) {
MyClass mc = new MyClass();
System.out.println(mc.toString());
}
}
@Retention
@Retention cho biết cho trình biên dịch về thời điểm mà các annotations sử dụng loại đã được gắn vào có thể được giữ lại. Ví dụ:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@interface SampleAnnotation {
String value();
}
Lưu ý rằng @Retention sẽ được thêm vào phần đầu của annotation.
Cú pháp:
@Retention(RetentionPolicy.value)
Trong đó
RetentionPolicy chỉ ra điểm dừng của annotation. Nó là một enum với ba giá trị:
- RetentionPolicy.RUNTIME xác định cho trình biên dịch Java cũng như JVM rằng annotation nên được giữ lại cho đến thời gian chạy, để có thể quản lý thông qua reflection.
- RetentionPolicy.CLASS đồng nghĩa với việc annotation được lưu trong tệp .class và không thể truy cập trong thời gian chạy. Annotations dưới loại này sẽ được ghi vào tệp .class bởi trình biên dịch, nhưng không được giữ lại bởi JVM trong thời gian chạy. Đây là chính sách giữ lại mặc định.
- RetentionPolicy.SOURCE chỉ cho phép annotation tồn tại trong thời gian biên dịch và không có sẵn trong thời gian chạy.
Chọn RetentionPolicy.RUNTIME cho phép bạn sử dụng annotation trong quá trình chạy chương trình và thông qua reflection (sự phản chiếu) để kiểm tra thông tin annotation.
@Target
Enumeration ElementType xác định các phần tử cụ thể trong Java có thể được chú thích (annotated) bằng annotation tùy chỉnh. Có các giá trị sau:
- ElementType.TYPE: Chú thích có thể áp dụng cho lớp, giao diện hoặc enum.
- ElementType.FIELD: Chú thích có thể áp dụng cho trường (field) hoặc thuộc tính (property).
- ElementType.METHOD: Chú thích có thể áp dụng cho phương thức.
- ElementType.PARAMETER: Chú thích có thể áp dụng cho các tham số của phương thức.
- ElementType.CONSTRUCTOR: Chú thích có thể áp dụng cho constructor.
- ElementType.LOCAL_VARIABLE: Chú thích có thể áp dụng cho biến cục bộ (local variable).
- ElementType.ANNOTATION_TYPE: Chú thích có thể áp dụng cho annotation khác.
- ElementType.PACKAGE: Chú thích có thể áp dụng cho package.
Ví dụ, nếu bạn muốn một annotation chỉ được sử dụng trên các phương thức, bạn có thể sử dụng @Target(ElementType.METHOD).
@Inherited
@Inherited annotation được sử dụng để chỉ ra rằng các chú thích trên một lớp cha nên được kế thừa bởi các lớp con của nó. Tuy nhiên, nó chỉ hoạt động với các chú thích trên các khai báo lớp. Điều này có nghĩa rằng nếu bạn có một chú thích tùy chỉnh được đánh dấu với @Inherited trên một lớp cha và bạn áp dụng chú thích đó cho lớp cha, nó cũng sẽ được kế thừa bởi các lớp con của nó.
Dưới đây là cách bạn có thể sử dụng chú thích @Inherited trong Java:
import java.lang.annotation.Inherited;
@Inherited
public @interface SampleAnnotation {
String value();
}
Trong ví dụ này, @SampleAnnotation được đánh dấu với @Inherited, cho thấy nếu nó được sử dụng trên một lớp cha, nó sẽ được kế thừa bởi các lớp con của nó.
Bây giờ, hãy xem cách bạn có thể sử dụng nó:
@SampleAnnotation("Superclass Annotation")
class Person {
// Triển khai lớp
}
public class Employee extends Person {
// Triển khai lớp
}
public class Main {
public static void main(String[] args) {
SampleAnnotation annotation = Employee.class.getAnnotation(SampleAnnotation.class);
if (annotation != null) {
String value = annotation.value();
System.out.println(value); // Kết quả: Chú thích lớp cha
}
}
}
Trong ví dụ này, @SampleAnnotation được áp dụng cho lớp cha Person và nó được kế thừa bởi lớp con Employee. Bạn có thể lấy nó bằng cách sử dụng reflection.
Lưu ý rằng @Inherited chỉ hoạt động với các chú thích ở mức lớp và không áp dụng cho các chú thích ở mức phương thức hoặc trường.
@Documented
@Documented là một annotation trong Java được sử dụng để thông báo cho công cụ JavaDoc rằng các custom annotations phải được hiển thị trong tài liệu JavaDoc cho các lớp (classes) sử dụng custom annotation đó. Nó không tác động trực tiếp đến việc hoạt động của annotations, mà chỉ thông báo cho công cụ tạo tài liệu JavaDoc rằng annotations này phải được hiển thị trong tài liệu.
Ví dụ dưới mô tả một custom annotation @MyAnnotation được đánh dấu bằng @Documented. Khi tạo tài liệu JavaDoc cho lớp sử dụng annotation @MyAnnotation, thông tin về annotation này sẽ được bao gồm trong tài liệu JavaDoc cho lớp đó.
import java.lang.annotation.Documented;
@Documented
public @interface TestAnnotation {
}
@TestAnnotation
public class Employee {
// Triển khai lớp Employee
}
Trong Java, một custom annotation có thể chứa các thành phần gọi là “elements,” giúp bạn đặt các giá trị. Elements tương tự như các tham số hoặc thuộc tính.
Ví dụ, nếu bạn định nghĩa một custom annotation có tên @MyAnnotation với một element tên là value, bạn có thể sử dụng annotation này như sau:
@MyAnnotation(value = "This is a custom annotation")
public class MyClass {
// ...
}
Trong trường hợp này, value là một element của annotation @MyAnnotation và bạn đã đặt giá trị của nó thành “This is a custom annotation”.
Một custom annotation có thể có nhiều elements, và mỗi element có thể có một giá trị mặc định nếu cần. Elements trong annotation cũng có kiểu dữ liệu, giúp xác định loại giá trị mà bạn có thể đặt cho chúng.
Ví dụ annotation có nhiều thành phần:
@MyAnnotation(value1 = "This is a custom annotation", value2 = "Value 2 of annotation")
Trong trường hợp một annotation chỉ chứa một element, quy ước chuẩn là đặt tên element đó là “value.” Điều này giúp rút ngắn cú pháp khi sử dụng annotation. Ví dụ:
public @interface MyAnnotation {
String value() default "";
}
@MyAnnotation("This is a custom annotation")
public class MyClass {
// ...
}
Trong ví dụ trên, annotation @MyAnnotation chỉ có một element tên là “value,” và bạn có thể đặt giá trị cho nó mà không cần chỉ rõ tên của element.
Vị trí đặt annotation
Các annotation trong Java có thể được đặt trên các lớp (classes), giao diện (interfaces), phương thức (methods), tham số phương thức (method parameters), trường (fields), và biến cục bộ (local variables). Ví dụ về việc sử dụng annotation trước định nghĩa một lớp được thể hiện trong ví dụ:
@MyAnnotation
public class Gadget {
// ...
}
Trong ví dụ này, annotation @MyAnnotation được đặt trước định nghĩa lớp Gadget.
Khai báo kiểu annotation
Các annotation có thể thay thế các comment trong mã nguồn. Hãy xem xét một tình huống ví dụ.
Mã phía dưới hiển thị một ví dụ trong đó một nhà phát triển web thường bắt đầu mỗi lớp bằng các comment để cung cấp thông tin quan trọng.
public class SampleInfo {
// Lead Designer: Mark Lee
// Last modified: 06/01/2021
// By: Mark Lee
// Dev Team: Parker, Kevin, and Anna
// Business Logic Functionality begins here
}
Tuy nhiên, bạn có thể đạt được cùng một kết quả bằng cách sử dụng annotation. Đầu tiên, bạn cần định nghĩa kiểu annotation, sau đó sử dụng nó để thêm các thông tin tương tự bằng annotation, như được thể hiện trong mã dưới.
@interface ClassWebDevelopment {
String leadDesigner() default "";
int recentUpdates() default 0;
String lastEdited() default "N/a";
String lastEditedDay() default "N/a";
String[] developmentTeam() default {};
}
Một khi kiểu annotation đã được định nghĩa, bạn có thể sử dụng các annotation của loại đó với các giá trị như được hiển thị trong ví dụ dưới:
@ClassWebDevelopment(
leadDesigner = "Mark Lee",
recentUpdates = 3,
lastEdited = "6/04/2012",
lastEditedBy = "Mark Lee",
devTeam = {"Parker", "Kevin", "Anna"}
)
public class SampleInfo {
// todo
}
Kiểu annotation
Java SE 8 đã làm cho các annotations trở nên mạnh mẽ và tốt hơn. Annotations hiện đã trở nên đa mục đích. Type annotations được tạo ra để duy trì phân tích tốt hơn của các chương trình Java, đảm bảo việc kiểm tra kiểu tốt hơn.
Ví dụ, để đảm bảo rằng một biến cụ thể trong chương trình không bao giờ được gán bằng null hoặc để tránh kích hoạt một NullPointerException, bạn có thể viết một plugin tùy chỉnh. Sau đó, sửa mã để gắn thông báo cho biến cụ thể đó, chỉ định rằng nó không bao giờ được gán bằng null. Cú pháp sau thể hiện việc khai báo biến với annotation @Nonnull:
String @NonNull [] names = new String @NonNull [5];
Trình biên dịch sẽ hiển thị một cảnh báo nếu nó phát hiện một vấn đề tiềm năng, cho phép bạn chỉnh sửa mã để tránh lỗi trong quá trình tính toán mô-đun Nonnull tại dòng lệnh trong quá trình biên dịch mã.
Sau khi chỉnh sửa mã để loại bỏ tất cả các cảnh báo, lỗi cụ thể này sẽ không xảy ra khi chương trình chạy.
Có thể sử dụng nhiều kiểm tra kiểu trong các module, trong đó mỗi module theo dõi một loại lỗi khác nhau. Tương tự, chúng ta có thể xây dựng trên cơ sở hệ thống kiểu Java, thực hiện kiểm tra chính xác khi cần thiết.
Việc sử dụng chú thích kiểu và sự hiện diện của các plugin sẽ giúp bạn viết mã mạnh mẽ hơn và ít dễ bị lỗi.
Ví dụ tạo thể hiện từ biểu thức
Cú pháp:
new @Interned MyObject();
Ví dụ ép kiểu
Cú pháp:
myString = (@NonNull String) str;
Lặp lại chú thích (Repeating Annotation)
Chúng ta có thể muốn áp dụng cùng một chú thích cho một khai báo hoặc sử dụng kiểu. Từ phiên bản Java 8 trở đi, Java cung cấp tính năng “repeating annotations” cho phép bạn làm điều này.
Ví dụ, giả sử bạn cần sử dụng một dịch vụ đếm thời gian cho phép chạy một phương thức scorePapers() vào thời gian cụ thể hoặc theo một lịch trình nhất định, vào ngày cuối cùng của tháng và vào mỗi thứ Tư lúc 21:00. Để thiết lập dịch vụ đếm thời gian, bạn có thể tạo một chú thích @ScoreSchedule và sử dụng nó hai lần cho phương thức scorePapers().
Ví dụ mô tả:
@Scoreschedule(dayofMonth="last")
@Scoreschedule(dayOfWeek="Wed", hour="21")
public void scorePapers(){
// todo
}
Dưới đây, chúng ta đã sử dụng chú thích @scoreSchedule hai lần, do đó, nó là một chú thích lặp lại. Chú thích lặp lại có thể được áp dụng không chỉ cho các phương thức mà còn cho bất kỳ phần tử nào có thể được chú thích.
Đoạn mã dưới trình bày một ví dụ về việc định nghĩa một lớp để xử lý các ngoại lệ truy cập trái phép.
@Alert(role = "Publishers")
@Alert(role = "Editors")
@Alert(role = "Authors")
public class UnauthorizedAccessException extends ... {
// Class definition
}
Vì mục đích tương thích, các repeating annotation được tải vào một annotation chứa được tạo ra bởi trình biên dịch Java.
Để trình biên dịch thực hiện điều này, cần có hai khai báo trong mã nguồn như sau:
Khai báo một Loại Annotation Có Khả Năng Lặp Lại (Repeatable Annotation Type):
Để cho phép các annotation có khả năng lặp lại, bạn cần định nghĩa một kiểu annotation cho phép lặp lại (repeatable annotation type). Điều này thường thực hiện bằng cách sử dụng annotation @Repeatable trên một kiểu chứa (container type) cho phép lặp lại.
Ví dụ:
import java.lang.annotation.Repeatable;
@Repeatable(ScoreSchedules.class)
public @interface Scoreschedule {
String monthDay() default "1st";
String weekDay() default "Monday";
int hour() default 12;
}
Trình biên dịch Java tạo ra một kiểu annotation để lưu trữ các annotation lặp lại, đó chính là giá trị của annotation @Repeatable.
Khai báo Kiểu Annotation Container
Bên cạnh đó, bạn cũng cần định nghĩa một kiểu annotation container, ví dụ như @Alerts. Kiểu container này sẽ chứa các phiên bản của annotation lặp lại. Nó phải được định nghĩa sao cho chứa một mảng các phiên bản của annotation lặp lại.
public @interface Alerts {
Alert[] value();
}
Trong trường hợp này, kiểu annotation container @Alerts chứa một mảng các phiên bản của @Alert.
Sau khi bạn đã định nghĩa kiểu annotation lặp lại và kiểu annotation container, bạn có thể sử dụng annotation lặp lại như đã mô tả trong ví dụ trước đó. Trình biên dịch Java sẽ tự động quản lý việc gom các phiên bản của annotation lặp lại vào kiểu annotation container khi bạn sử dụng chúng trên một phần tử.
package com;
import java.lang.annotation.Repeatable;
@Repeatable(Scoreschedules.class)
@interface Scoreschedule {
String monthDay() default "1st";
String weekDay() default "Monday";
int hour() default 12;
}
@interface Scoreschedules {
Scoreschedule[] value();
}
public class RepeatingDemo {
public static void main(String[] args) {
}
@Scoreschedule(monthDay = "last")
@Scoreschedule(weekDay = "Fri", hour = 15)
public void scorePapers() {
}
}
Xử lý annotation sử dụng Reflection
API Reflection của Java có thể được sử dụng để truy cập các annotation trên bất kỳ loại nào như class, interface hoặc methods.
Có một số phương thức trong API Reflection để truy xuất các annotation. Hành vi của các phương thức tạo ra một annotation duy nhất, chẳng hạn như AnnotatedElement.getAnnotationByType(Class <T>), vẫn không thay đổi và chỉ trả về một annotation duy nhất khi có một annotation kiểu cần thiết. Nếu có một hoặc nhiều annotation kiểu cần thiết, chúng có thể được truy xuất bằng cách lấy kiểu annotation container.
Ví dụ:
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.WildcardType;
import java.util.List;
/**
* Sample Java Reflection
*/
public class SampleRefl {
public static void main(String[] args) throws NoSuchMethodException, SecurityException {
Method sampMeth = SampleRefl.class.getMethod("sampMeth", List.class); // Method defined as sampMeth
ParameterizedType sampleLity = (ParameterizedType) sampMeth.getGenericParameterTypes()[0]; // List Type defined as SampleLity
ParameterizedType sampleClty = (ParameterizedType) sampleLity.getActualTypeArguments()[0]; // Class Type defined as SampleClTy
WildcardType sampleGenTy = (WildcardType) sampleClty.getActualTypeArguments()[0]; // Generic Type defined as SampleGenTy
Class<?> sampleGenCl = (Class<?>) sampleGenTy.getUpperBounds()[0]; // Generic Class defined as SampleGenCl
boolean isException = Exception.class.isAssignableFrom(sampleGenCl);
// To display whether the statement is true or false
System.out.println("This Class extends Exception: " + isException);
boolean isRuntimeException = RuntimeException.class.isAssignableFrom(sampleGenCl);
// To display whether the statement is true or false
System.out.println("This Class extends RuntimeException: " + isRuntimeException);
}
public void sampMeth(List<Class<? extends Exception>> exceptionClasses) {
// Method implementation
}
}
@Functional interface
Giao diện chức năng (functional interface) là một giao diện có một phương thức trừu tượng (không phải mặc định). Trình biên dịch sẽ xem xét phần tử được chú thích là một giao diện chức năng và sẽ tạo ra một lỗi nếu phần tử không tuân thủ các yêu cầu
Ví dụ:
@FunctionalInterface
interface MyFunctionalInterface {
void doSomething(int a, int b);
}
public class FunctionalInterfaceExample {
public static void main(String[] args) {
// Using a lambda expression to implement the functional interface
MyFunctionalInterface myFunction = (int a, int b) -> {
System.out.println("The sum is: " + (a + b));
};
// Calling the method defined in the functional interface
myFunction.doSomething(10, 5);
// You can also use a method reference
MyFunctionalInterface anotherFunction = FunctionalInterfaceExample::printSum;
anotherFunction.doSomething(7, 3);
}
public static void printSum(int a, int b) {
System.out.println("The sum is: " + (a + b));
}
}
Các thư viện đã biết (có sẵn) sử dụng các chú thích
Hiện nay, rất nhiều thư viện đang sử dụng các chú thích với nhiều mục đích khác nhau như phân tích chất lượng mã nguồn, kiểm thử đơn vị, phân tích XML, tiêm phụ thuộc, và nhiều mục đích khác. Một số thư viện này bao gồm JAXB, Junit và FindBugs.
Bài tập
1. Để xử lý văn bản người ta xây dựng lớp VanBan có thuộc tính riêng là một xâu ký tự.
Yêu cầu 1: Xây dựng hàm khởi tạo VanBan(), VanBan(String st).
Yêu cầu 2: Xây dựng phương thức đếm số từ của văn bản.
Yêu cầu 3: Xây dựng phương thức đếm số lượng ký tự A( không phân biệt hoa thường) của văn bản.
Yêu cầu 4: Chuẩn hoá văn bản theo tiêu chuẩn sau: Ở đầu và cuối sâu không có ký tự trống, ở giữa sâu không có 2 hoặc nhiều hơn các ký tự khoảng trắng kiền kề nhau.
2.Để quản lý biên lai thu tiền điện, người ta cần các thông tin sau:
- Với mỗi biên lai: Thông tin về hộ sử dụng điện, chỉ số điện cũ, chỉ số mới, số tiền phải trả.
- Các thông tin riêng của từng hộ gia đình sử dụng điện: Họ tên chủ hộ, số nhà, mã số công tơ điện.
Yêu cầu 1: Hãy xây dựng lớp khachHang để lưu trữu các thông tin riêng của mỗi hộ gia đình.
Yêu cầu 2: Xây dựng lớp BienLai để quản lý việc sử dụng và thanh toán tiền điện của các hộ dân.
Yêu cầu 3: Xây dựng các phương thức thêm, xoá sửa các thông tin riêng của mỗi hộ sử dụng điện.
Yêu cầu 4: Viết phương thức tính tiền điện cho mỗi hộ gia đình theo công thức: (số mới – số cũ ) * 5
3. Thư viện trung tâm một trường đại học có nhu cầu quản lý việc mượn, trả sách. Sinh viên đăng ký tham gia mượn sách thông qua thẻ mà thư viện cung cấp.
Với mỗi thẻ sẽ lưu các thông tin sau: Mã phiếu mượn, ngày mượn, hạn trả, số hiệu sách, và các thông tin cá nhân của sinh viên mượn sách. Các thông tin của sinh viên mượn sách bao gồm: Họ tên, tuổi, lớp.
Để đơn giản cho ứng dụng console. Chúng ta mặc định ngày mượn, ngày trả là số nguyên dương.
Yêu cầu 1: Xây dựng lớp SinhVien để quản lý thông tin của mỗi sinh viên.
Yêu cầu 2: Xây dựng lớp TheMuon để quản lý việc mượn trả sách của các sinh viên.
Yêu cầu 3: Xây dựng các phương thức: Thêm, xoá theo mã phiếu mượn và hiển thị thông tin các thẻ mượn.