Tổng quan về chú thích tích hợp trong Java

1. Tổng quan

Trong bài viết này, chúng ta sẽ nói về một tính năng cốt lõi của ngôn ngữ Java - các chú thích mặc định có sẵn trong JDK.

2. Chú thích là gì

Nói một cách đơn giản, chú thích là các loại Java được đặt trước bằng ký hiệu “@” .

Java đã có chú thích kể từ bản phát hành 1.5. Kể từ đó, họ đã định hình cách chúng tôi thiết kế các ứng dụng của mình.

Spring và Hibernate là những ví dụ tuyệt vời về các khuôn khổ dựa nhiều vào chú thích để kích hoạt các kỹ thuật thiết kế khác nhau.

Về cơ bản, một chú thích chỉ định siêu dữ liệu bổ sung cho mã nguồn mà nó bị ràng buộc . Bằng cách thêm chú thích vào một phương thức, giao diện, lớp hoặc trường, chúng ta có thể:

  1. Thông báo cho trình biên dịch về các cảnh báo và lỗi
  2. Thao tác mã nguồn tại thời điểm biên dịch
  3. Sửa đổi hoặc kiểm tra hành vi trong thời gian chạy

3. Chú thích tích hợp trong Java

Bây giờ chúng ta đã xem xét các khái niệm cơ bản, hãy cùng xem một số chú thích đi kèm với Java lõi. Đầu tiên, có một số thông tin biên dịch:

  1. @Ghi đè
  2. @SuppressWarnings
  3. @Deprecated
  4. @SafeVarargs
  5. @F FunctionInterface
  6. @Tự nhiên

Các chú thích này tạo ra hoặc ngăn chặn các cảnh báo và lỗi của trình biên dịch. Áp dụng chúng một cách nhất quán thường là một thực tiễn tốt vì việc thêm chúng có thể ngăn ngừa lỗi lập trình viên trong tương lai.

Các @ Override chú thích được sử dụng để chỉ ra rằng một phương pháp ghi đè hoặc thay thế hành vi của một phương pháp di truyền.

@SuppressWarnings cho biết chúng tôi muốn bỏ qua một số cảnh báo nhất định từ một phần của mã. Các @SafeVarargs chú thích cũng hoạt động trên một loại cảnh báo liên quan đến sử dụng varargs.

Các @Deprecated chú thích có thể được sử dụng để đánh dấu một API như không có ý định để sử dụng nữa. Hơn nữa, chú thích này đã được trang bị thêm trong Java 9 để trình bày thêm thông tin về việc không dùng nữa.

Đối với tất cả những điều này, bạn có thể tìm thấy thông tin chi tiết hơn trong các bài báo được liên kết.

3.1. @F FunctionInterface

Java 8 cho phép chúng ta viết mã theo một cách chức năng hơn.

Các giao diện Phương pháp trừu tượng đơn là một phần quan trọng của điều này. Nếu chúng tôi dự định sử dụng giao diện SAM bởi lambdas, chúng tôi có thể tùy chọn đánh dấu giao diện đó bằng @F Chức năngInterface :

@FunctionalInterface public interface Adder { int add(int a, int b); }

Giống như @Override với các phương thức, @F FunctionInterface tuyên bố ý định của chúng tôi với Adder .

Bây giờ, cho dù chúng tôi sử dụng @F FunctionInterface hay không, chúng tôi vẫn có thể sử dụng Adder theo cách tương tự:

Adder adder = (a,b) -> a + b; int result = adder.add(4,5);

Tuy nhiên, nếu chúng ta thêm phương thức thứ hai vào Adder, thì trình biên dịch sẽ phàn nàn:

@FunctionalInterface public interface Adder { // compiler complains that the interface is not a SAM int add(int a, int b); int div(int a, int b); }

Bây giờ, điều này sẽ được biên dịch mà không có chú thích @F FunctionInterface . Vậy, nó cho chúng ta những gì?

Giống như @Override , chú thích này bảo vệ chúng tôi khỏi lỗi lập trình viên trong tương lai. Mặc dù hợp pháp khi có nhiều hơn một phương thức trên một giao diện, nhưng đó không phải là khi giao diện đó được sử dụng làm mục tiêu lambda. Nếu không có chú thích này, trình biên dịch sẽ bị hỏng ở hàng chục vị trí mà Adder được sử dụng làm lambda. Bây giờ, nó chỉ phá vỡ trong chính Adder .

3.2. @Tự nhiên

Kể từ Java 8, có một chú thích mới trong gói java.lang.annotation được gọi là Native. Các @Native chú thích là chỉ áp dụng đối với các lĩnh vực. Nó cho biết trường được chú thích là một hằng số có thể được tham chiếu từ mã gốc . Ví dụ, đây là cách nó được sử dụng trong lớp Integer :

public final class Integer { @Native public static final int MIN_VALUE = 0x80000000; // omitted }

Chú thích này cũng có thể dùng như một gợi ý cho các công cụ tạo một số tệp tiêu đề phụ trợ.

4. Meta-Annotations

Tiếp theo, chú thích meta là chú thích có thể được áp dụng cho các chú thích khác.

Ví dụ: các siêu chú thích này được sử dụng cho cấu hình chú thích:

