【Java】注解入门

本文深入介绍了Java注解,包括其定义、预置注解如@Override和@Deprecated,以及如何自定义注解和使用元注解如@Retention和@Target。注解用于提供编译时和运行时的元数据,可以通过反射来解析。文中还讨论了注解的生命周期和使用场景,帮助理解JDK源代码中的注解相关代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文作为注解的入门文章,主要向读者介绍注解的基本概念与用法,目的还是能够看懂JDK源代码的注解相关代码。

主要内容有:初步了解一下注解的定义;JDK提供的注解;自己写一个注解;用于自己写注解时用的注解(元注解);JDK在运行时如何解析注解的;


目录

什么是注解

预置注解

自定义注解

定义注解

注解属性

元注解

@Retention

@Target

@Documented

@Inherited

@Repeatable

注解的解析


什么是注解

对注解的理解:

  1. 就像是对人贴上了一个标签。吴小明这个人是个理想主义者、单身狗、技术宅。
  2. 对类或方法贴上一个标签。某方法是Override、Deprecated、SuppressWarnings。

一般概念:说明程序,是给计算机看的。和注释的区别是,注释是用文字描述程序的,给程序员看的。

注解的定义:

  1. 注解(Annotation),也叫元数据。一种代码级别的说明。
  2. 用在类上,方法上,成员变量,构造器,...上对成分进行编译约束标记等操作的。

简单认识:

  1. 它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。
  2. 像一种修饰符一样,它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
  3. 注解是给编译器或JVM看的,编译器或JVM可以根据注解来完成对应的功能。

注解的功能:

  1. 使用注解:@注解名称
  2. 为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。
  3. 注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在java.lang.annotation包中。
  4. 简单来说:注解其实就是代码中的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相对应的处理

注解的本质:

  1. 元注解的格式:public @interface 注解名 { }
  2. 本质是一个接口,该接口默认继承Annotation接口:
    public interface 注解名 extends java.lang.annotation.Annotion {}

     

Java预定义注解:

  1. @Override:检测被该注解标注的方法是否是继承自父类(接口)的;
  2. @Deprecated:该注解标注的内容,表示已过时;
  3. @SuppressWarnings():压制警告,一般传递参数all;

作用分类:

  1. 编写文档:通过代码里标识的注解生成文档【生成文档doc文档】
  2. 代码分析:通过代码里标识的注解对代码进行分析【使用反射】
  3. 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】

预置注解

在 java.lang 包下存在着5个基本的 Annotation

@Deprecated

发出提醒警告,告诉开发者正在调用一个过时的元素比如过时的方法、过时的类、过时的成员变量。

使用示例:

public class Hero {
	@Deprecated
	public void say(){
		System.out.println("Noting has to say!");
	}
	
	public void speak(){
		System.out.println("I have a dream!");
	}
}

@Override

这个大家应该很熟悉了,提示子类要复写父类中被 @Override 修饰的方法。

@SuppressWarnings

阻止警告的意思。之前说过调用被 @Deprecated 注解的方法后,编译器会警告提醒,而有时候开发者会忽略这种警告,他们可以在调用的地方通过 @SuppressWarnings 达到目的。

@SuppressWarnings("deprecation")
public void test1(){
	Hero hero = new Hero();
	hero.say();
	hero.speak();
}

@SafeVarargs

参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。它是在 Java 1.7 的版本中加入的。

@SafeVarargs // Not actually safe!
static void m(List<String>... stringLists) {
	Object[] array = stringLists;
	List<Integer> tmpList = Arrays.asList(42);
	array[0] = tmpList; // Semantically invalid, but compiles without warnings
	String s = stringLists[0].get(0); // Oh no, ClassCastException at runtime!
}

上面的代码中,编译阶段不会报错,但是运行时会抛出 ClassCastException 这个异常,所以它虽然告诉开发者要妥善处理,但是开发者自己还是搞砸了。Java 官方文档说,未来的版本会授权编译器对这种不安全的操作产生错误警告。

自定义注解

定义注解

语法格式:

修饰符 @interface 注解名{

     // 注解属性

}

特性:

  1. 所有的自定义 Annotation 会自动继承 java.lang.Annotation 这一接口,并且不能再去继承别的类或是接口.
  2. 参数成员只能用 public 或默认(default)这两个访问权修饰
  3. 参数成员只能用基本类型 byte, short, char, int, long, float, double, boolean 八种基本数据类型和 String、Enum、Class、annotations 等数据类型,以及这一些类型的数组
  4. 获取类方法和字段的注解信息,必须通过 Java 的反射技术来获取 Annotation 对象,因为你除此之外没有别的获取注解对象的方法
  5. 注解也可以没有定义成员, 不过这样注解就没啥用了。此外,自定义注解需要使用到元注解;
第一个注解实例
@FirstAnnotation //注解可以用在类上
public class Main {
    
    @FirstAnnotation //可以用在方法上
    public static void main(@FirstAnnotation String[] args) { //可以用在参数上
        @FirstAnnotation //可以用在变量上
        int a = 10;
    }
}

@interface FirstAnnotation {
    
}

注解属性

格式:

修饰符 @interface 注解名{

     // 注解属性

}

注解属性

  1. 格式1:数据类型 属性名();
  2. 格式2:数据类型 属性名() default 默认值;

特性:

  1. 注解可以有属性,属性名必须带()
  2. 在用注解的时候,属性必须赋值,除非这个属性有默认值!!
