Java注解(Annotation)是从JDK 5.0开始引入的重要特性,它为代码提供了元数据支持,可以在编译时、运行时或开发工具中使用。注解广泛应用于框架配置、代码生成、依赖注入等方面。今天详细介绍Java注解的概念、使用方法、自定义注解以及在实际开发中的应用。
注解的基本概念
什么是注解
注解是一种特殊的Java类型,它以@符号开头,可以为代码添加元数据信息。注解本身不影响代码的执行,但可以通过反射或其他工具读取和处理这些元数据。
注解的分类
内置注解:Java语言本身提供的注解
元注解:用于定义其他注解的注解
自定义注解:开发者定义的注解
注解的作用
编译时检查:如@Override检查方法重写
代码生成:如Lombok生成getter/setter方法
配置信息:如Spring框架的@Configuration注解
运行时处理:如JUnit测试注解
内置注解
@Override
用于标记方法重写父类方法,编译器会检查方法签名是否正确。
public class Parent {
public void display() {
System.out.println("Parent display");
}
}
publicclass Child extends Parent {
@Override
public void display() {
System.out.println("Child display");
}
// 编译错误:方法不存在于父类中
// @Override
// public void show() {
// }
}
@Deprecated
标记方法、类或字段已过时,建议不要使用。
public class DeprecatedExample {
@Deprecated
public static void oldMethod() {
System.out.println("这个方法已过时");
}
@Deprecated(since = "1.2", forRemoval = true)
public static void veryOldMethod() {
System.out.println("这个方法即将被移除");
}
public static void main(String[] args) {
// 使用已过时的方法会产生警告
oldMethod();
veryOldMethod();
}
}
@SuppressWarnings
抑制编译器警告。
import java.util.ArrayList;
import java.util.List;
publicclass SuppressWarningsExample {
@SuppressWarnings("unchecked")
public static void uncheckedCast() {
List rawList = new ArrayList();
List<String> stringList = (List<String>) rawList; // 通常会产生unchecked警告
}
@SuppressWarnings({"unchecked", "unused"})
public static void multipleWarnings() {
List rawList = new ArrayList();
List<String> stringList = (List<String>) rawList;
int unusedVariable = 42; // 不会产生unused警告
}
@SuppressWarnings("all")
public static void suppressAll() {
// 抑制所有警告
}
}
@SafeVarargs (Java 7+)
用于标记可变参数方法是安全的,不会产生堆污染警告。
import java.util.Arrays;
import java.util.List;
publicclass SafeVarargsExample {
@SafeVarargs
publicstatic <T> List<T> asList(T... elements) {
return Arrays.asList(elements);
}
public static void main(String[] args) {
List<String> list = asList("A", "B", "C");
System.out.println(list);
}
}
@FunctionalInterface (Java 8+)
标记接口为函数式接口,确保只包含一个抽象方法。
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
// 默认方法不计入抽象方法数量
default void display() {
System.out.println("Calculator");
}
// 静态方法不计入抽象方法数量
static void info() {
System.out.println("这是一个计算器接口");
}
// 编译错误:函数式接口只能有一个抽象方法
// void anotherMethod();
}
元注解
元注解用于定义其他注解的行为。
@Retention
指定注解的保留策略:
RetentionPolicy.SOURCE:只在源代码中保留,编译时丢弃
RetentionPolicy.CLASS:保留在class文件中,但JVM运行时不保留(默认)
RetentionPolicy.RUNTIME:保留到运行时,可以通过反射获取
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.SOURCE)
@interface SourceAnnotation {
}
@Retention(RetentionPolicy.CLASS)
@interface ClassAnnotation {
}
@Retention(RetentionPolicy.RUNTIME)
@interface RuntimeAnnotation {
}
@Target
指定注解可以应用的目标元素:
ElementType.TYPE:类、接口、枚举
ElementType.FIELD:字段
ElementType.METHOD:方法
ElementType.PARAMETER:方法参数
ElementType.CONSTRUCTOR:构造方法
ElementType.LOCAL_VARIABLE:局部变量
ElementType.ANNOTATION_TYPE:注解类型
ElementType.PACKAGE:包
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@interface ClassAnnotation {
}
@Target(ElementType.FIELD)
@interface FieldAnnotation {
}
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
@interface MethodAnnotation {
}
@Target(ElementType.PARAMETER)
@interface ParameterAnnotation {
}
@Documented
指定注解是否包含在JavaDoc中。
import java.lang.annotation.Documented;
@Documented
@interface DocumentedAnnotation {
String value();
}
// 这个注解会出现在生成的JavaDoc中
@Inherited
指定注解是否可以被子类继承。
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface InheritableAnnotation {
String value();
}
@InheritableAnnotation("父类注解")
class Parent {
}
class Child extends Parent {
public static void main(String[] args) {
// 子类会继承父类的注解
InheritableAnnotation annotation = Child.class.getAnnotation(InheritableAnnotation.class);
if (annotation != null) {
System.out.println("继承的注解值:" + annotation.value());
}
}
}
@Repeatable (Java 8+)
允许注解在同一元素上重复使用。
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@interface Author {
String name();
}
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Authors.class)
@interface Authors {
Author[] value();
}
@Author(name = "张三")
@Author(name = "李四")
class Book {
public static void main(String[] args) {
Author[] authors = Book.class.getAnnotationsByType(Author.class);
for (Author author : authors) {
System.out.println("作者:" + author.name());
}
}
}
自定义注解
基本语法
// 定义注解
@interface MyAnnotation {
String value(); // 必需属性
int count() default 1; // 可选属性,默认值为1
String[] tags() default {}; // 数组属性
}
// 使用注解
@MyAnnotation(value = "测试", count = 5, tags = {"tag1", "tag2"})
publicclass MyClass {
@MyAnnotation("字段注解")
private String field;
@MyAnnotation(value = "方法注解", count = 3)
public void myMethod() {
}
}
注解属性类型
注解属性可以是以下类型:
基本数据类型(int, long, float, double, boolean, char, byte, short)
String
Class
枚举类型
注解类型
以上类型的数组
@interface ComplexAnnotation {
// 基本类型
int intValue() default 0;
boolean boolValue() default false;
// 字符串
String stringValue() default "";
// Class类型
Class<?> classValue() default Object.class;
// 枚举
enum Level {LOW, MEDIUM, HIGH}
Level level() default Level.MEDIUM;
// 嵌套注解
SimpleAnnotation simple() default @SimpleAnnotation("default");
// 数组
String[] stringArray() default {};
int[] intArray() default {};
}
@interface SimpleAnnotation {
String value();
}
注解处理
运行时通过反射处理注解
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@Retention(RetentionPolicy.RUNTIME)
@interface MyRuntimeAnnotation {
String value();
int priority() default 1;
}
@MyRuntimeAnnotation(value = "测试类", priority = 5)
publicclass AnnotationProcessor {
@MyRuntimeAnnotation("测试字段")
private String testField;
@MyRuntimeAnnotation(value = "测试方法", priority = 3)
public void testMethod() {
}
public static void main(String[] args) {
Class<?> clazz = AnnotationProcessor.class;
// 处理类级注解
if (clazz.isAnnotationPresent(MyRuntimeAnnotation.class)) {
MyRuntimeAnnotation classAnnotation = clazz.getAnnotation(MyRuntimeAnnotation.class);
System.out.println("类注解:" + classAnnotation.value() + ", 优先级:" + classAnnotation.priority());
}
// 处理字段注解
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(MyRuntimeAnnotation.class)) {
MyRuntimeAnnotation fieldAnnotation = field.getAnnotation(MyRuntimeAnnotation.class);
System.out.println("字段注解:" + fieldAnnotation.value());
}
}
// 处理方法注解
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(MyRuntimeAnnotation.class)) {
MyRuntimeAnnotation methodAnnotation = method.getAnnotation(MyRuntimeAnnotation.class);
System.out.println("方法注解:" + methodAnnotation.value() + ", 优先级:" + methodAnnotation.priority());
}
}
}
}
编译时注解处理
编译时注解处理需要使用Annotation Processor API。
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import java.util.Set;
@SupportedAnnotationTypes("com.example.MyCompileAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
publicclass MyAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
// 处理注解元素,生成代码等
System.out.println("处理注解元素:" + element.getSimpleName());
}
}
returntrue;
}
}
注解在框架中的应用
Spring框架注解示例
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
@Component
publicclass UserService {
@Value("${app.user.default-age}")
privateint defaultAge;
@Autowired
private UserRepository userRepository;
@Transactional
public User createUser(@Valid CreateUserRequest request) {
// 创建用户逻辑
}
}
@RestController
@RequestMapping("/api/users")
publicclass UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUser(id);
}
@PostMapping
public User createUser(@RequestBody @Valid CreateUserRequest request) {
return userService.createUser(request);
}
}
JUnit测试注解示例
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
@ExtendWith(MockitoExtension.class)
public class CalculatorTest {
@BeforeAll
static void setUpAll() {
System.out.println("在所有测试前执行一次");
}
@BeforeEach
void setUp() {
System.out.println("在每个测试前执行");
}
@Test
@DisplayName("测试加法运算")
void testAddition() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
assertEquals(5, result);
}
@Test
@Disabled("暂时跳过这个测试")
void testDisabled() {
// 这个测试会被跳过
}
@AfterEach
void tearDown() {
System.out.println("在每个测试后执行");
}
@AfterAll
static void tearDownAll() {
System.out.println("在所有测试后执行一次");
}
}
Lombok注解示例
Lombok使用注解在编译时生成代码。
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Builder;
@Data// 生成getter、setter、equals、hashCode、toString方法
@NoArgsConstructor// 生成无参构造方法
@AllArgsConstructor// 生成全参构造方法
@Builder// 生成建造者模式
publicclass User {
private Long id;
private String name;
private String email;
privateint age;
// Lombok会自动生成所有这些方法
// public User() {}
// public User(Long id, String name, String email, int age) {}
// public Long getId() { return id; }
// public void setId(Long id) { this.id = id; }
// ... 其他getter和setter
// public boolean equals(Object o) { ... }
// public int hashCode() { ... }
// public String toString() { ... }
// public UserBuilder builder() { ... }
}
注解最佳实践
1. 合理使用@Retention
// 只在编译时需要的注解使用SOURCE
@Retention(RetentionPolicy.SOURCE)
@interface CompileTimeAnnotation {
}
// 需要运行时反射的注解使用RUNTIME
@Retention(RetentionPolicy.RUNTIME)
@interface RuntimeAnnotation {
}
2. 提供有意义的默认值
@interface ConfigAnnotation {
String name() default ""; // 有意义的默认值
int timeout() default 30; // 合理的默认超时时间
String[] tags() default {}; // 空数组作为默认值
}
3. 使用@Documented为库注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyLibraryAnnotation {
String description();
}
4. 避免过度使用注解
// 不推荐:过度使用注解
@MyAnnotation1
@MyAnnotation2
@MyAnnotation3
publicclass OverAnnotatedClass {
@MyAnnotation4
@MyAnnotation5
private String field;
}
// 推荐:合并相关注解或使用配置类
@MyCombinedAnnotation
publicclass ProperlyAnnotatedClass {
@MyFieldAnnotation
private String field;
}
5. 为注解编写文档
/**
* 用于标记需要特殊处理的类
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SpecialProcessing {
/**
* 处理器的名称
* @return 处理器名称
*/
String processorName() default "default";
/**
* 处理优先级,数字越大优先级越高
* @return 优先级
*/
int priority() default 0;
}
总结
Java注解是现代Java开发的重要组成部分,它为代码提供了丰富的元数据支持。通过合理使用注解,可以提高代码的可读性、可维护性和开发效率。
关键要点:
内置注解:@Override、@Deprecated、@SuppressWarnings等
元注解:@Retention、@Target、@Documented、@Inherited等
自定义注解:定义自己的注解来满足特定需求
注解处理:通过反射或编译时处理来使用注解信息
框架应用:Spring、JUnit等框架大量使用注解
最佳实践:合理选择保留策略,提供有意义的默认值
Java注解详解与实战应用
881

被折叠的 条评论
为什么被折叠?



