跳到主要内容

Java 注解概述

什么是Java注解?

Java注解(Annotation)是JDK 5.0引入的一种特殊"标记",它提供了一种为代码添加元数据(metadata)的方式,这些元数据信息可以在编译时、类加载时或运行时被读取,并执行相应的处理。

注解本身不会对代码逻辑产生直接影响,但可以被其他工具或框架用来产生额外的行为或效果。

备注

注解的英文是Annotation,在Java中使用@符号表示,如@Override@Deprecated等。

注解的基本语法

Java注解的基本语法如下:

java
@AnnotationName(elementName = elementValue, elementName2 = elementValue2)

其中:

  • @AnnotationName:注解的名称
  • elementNameelementValue:注解的元素名和值

若注解只有一个元素且名为value,可以省略元素名:

java
@AnnotationName(elementValue)

若注解没有元素,则可以直接使用:

java
@AnnotationName

Java 内置注解

Java提供了几个内置的注解,它们已经在JDK中定义好,可以直接使用:

1. @Override

用于标记一个方法是重写父类的方法。如果被标记的方法不是重写父类方法,编译器会报错。

java
class Parent {
public void display() {
System.out.println("Parent's display");
}
}

class Child extends Parent {
@Override
public void display() {
System.out.println("Child's display");
}
}

2. @Deprecated

用于标记某个程序元素(类、方法、字段等)已过时,不推荐使用。

java
public class Calculator {
/**
* @deprecated 此方法效率低,请使用 {@link #addFast(int, int)} 替代
*/
@Deprecated
public int add(int a, int b) {
try {
Thread.sleep(100); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
return a + b;
}

public int addFast(int a, int b) {
return a + b;
}
}

当你使用被@Deprecated标记的方法时,IDE会给出警告提示:

java
Calculator calc = new Calculator();
calc.add(5, 3); // 编译器会警告:'add(int, int)' is deprecated

3. @SuppressWarnings

用于抑制编译器产生的警告信息。

java
@SuppressWarnings("unchecked")
public List<String> createList() {
List list = new ArrayList();
return list; // 没有使用泛型,正常会产生警告,但被抑制了
}

4. @FunctionalInterface

用于标记一个接口是函数式接口(只包含一个抽象方法的接口)。

java
@FunctionalInterface
public interface Calculator {
int calculate(int a, int b);

// 如果添加第二个抽象方法,编译器会报错
// int multiply(int a, int b);
}

自定义注解

除了使用Java内置的注解外,你还可以创建自己的注解。定义一个注解类似于定义一个接口,但使用@interface关键字:

java
public @interface MyAnnotation {
String value() default "默认值";
int count() default 0;
String[] tags() default {};
}

在这个例子中,我们定义了一个名为MyAnnotation的注解,它有三个元素:valuecounttags,并都提供了默认值。

元注解

元注解是用于注解其他注解的注解。Java提供了几个重要的元注解:

1. @Retention

指定注解的保留策略,即注解在什么级别可用:

  • RetentionPolicy.SOURCE:注解仅在源代码中保留,编译时会被丢弃。
  • RetentionPolicy.CLASS:注解在编译时保留,但在运行时会被VM丢弃,这是默认值。
  • RetentionPolicy.RUNTIME:注解在运行时保留,可以通过反射API获取。
java
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRuntimeAnnotation {
String value() default "";
}

2. @Target

指定注解可以应用在哪些程序元素上:

