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
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值在运行时可以通过反射提取并使用。