Skip to content

一、注解简介

注解(Annotation)是 Java 5 引入的一项重要特性,它是一种特殊的"元数据",可以添加到 Java 代码中的类、方法、字段、参数等元素上。

注解本身不会直接影响代码的执行,但可以通过反射机制在运行时获取注解信息,从而实现各种功能。

注解的主要作用在于:

  1. 文档生成:通过注解生成API文档(如 JavaDoc
  2. 编译检查:帮助编译器进行代码检查(如 @Override
  3. 代码分析:通过反射机制在运行时分析代码
  4. 框架配置:在 SpringHibernate 等框架中用于配置
  5. 代码生成:通过注解处理器生成代码

注解的分类有:

  1. 内置注解Java 语言内置的注解
  2. 元注解:用于定义其他注解的注解
  3. 自定义注解:开发者根据需求自定义的注解

二、内置注解

@Override

用于标记方法重写,确保子类方法正确覆盖父类方法。

java
public class Animal {
    public void makeSound() {
        System.out.println("Animal makes sound");
    }
}

public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Dog barks");
    }
}

最佳实践

  • 始终使用@Override注解标记重写方法
  • 这可以帮助编译器检查方法签名是否正确
  • 提高代码的可读性和可维护性

@Deprecated

标记已过时的API,表示该API在未来版本中可能会被移除。

java
public class OldAPI {
    @Deprecated
    public void oldMethod() {
        // 旧方法实现
    }
    
    @Deprecated(since = "1.8", forRemoval = true)
    public void deprecatedMethod() {
        // 将在未来版本中移除的方法
    }
}

注意事项

  • 使用@Deprecated标记的方法仍然可以正常使用
  • 建议使用新版本提供的替代方法
  • 在IDE中会显示警告提示

@SuppressWarnings

用于抑制编译器警告。

java
@SuppressWarnings("unchecked")
public List<String> getList() {
    return new ArrayList(); // 未指定泛型类型
}

@SuppressWarnings({"unchecked", "deprecation"})
public void process() {
    // 同时抑制多个警告
}

常用警告类型:

说明
deprecation使用了不赞成使用的类或方法时的警告
unchecked使用了未经检查的转换时的警告
fallthroughswitch 程序块直接通往下一种情况而没有 break 时的警告
path在类路径、源文件路径等中有不存在的路径时的警告
serial当在可序列化的类上缺少 serialVersionUID 定义时的警告
finally任何 finally 子句不能正常完成时的警告
rawtypes泛型类型未指明
unused引用定义了,但是没有被使用
all关闭以上所有情况的警告

三、元注解

元注解是用于定义其他注解的注解。

@Target

指定注解可以应用的位置。

说明
ElementType.TYPE表示可以作用于类或接口
ElementType.FIELD表示可以作用于成员变量
ElementType.METHOD表示可以作用于方法
ElementType.CONSTRUCTOR表示可以作用于构造方法
ElementType.PARAMETER表示可以作用于方法的参数
java
@Target(ElementType.METHOD)
public @interface MethodAnnotation {
    // 只能用于方法
}

@Target({ElementType.TYPE, ElementType.METHOD})
public @interface TypeAndMethodAnnotation {
    // 可以用于类和方法
}

@Retention

指定注解的生命周期。

说明
RetentionPolicy.SOURCE表示在源代码文件中有效,注解将被编译器丢弃(注解信息仅保留在源码中,源码经编译后注解信息丢失,不再保留到字节码文件中)
RetentionPolicy.CLASS表示在字节码文件中有效,注解在字节码文件中可用,但会被 JVM 丢弃
RetentionPolicy.RUNTIME表示在运行时有效,此时可以通过反射机制来读取注解的信息
java
@Retention(RetentionPolicy.SOURCE)
public @interface SourceAnnotation {
    // 只在源码中保留
}

@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimeAnnotation {
    // 在运行时保留
}

@Documented

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

java
@Documented
public @interface DocumentedAnnotation {
    // 会出现在JavaDoc中
}

@Inherited

指定注解是否可以被继承。

