Java中的注释和注解——原理介绍与使用详解

    1. 原理介绍

    什么是注释?

    在Java中,注释(Comments)是我们写代码时添加的文字说明。注释是给开发人员看的,帮助我们或其他人理解代码的结构、目的或逻辑,尤其是在代码复杂的情况下,注释可以提供非常有用的信息。

    注释的种类:

    单行注释:以//开头,用于单行注释。例如:

    // 这是单行注释

    多行注释:以/*开头,以*/结尾,用于跨多行的注释。例如:

    /* 这是多行注释 可以在这里解释复杂的代码 */

    文档注释:以/**开头,以*/结尾,用于生成API文档。例如:

    /** * 这是文档注释 * 可用于生成JavaDoc文档 */

    注释的特点:

    编译器会忽略所有注释内容,也就是说,它们不会被编译到字节码文件(.class文件)中。

    注释只是为了帮助人理解代码,并不会影响程序的执行。

    什么是注解?

    注解(Annotations)是Java代码中的一种元数据。注解看上去和注释很相似,都是一种附加在代码上的信息,但注解其实是给编译器、虚拟机或框架看的,具有更深层次的功能。

    注解的作用:

    提供编译时信息,帮助编译器检查代码,如@Override可以提醒编译器检查方法是否正确覆盖父类方法。

    在运行时供程序使用(如反射读取),如Spring框架中的@Autowired可以帮助注入对象依赖。

    在编译或打包时生成代码或其他文件,如@Entity用于Java Persistence API (JPA)的数据库映射。

    注解的原理:

    注解在Java中本质上是一个接口,所有的注解都自动继承自java.lang.annotation.Annotation接口。

    注解的数据可以保存在编译后的字节码文件中(具体取决于注解的保留策略)。

    在运行时,我们可以通过反射机制读取注解的数据并处理。

    简单来说,注解是给编译器或工具看的,而注释是给人看的,二者在代码中扮演的角色完全不同。

    2. 使用注解

    在Java中,注解有很多实际用途,主要包括:

    提供编译信息:可以帮助编译器进行代码检查或提醒开发人员,例如@Override。表示特定的元数据:用于描述代码结构中的信息,例如@Deprecated表示某个元素已过时。作为配置框架或库的方式:很多框架用注解来替代XML配置,简化了开发工作,例如Spring中的@Autowired注解用于依赖注入。常见的内置注解

    Java内置了几个非常常用的注解:

    @Override:告诉编译器,这个方法覆盖了父类中的方法。@Deprecated:标记某个类、方法或字段已经过时,不推荐使用。@SuppressWarnings:抑制编译器的警告信息。使用区域(Target)分类

    Java允许我们限定注解可以应用在哪些地方,即使用区域,通过@Target元注解来指定。@Target接受一个ElementType的数组,指定注解的适用范围。常见的使用区域如下:

    ElementType.TYPE:类、接口或枚举。如@Deprecated可以标记一个类已过时。ElementType.METHOD:方法。如@Override只能用于方法。ElementType.FIELD:成员变量(字段)。如@Autowired可以标记字段,以实现依赖注入。ElementType.PARAMETER:方法参数。如Spring的@RequestParam,用于标记HTTP请求参数。ElementType.CONSTRUCTOR:构造方法。ElementType.LOCAL_VARIABLE:局部变量,用于在方法内部标记局部变量。ElementType.ANNOTATION_TYPE:可以用于注解定义,定义一个作用于其他注解的注解。ElementType.PACKAGE:包,用于标记包级别的信息。示例: 假设我们创建了一个只能用于方法的注解:

    import java.lang.annotation.ElementType;

    import java.lang.annotation.Target;

    @Target(ElementType.METHOD)

    public @interface MethodOnlyAnnotation {

    }

    这样,MethodOnlyAnnotation注解只能在方法上使用,如果放在类或字段上会报编译错误。

    作用时间(Retention)分类

    注解的作用时间(生命周期)由@Retention来控制,分为以下几种:

    RetentionPolicy.SOURCE:源代码级别,注解只在源代码中存在,编译后即被丢弃,如@SuppressWarnings。RetentionPolicy.CLASS:编译级别,注解在字节码中存在,但运行时会被JVM丢弃,不可通过反射读取。RetentionPolicy.RUNTIME:运行时级别,注解一直保留到运行时,可以通过反射获取。这类注解在框架中很常用。示例: 下面是一个在运行时保留的注解定义:

    import java.lang.annotation.Retention;

    import java.lang.annotation.RetentionPolicy;

    @Retention(RetentionPolicy.RUNTIME)

    public @interface RuntimeAnnotation {

    }

    这个注解在运行时可通过反射读取,广泛用于需要运行时动态行为的场景,如Spring等框架。

    3. 定义注解

    一些常用的、非自定义的标准注解,JVM会自动处理它们的逻辑,开发者不需要额外编写代码来处理。这类注解主要包括@Override、@Deprecated、@SuppressWarnings等,它们在编译时或运行时触发特定行为。自定义注解允许开发者根据需要创建新的注解。定义注解时,我们可以指定它的属性、适用区域和保留策略。

    定义注解的步骤

    使用@interface关键字定义一个新的注解。使用@Target指定注解可以应用的代码元素,如类、方法、字段等。使用@Retention指定注解的保留策略,如源代码、字节码或运行时。在注解中定义属性。属性类似于方法,通常用来传递额外的配置信息。示例: 定义一个包含默认值的自定义注解@Action,仅限于方法使用,并在运行时可读取:

    import java.lang.annotation.ElementType;

    import java.lang.annotation.Retention;

    import java.lang.annotation.RetentionPolicy;

    import java.lang.annotation.Target;

    @Target(ElementType.METHOD)

    // 只能应用在方法上

    @Retention(RetentionPolicy.RUNTIME)

    // 运行时保留

    public @interface Action {

    String description() default "No description";

    // 默认属性

    }

    使用时可以自定义description的值:

    @Action(description = "This is a custom action")

    public void customActionMethod() {

    // 方法实现

    }

    4. 处理注解

    定义完注解后,我们可以在代码运行时通过反射来处理它们。注解处理过程如下:

    注解处理的步骤

    获取类的Class对象:通过Class clazz = SomeClass.class;来获取类的字节码信息。检查注解的存在:用isAnnotationPresent()判断某个注解是否存在。获取注解实例并提取属性:使用getAnnotation()获取注解实例,提取其属性值。示例: 假设我们有一个使用了@Action注解的方法,我们可以在运行时通过反射读取并处理它:

    先定义注解@Action:

    @Target(ElementType.METHOD)

    @Retention(RetentionPolicy.RUNTIME)

    public @interface Action {

    String description();

    }

    使用注解:

    public class ExampleClass {

    @Action(description = "This is a test action")

    public void someMethod() {

    System.out.println("Executing someMethod");

    }

    }

    编写反射代码处理注解:

    import java.lang.reflect.Method;

    public class AnnotationProcessor {

    public static void main(String[] args) throws Exception {

    Class clazz = ExampleClass.class;

    for (Method method : clazz.getDeclaredMethods()) {

    if (method.isAnnotationPresent(Action.class)) {

    Action action = method.getAnnotation(Action.class);

    System.out.println("Method: " + method.getName());

    System.out.println("Action description: " + action.description());

    }

    }

    }

    }

    在这个例子中,@Action注解的description值在运行时可以通过反射提取并使用。