第一个注解属性实例
@MyBook(name="《Java基础》",authors = {"aa","bb","cc"} , price = 99.9 )
public class AnnotationDemo01 {
    @MyBook(name="《MySQL数据库入门到删库跑路》",authors = {"小白","小黑"} ,
            price = 19.9 , address = "北京")
    public static void main(String[] args) {

    }
}

// 自定义一个注解
@interface MyBook{
    String name();
    String[] authors(); // 数组
    double price();
    String address() default "广州";
}

特殊属性:

  1. value属性,如果只有一个value属性的情况下,使用value属性的时候可以省略value名称不写!
  2. 但是如果有多个属性,且多个属性没有默认值,那么value是不能省略的。
特殊属性
public class Main {
    @Annotation01("01") //等价于@Annotation01(value="01")
    public void test01() {
    }

    //但是如果有多个属性,且多个属性没有默认值,那么value是不能省略的。
    @Annotation02(value = "02", str = "02")
    public void test02() {
    }
    
    //但是如果有多个属性,且多个属性没有默认值,那么value是不能省略的。
    @Annotation03("03")
    public void test03() {
    }
}

@interface Annotation01 {
    String value(); //value属性,如果只有一个value属性的情况下,使用value属性的时候可以省略value名称不写!
}
@interface Annotation02 {
    String value();
    String str();
}
@interface Annotation03 {
    String value();
    String str() default "003";
}

元注解

理解元注解:

  1. 元注解也是一张标签,但是它是一张特殊的标签,它的作用和目的就是给其他普通的标签进行解释说明的。
  2. 即,元注解是用在自定义注解上的注解

元标签有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 种。

@Retention

功能:

  1. 申明注解的生命周期
  2. 申明注解的作用范围:编译时,运行时。

它的取值如下:

  1. RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
  2. RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
  3. RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。

使用示例:

@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
	方法体
}

@Target

功能:

  1. 默认的注解可以在类,方法,构造器,成员变量,... 使用。
  2. 约束自定义注解只能在哪些地方使用

@Target 有下面的取值:

  1. ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
  2. ElementType.CONSTRUCTOR 可以给构造方法进行注解
  3. ElementType.FIELD 可以给属性进行注解
  4. ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
  5. ElementType.METHOD 可以给方法进行注解
  6. ElementType.PACKAGE 可以给一个包进行注解
  7. ElementType.PARAMETER 可以给一个方法内的参数进行注解
  8. ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举

实例:

@Target({ElementType.METHOD , ElementType.FIELD}) // 申明只能注解方法和成员变量!
// @Target(ElementType.METHOD ) // 申明只能注解方法
@interface MyTest{
}

@Documented

顾名思义,这个元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去。

@Inherited

Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。


@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}

@Test
public class A {}

public class B extends A {}

注解 Test 被 @Inherited 修饰,之后类 A 被 Test 注解,类 B 继承 A,类 B 也拥有 Test 这个注解。

可以这样理解:

  1. 老子非常有钱,所以人们给他贴了一张标签叫做富豪。
  2. 老子的儿子长大后,只要没有和老子断绝父子关系,虽然别人没有给他贴标签,但是他自然也是富豪。
  3. 老子的孙子长大了,自然也是富豪。
  4. 这就是人们口中戏称的富一代,富二代,富三代。虽然叫法不同,好像好多个标签,但其实事情的本质也就是他们有一张共同的标签,也就是老子身上的那张富豪的标签。

@Repeatable

Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。

什么样的注解会多次应用呢?通常是注解的值可以同时取多个。

举个例子,一个人他既是程序员又是产品经理,同时他还是个画家。


@interface Persons {
	Person[]  value();
}

@Repeatable(Persons.class)
@interface Person{
	String role default "";
}

@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{
	
}

注意上面的代码,@Repeatable 注解了 Person。而 @Repeatable 后面括号中的类相当于一个容器注解。

什么是容器注解呢?就是用来存放其它注解的地方。它本身也是一个注解。

注解的解析

功能:

  1. 对于一个类,我们需要知道这个类到底用了哪些注解。
  2. 并且获取到注解中的值。

AnnotatedElement接口定义了与注解解析相关的方法

所有的类成分Class, Method , Field , Constructor:都实现了AnnotatedElement接口,则他们都拥有解析注解的能力:

  1. Annotation[] getDeclaredAnnotations()  获得当前对象上使用的所有注解,返回注解数组。
  2. T getDeclaredAnnotation(Class<T> annotationClass)  根据注解类型获得对应注解对象
  3. boolean isAnnotationPresent(Class<Annotation> annotationClass)  判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false

 

解析注解数据的原理:

比如注解作用成员方法,则要获得该成员方法对应的Method对象,再来拿上面的注解

比如注解作用在类上,则要该类的Class对象,再来拿上面的注解

比如注解作用在成员变量上,则要获得该成员变量对应的Field对象,再来拿上面的注解

总结

什么是注解:给代码打个标签,说明它是一类什么样的代码;

什么是预置注解,它们都是干什么的:JDK提供的,可以直接使用的注解,对代码进行标记,例如@Override标明这个函数是重写父类中的同名函数的。

自定义注解:自己写一个注解。

元注解:用在自己写注解时用到的注解。

注解的解析:自己编写的注解时怎么解析的,看懂JDK注解的解析。

 

引用、参考资料:

  1. Java核心技术(卷一) 
  2. 黑马程序员
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值