java
@Inherited
public @interface InheritedAnnotation {
    // 子类会继承父类的注解
}

@Repeatable

允许在同一位置重复使用注解。

java
@Repeatable(Authorities.class)
public @interface Authority {
    String value();
}

public @interface Authorities {
    Authority[] value();
}

@Authority("admin")
@Authority("user")
public class User {
    // 可以重复使用注解
}

四、自定义注解

基本语法

java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAnnotation {
    String name() default "default";
    int value() default 0;
    String[] tags() default {};
}

注解参数类型

  1. 基本类型(int, float, boolean等)
  2. String
  3. Class
  4. 枚举
  5. 注解
  6. 以上类型的数组

使用示例

java
@CustomAnnotation(
    name = "test",
    value = 100,
    tags = {"tag1", "tag2"}
)
public class TestClass {
    // 类实现
}

五、注解处理器

运行时处理

java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
    String value() default "";
}

public class TestRunner {
    public static void runTests(Class<?> testClass) {
        for (Method method : testClass.getDeclaredMethods()) {
            if (method.isAnnotationPresent(Test.class)) {
                Test test = method.getAnnotation(Test.class);
                System.out.println("Running test: " + test.value());
                try {
                    method.invoke(testClass.newInstance());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

编译时处理

使用注解处理器(Annotation Processor)在编译时处理注解。

java
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
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.CustomAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class CustomAnnotationProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (TypeElement annotation : annotations) {
            Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(annotation);
            for (Element element : annotatedElements) {
                // 在这里处理每个被注解的元素
                System.out.println("Processing: " + element.toString());
            }
        }
        return true; // 表示注解已经被处理
    }
}

ps.反射机制处理

其实上面的运行时处理,利用的就是反射机制。

但此处,我们特别举例,额外说明下反射机制处理。

java
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value();
}

public class MyClass {
    @MyAnnotation("Hello, World!")
    public void myMethod() {
        // 方法实现
    }
}
java
import java.lang.reflect.Method;

public class AnnotationProcessor {
    public static void main(String[] args) {
        try {
            // 获取 MyClass 类的 Class 对象
            Class<?> clazz = MyClass.class;

            // 获取 myMethod 方法的 Method 对象
            Method method = clazz.getMethod("myMethod");

            // 检查方法上是否存在 MyAnnotation 注解
            if (method.isAnnotationPresent(MyAnnotation.class)) {
                // 获取注解实例
                MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);

                // 输出注解的值
                System.out.println("Annotation value: " + annotation.value());
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

六、实际应用场景

1. 数据验证

java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {
    String message() default "Field cannot be null";
}

public class Validator {
    public static void validate(Object obj) {
        for (Field field : obj.getClass().getDeclaredFields()) {
            if (field.isAnnotationPresent(NotNull.class)) {
                field.setAccessible(true);
                try {
                    if (field.get(obj) == null) {
                        throw new ValidationException(
                            field.getAnnotation(NotNull.class).message()
                        );
                    }
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2. 依赖注入

java
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Inject {
}

public class Container {
    public static <T> T createInstance(Class<T> clazz) {
        try {
            T instance = clazz.newInstance();
            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(Inject.class)) {
                    field.setAccessible(true);
                    field.set(instance, createInstance(field.getType()));
                }
            }
            return instance;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

七、最佳实践

  1. 合理使用注解

    • 不要过度使用注解
    • 保持注解的简单性
    • 为注解提供清晰的文档
  2. 性能考虑

    • 运行时注解会影响性能
    • 考虑使用编译时注解处理
  3. 安全性

    • 注意注解中的敏感信息
    • 避免在注解中存储密码等敏感数据
  4. 可维护性

    • 为注解提供默认值
    • 使用有意义的注解名称
    • 保持注解的一致性

八、总结

注解是 Java 中强大的元编程工具,正确使用注解可以:

  • 提高代码的可读性
  • 减少样板代码
  • 实现更灵活的配置
  • 支持框架的扩展性

掌握注解的使用,对于Java开发者来说是非常重要的技能。