  1. @Mục tiêu
  2. @Giữ lại
  3. @Thừa hưởng
  4. @Documented
  5. @Có thể lặp lại

4.1. @Mục tiêu

Phạm vi của chú thích có thể thay đổi tùy theo yêu cầu. Trong khi một chú thích chỉ được sử dụng với các phương thức, chú thích khác có thể được sử dụng với các khai báo trường và phương thức khởi tạo.

Để xác định các yếu tố mục tiêu của một chú thích tùy chỉnh, chúng tôi cần gắn nhãn nó bằng chú thích @Target .

@Target có thể hoạt động với tám loại phần tử khác nhau. Nếu chúng ta nhìn vào mã nguồn của @ SafeVarargs , thì chúng ta có thể thấy rằng nó chỉ được đính kèm với các hàm tạo hoặc phương thức:

@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.CONSTRUCTOR, ElementType.METHOD}) public @interface SafeVarargs { }

4.2. @Giữ lại

Một số chú thích được sử dụng để gợi ý cho trình biên dịch, trong khi những chú thích khác được sử dụng trong thời gian chạy.

Chúng tôi sử dụng chú thích @Retention để cho biết chú thích của chúng tôi được áp dụng ở đâu trong vòng đời chương trình .

To do this, we need to configure @Retention with one of three retention policies:

  1. RetentionPolicy.SOURCE – visible by neither the compiler nor the runtime
  2. RetentionPolicy.CLASS – visible by the compiler
  3. RetentionPolicy.RUNTIME – visible by the compiler and the runtime

@Retention defaults to RetentionPolicy.SOURCE.

If we have an annotation that should be accessible at runtime:

@Retention(RetentionPolicy.RUNTIME) @Target(TYPE) public @interface RetentionAnnotation { }

Then, if we add some annotations to a class:

@RetentionAnnotation @Deprecated public class AnnotatedClass { }

Now we can reflect on AnnotatedClass to see how many annotations are retained:

@Test public void whenAnnotationRetentionPolicyRuntime_shouldAccess() { AnnotatedClass anAnnotatedClass = new AnnotatedClass(); Annotation[] annotations = anAnnotatedClass.getClass().getAnnotations(); assertThat(annotations.length, is(1)); }

The value is 1 because @RetentionAnnotation has a retention policy of RUNTIME while @Deprecated doesn't.

4.3. @Inherited

In some situations, we may need a subclass to have the annotations bound to a parent class.

We can use the @Inherited annotation to make our annotation propagate from an annotated class to its subclasses.

If we apply @Inherited to our custom annotation and then apply it to BaseClass:

@Inherited @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface InheritedAnnotation { } @InheritedAnnotation public class BaseClass { } public class DerivedClass extends BaseClass { }

Then, after extending the BaseClass, we should see that DerivedClass appears to have the same annotation at runtime:

@Test public void whenAnnotationInherited_thenShouldExist() { DerivedClass derivedClass = new DerivedClass(); InheritedAnnotation annotation = derivedClass.getClass() .getAnnotation(InheritedAnnotation.class); assertThat(annotation, instanceOf(InheritedAnnotation.class)); }

Without the @Inherited annotation, the above test would fail.

4.4. @Documented

By default, Java doesn't document the usage of an annotation in Javadocs.

But, we can use the @Documented annotation to change Java's default behavior.

If we create a custom annotation that uses @Documented:

@Documented @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ExcelCell { int value(); }

And, apply it to the appropriate Java element:

public class Employee { @ExcelCell(0) public String name; }

Then, the Employee Javadoc will reveal the annotation usage:

4.5. @Repeatable

Sometimes it can be useful to specify the same annotation more than once on a given Java element.

Before Java 7, we had to group annotations together into a single container annotation:

@Schedules({ @Schedule(time = "15:05"), @Schedule(time = "23:00") }) void scheduledAlarm() { }

However, Java 7 brought a cleaner approach. With the @Repeatable annotation, we can make an annotation repeatable:

@Repeatable(Schedules.class) public @interface Schedule { String time() default "09:00"; }

To use @Repeatable, we need to have a container annotation, too. In this case, we'll reuse @Schedules:

public @interface Schedules { Schedule[] value(); }

Of course, this looks a lot like what we had before Java 7. But, the value now is that the wrapper @Schedules isn't specified anymore when we need to repeat @Schedule:

@Schedule @Schedule(time = "15:05") @Schedule(time = "23:00") void scheduledAlarm() { }

Because Java requires the wrapper annotation, it was easy for us to migrate from pre-Java 7 annotation lists to repeatable annotations.

5. Conclusion

Trong bài viết này, chúng tôi đã nói về các chú thích tích hợp sẵn của Java mà mọi nhà phát triển Java nên quen thuộc.

Như mọi khi, tất cả các ví dụ của bài viết có thể được tìm thấy trên GitHub.