  • ElementType.TYPE:类、接口、枚举类
  • ElementType.FIELD:字段(包括枚举常量)
  • ElementType.METHOD:方法
  • ElementType.PARAMETER:方法参数
  • ElementType.CONSTRUCTOR:构造函数
  • ElementType.LOCAL_VARIABLE:局部变量
  • ElementType.ANNOTATION_TYPE:注解类型
  • ElementType.PACKAGE:包
  • ElementType.TYPE_PARAMETER:类型参数(Java 8引入)
  • ElementType.TYPE_USE:类型使用(Java 8引入)
java
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface MyFieldAndMethodAnnotation {
// 这个注解只能用于方法和字段
}

3. @Documented

指定注解是否包含在JavaDoc中:

java
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface MyDocumentedAnnotation {
String value();
}

4. @Inherited

指定注解是否可以被子类继承:

java
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyInheritedAnnotation {
String value();
}

5. @Repeatable(Java 8+)

指定注解是可重复的,即同一个元素上可以多次使用相同的注解:

java
// 首先定义一个容器注解
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRepeatableAnnotations {
MyRepeatableAnnotation[] value();
}

// 然后定义可重复注解
@Repeatable(MyRepeatableAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRepeatableAnnotation {
String value();
}

// 使用方式
@MyRepeatableAnnotation("value1")
@MyRepeatableAnnotation("value2")
public class RepeatableExample {
// 类定义
}

注解处理

注解本身不会执行任何操作,它只是作为一种标记。要使注解起作用,需要有相应的处理器来处理这些注解。处理方式主要有两种:

  1. 编译时处理:通过注解处理器(Annotation Processor)在编译阶段处理注解
  2. 运行时处理:通过反射API在运行时处理注解

运行时处理示例

以下是使用反射在运行时处理注解的示例:

java
// 定义一个注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TestInfo {
String value();
int priority() default 0;
}

// 使用注解
public class AnnotationTest {
@TestInfo(value = "这是一个测试方法", priority = 1)
public void testMethod1() {
System.out.println("执行测试方法1");
}

@TestInfo("这是另一个测试方法")
public void testMethod2() {
System.out.println("执行测试方法2");
}

public static void main(String[] args) throws Exception {
AnnotationTest test = new AnnotationTest();

// 获取类的所有方法
Method[] methods = AnnotationTest.class.getDeclaredMethods();

for (Method method : methods) {
// 检查方法是否有TestInfo注解
if (method.isAnnotationPresent(TestInfo.class)) {
// 获取注解对象
TestInfo annotation = method.getAnnotation(TestInfo.class);
System.out.println("方法: " + method.getName());
System.out.println("描述: " + annotation.value());
System.out.println("优先级: " + annotation.priority());

// 反射调用方法
method.invoke(test);
System.out.println("------------------------");
}
}
}
}

输出结果:

方法: testMethod1
描述: 这是一个测试方法
优先级: 1
执行测试方法1
------------------------
方法: testMethod2
描述: 这是另一个测试方法
优先级: 0
执行测试方法2
------------------------

注解的实际应用场景

注解在现代Java开发中有广泛的应用,以下是一些常见的应用场景:

1. 依赖注入(如Spring框架)

Spring框架大量使用注解来实现依赖注入和配置:

java
@Controller
public class UserController {
@Autowired
private UserService userService;

@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
}

2. 持久化(如JPA/Hibernate)

JPA使用注解来定义实体类和映射关系:

java
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "user_name", nullable = false, length = 50)
private String userName;

@Column(name = "email", unique = true)
private String email;

@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Order> orders;
}

3. 单元测试(如JUnit)

JUnit使用注解来标记测试方法和生命周期方法:

java
public class CalculatorTest {
private Calculator calculator;

@BeforeEach
public void setUp() {
calculator = new Calculator();
}

@Test
public void testAdd() {
assertEquals(5, calculator.add(2, 3), "2 + 3 应该等于 5");
}

@Test
@Disabled("这个测试暂时被禁用")
public void testDivide() {
assertEquals(2, calculator.divide(6, 3));
}

@AfterEach
public void tearDown() {
calculator = null;
}
}

4. 配置和校验(如Lombok和Validation API)

Lombok使用注解来自动生成代码:

java
@Data // 自动生成getter、setter、toString、equals和hashCode方法
@NoArgsConstructor
@AllArgsConstructor
public class Product {
private Long id;
private String name;

@NonNull
private BigDecimal price;
}

Bean Validation API使用注解进行数据验证:

java
public class RegisterForm {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在3-20之间")
private String username;

@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;

@NotBlank(message = "密码不能为空")
@Pattern(regexp = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).{8,20}$",
message = "密码必须包含数字、小写字母、大写字母,长度在8-20之间")
private String password;
}

Java 注解的优点

  1. 提供元数据:为代码提供丰富的元数据,增强代码的可读性和可维护性
  2. 简化配置:减少或替代XML配置文件的使用
  3. 编译时检查:某些注解可以在编译时进行检查,及早发现错误
  4. 提高开发效率:减少重复代码,如自动生成getter/setter方法
  5. 与框架集成:现代Java框架大多基于注解驱动,简化开发

注解的局限性

  1. 增加学习成本:需要了解各种注解的用途和使用场景
  2. 可读性问题:过多的注解可能降低代码可读性
  3. 运行时开销:运行时处理注解会带来一定的性能开销
  4. 不适合复杂配置:对于非常复杂的配置,XML或配置类可能更合适

总结

Java注解是一种强大的元编程工具,它通过为代码添加元数据的方式,使程序能够在编译时或运行时获取这些信息并采取相应的行动。注解广泛应用于现代Java开发中的各个方面,包括依赖注入、ORM映射、单元测试、配置管理等。

理解和掌握Java注解可以帮助你更高效地使用各种Java框架,减少样板代码,提升开发效率。但同时也要注意不要过度依赖注解,在适当的场景使用适当的技术才是最佳实践。

进一步学习

  1. 尝试创建自己的自定义注解,并实现一个简单的注解处理器
  2. 深入学习常用框架中的注解,如Spring、Hibernate、JUnit等
  3. 了解注解处理器API(javax.annotation.processing包)
  4. 探索Java 8引入的类型注解功能
  5. 学习如何在单元测试中使用反射测试带注解的代码
练习

尝试创建一个简单的日志注解@Log,可以应用在方法上,并编写处理代码在方法执行前后输出日志信息。