首页 > 编程语言 >Java(1)——注解

Java(1)——注解

时间:2024-04-06 12:56:32浏览次数:17  
标签:Java void class 类型 修饰 注解 public

常用注解

Java注解从Java1.5开始引入,注解就是代码中的特殊标记,告诉类要如何运作。注解的典型应用是:通过反射技术获得类中的注解,来决定如何运行类。

注解可以标记在类、属性、方法、变量等,并且一个地方可以同时标记多个注解。

先从一个简单的注解开始说起。

class SuperClass {
    public void test() {}
}

class SubClass extends SuperClass{
    @Override
    public void test() {}
}

@Override这个注解表示SubClass中的test()方法是覆写父类SuperClass中的test方法,阿里开发手册要求,凡是覆写的方法,强制加上@Override注解。可以防止书写错误,类似getObject()与 get0bject()的问题,一个是字母的 O,一个是数字的 0,加@Override 可以准确判断是否覆盖成功。以及接口或是父类的方法签名修改后,能够及时报错。

Java自带的注解中,常用的还有两个:@Deprecated、@SuppressWarnings。

注解作用
@Deprecated 写在方法上,表示此方法是过时方法,不建议使用
@SuppressWarnings 表示抑制指定警告

当一个方法被@Deprecated注解修饰,调用此方法时编译器会有相应提示。

class DeprecatedDemo {
    // 表示此方法为过时方法,不建议使用
    @Deprecated
    public void deprecate() {}
}

public class AnnotationDemo {
    public static void main(String[] args) {
        DeprecatedDemo deprecatedDemo = new DeprecatedDemo();
        // 此处提示deprecate()为过时方法,并显示删除线
        deprecatedDemo.deprecate();
    }
}

@SuppressWarnings会抑制编译器警告

public static void main(String[] args) {
    // 此处编译器警告:Variable 'str' is never used
    String str = "";
}
public static void main(String[] args) {
    // 加上@SuppressWarnings,警告消失
    @SuppressWarnings("unused")
    String str = "";
}

@SuppressWarnings可以同时抑制多类型警告,@SuppressWarnings({"unused", "unchecked"})

@SuppressWarnings也可以抑制所有类型的警告,@SuppressWarnings("all")

元注解

进入@Override注解源代码,可以看到如下代码:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

有另外两个注解@Target与@Retention标记了@Override,它俩被称为元注解。Java中提供了四个对注解类型记性注解的注解类,它们被叫做元注解。

元注解说明
@Target 描述被修饰的注解的使用范围
@Retention 描述被修饰的注解的生存期
@Documented 在使用javadoc工具为类生成帮助文档时,被修饰的注解会被保留
@Inherited 被修饰的注解可以被子类获取

 

@Target

注解可以修饰包、类、方法、参数等,@Target限定了被修饰的注解的作用范围。@Target的取值范围为枚举值ElementType。

public enum ElementType {
    // 类,接口(包括注解),枚举
    TYPE,
    // 字段(成员变量),包括枚举实例
    FIELD,
    // 方法
    METHOD,
    // 方法参数
    PARAMETER,
    // 构造方法
    CONSTRUCTOR,
    // 局部变量
    LOCAL_VARIABLE,
    // 注解
    ANNOTATION_TYPE,
    // 包
    PACKAGE,
    // 类型参数,jdk8新增
    TYPE_PARAMETER,
    // 使用类型,jdk8新增
    TYPE_USE
}

例如,被@Target(ElementType.METHOD)修饰的注解只能用于方法上,用于其他地方会报错。

// @AnnotationMethod只能用于修饰方法
@Target(ElementType.METHOD)
@interface AnnotationMethod {}

// 报错,不可用于类型,'@AnnotationMethod' not applicable to type
@AnnotationMethod
public class AnnotationDemo {
    @AnnotationMethod
    public static void main(String[] args) {}
}

ElementType.TYPE_PARAMETER修饰的注解用于类型参数。

// 用于类型参数
@Target(ElementType.TYPE_PARAMETER)
@interface AnnotationTypePra {}

// 泛型类,声明泛型T,此处可以使用@AnnotationTypePra
class Gen<@AnnotationTypePra T> {
    // 此处使用报错,'@AnnotationTypePra' not applicable to field
    // 此时T已被视为一个普通的类型,而不是类型参数
    private @AnnotationTypePra T field;

    // 此处使用报错,'@AnnotationTypePra' not applicable to parameter
    // 此时T已被视为一个普通的类型,而不是类型参数
    public void set(@AnnotationTypePra T var){
        field = var;
    }

    // 泛型方法,<M>声明了泛型M,第一个@AnnotationTypePra可以使用
    // 第二个@AnnotationTypePra报错,'@AnnotationTypePra' not applicable to parameter
    // 此时M已被视为一个普通的类型,而不是类型参数
    public <@AnnotationTypePra M> void show(@AnnotationTypePra M var){
        System.out.println(var);
    }
}

ElementType.TYPE_PARAMETER修饰的注解可以使用的地方,ElementType.TYPE_USE修饰的注解都可以使用,并且可以用在任何使用类型的地方,void除外。

// 用于所有使用类型的地方
@Target(ElementType.TYPE_USE)
@interface AnnotationTypeUse {}

// 用在类上
@AnnotationTypeUse
public class AnnotationDemo {
    // 用于字段类型
    private @AnnotationTypeUse String str;
    
    // 第一个@AnnotationTypeUse报错,'void' type may not be annotated
    // 第二个@AnnotationTypeUse用于方法参数,可以使用
    public static @AnnotationTypeUse void main(@AnnotationTypeUse String[] args) {}
    
    // 用于返回值类型
    public @AnnotationTypeUse Integer getInt() {
        return 1;
    }
}

// 用于类型参数
class Gen<@AnnotationTypeUse T> {
    // 用于字段类型
    private @AnnotationTypeUse T field;
    
    // 用于方法参数
    public void set(@AnnotationTypeUse T var){
        field = var;
    }

    // 第一个@AnnotationTypeUse用于类型参数
    // 第二个@AnnotationTypeUse用于方法参数
    public <@AnnotationTypeUse M> void show(@AnnotationTypeUse M var){
        System.out.println(var);
    }
}
  • 若注解未被@Target修饰,那这个注解的作用范围是,除了类型参数(ElementType.TYPE_PARAMETER)以外的,其他所有ElementType枚举值的作用范围。
  • 若注解想要有多个作用范围,可以在@Target()中写多个值,用{}包裹并用逗号隔开
@Target({ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD})
  • 若注解被@Target({})修饰,那么该注解不可用于直接修饰任何元素,只能作为其他复杂注解的成员类型。
@Target({})
@interface AnnotationEmpty {}

@interface AnnotationTest {
    // 作为其他注解的成员类型
    AnnotationEmpty annotationEmpty();
}

@Retention

注解的生存期分为:只存在于源码中,保存到.class文件但是不被VM加载,运行期中可以获取。@Retention用来约束注解的生存期,取值范围为枚举值RetentionPolicy。

public enum RetentionPolicy {
    // 该类型注解只存于源码中,将被编译器丢弃
    SOURCE,
    // 该类型注解会被编译器保存到.class文件,但是不会被VM加载到内存中
    // 这是注解的默认生存期
    CLASS,
    // 该类型注解会被编译器保存到.class文件,并且会被VM加载到内存中,从而可以通过反射获取注解的相关信息
    RUNTIME
}

下面用代码演示@Retention,首先定义三个不同生存期的注解。

// 该类型注解只存在于源码中
@Retention(RetentionPolicy.SOURCE)
@interface SourceAnnotation {}

// 该类型注解会被保存到.class文件,但是不会被VM加载到内存
@Retention(RetentionPolicy.CLASS)
@interface ClassAnnotation {}

// 该类型注解会被VM加载到内存中,运行期可通过反射获取
@Retention(RetentionPolicy.RUNTIME)
@interface RuntimeAnnotation {}

写三个被不同生存期注解修饰的方法、

package Annotation;

public class AnnotationDemo {

    @SourceAnnotation
    public void SourceMethod() {}

    @ClassAnnotation
    public void ClassMethod() {}

    @RuntimeAnnotation
    public void RuntimeMethod() {}
}

cmd中执行javac *.java,然后执行javap -verbose AnnotationDemo.class查看AnnotationDemo的字节码文件,其中部分字节码如下所示:

{
  public Annotation.AnnotationDemo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public void SourceMethod();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 8: 0

  public void ClassMethod();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 13: 0
    RuntimeInvisibleAnnotations:
      0: #11()

  public void RuntimeMethod();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 18: 0
    RuntimeVisibleAnnotations:
      0: #14()
}

通过上述字节码可以看到:

文件中有四个方法,分别是默认无参构造方法+3个代码中定义的方法

  • class文件并没有保存SourceMethod()方法的相关注解信息
  • ClassMethod()方法使用RuntimeInvisibleAnnotations描述注解信息,RuntimeMethod()方法使用RuntimeVisibleAnnotations描述注解信息。

在运行时查看注解信息

public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        // 获取AnnotationDemo的Class对象
        Class<?> clazz = Class.forName("Annotation.AnnotationDemo");
        // 通过Class对象获取所有public方法,包括继承的
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            // 通过Method对象获取修饰此方法的注解
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                System.out.println(method.getName() + "方法上的注解为:" + annotation);
            }
        }
    }
}

运行结果:

RuntimeMethod方法上的注解为:@Annotation.RuntimeAnnotation()

只有RuntimeMethod方法上的注解信息在运行期间被反射获取到。

@Documented

@Documented注解作用:在使用javadoc为类生成帮助文档时,是否保留此注解信息。

// 此注解未被@Documented修饰,不会在帮助文档中保留此注解信息
@interface WithoutDocAnnotation {}

// 此注解被@Documented修饰,会在帮助文档中保留此注解信息
@Documented
@interface DocumentedAnnotation {}

public class Test {
    @DocumentedAnnotation
    public void documentedTest() {

    }

    @WithoutDocAnnotation
    public void withoutDocumentedTest() {

    }
}

在cmd窗口执行javac Test.java,然后再执行javadoc -d doc Test.java,会在新建的doc文件夹中生成帮助文档,使用浏览器打开其中的index.html文件,可以看到以下内容。

 被@Documented修饰的注解信息保留到了帮助文档中,未被@Documented修饰的注解信息则未保留下来。

@Inherited

被@Inherited修饰的注解会具有继承性,也就是在父类上使用的注解,可以被子类通过Class对象的getAnnotations()方法获取。

// @Inherited修饰注解,则此类型注解可以被子类获取
@Inherited
// 注意添加RetentionPolicy.RUNTIME,否则默认只存在于.class文件,运行期间无法通过反射获取
@Retention(RetentionPolicy.RUNTIME)
@interface InheritedAnnotation {}

@Retention(RetentionPolicy.RUNTIME)
@interface WithoutInheritedAnnotation {}

@InheritedAnnotation
class SuperClassA {}

class SubClassA extends SuperClassA{}

@WithoutInheritedAnnotation
class SuperClassB {}

class SubClassB extends SuperClassB{}

public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        // 获取SubClassA的Class对象
        Class<?> clazzA = Class.forName("Annotation.SubClassA");
        // 通过Class对象获取修饰类的所有注解
        Annotation[] annotationsA = clazzA.getAnnotations();
        for (Annotation annotation : annotationsA) {
            System.out.println(annotation);
        }

        Class<?> clazzB = Class.forName("Annotation.SubClassB");
        Annotation[] annotationsB = clazzB.getAnnotations();
        for (Annotation annotation : annotationsB) {
            System.out.println(annotation);
        }
    }
}

运行结果:

@Annotation.InheritedAnnotation()

自定义注解

上面在介绍元注解的时候,用到了自定义注解。

@Documented
@Inherited
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationDemo {
    String name() default "default name";
}

自定义注解@AnnotationDemo,在帮助文档中会保留此注解信息,可以被子类通过反射获取(需要注解的生存期为Runtime),只能用来修饰类型(类,接口,包括注解,枚举)以及方法,生存期到运行期间。

在自定义注解@AnnotationDemo中,声明了一个String类型的name元素,默认值为"default name"。注意,注解中元素的声明需要使用类似于方法的方式,同时可选择使用default提供默认值。@AnnotationDemo的使用方式如下:

@AnnotationDemo(name = "annotation test")
class AnnotationTest {

}

在注解中可以使用的元素类型除了刚才的String,总共有下列几种:

所有的基本类型(boolean, byte, char, short, int, long, float, double)

  • String
  • Class
  • enum
  • Annotation
  • 上述类型的数组

使用其他数据类型,会编译报错。声明注解元素时,可以使用基本类型,但是不能使用包装类型。注解可以作为注解元素的类型。

enum WeekEnum {
    WEEKDAY,
    WEEKEND
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationDemo {
    String name() default "defaultName";
}

@interface AnnotationElementDemo {
    // 基本类型boolean作为注解内元素的类型
    boolean support() default false;
    // String作为注解内元素的类型
    String name() default "";
    // Class对象作为注解内元素的类型
    Class<?> clazz() default Void.class;
    // 枚举作为注解内元素的类型
    WeekEnum week() default WeekEnum.WEEKDAY;
    // 注解作为注解内元素的类型
    AnnotationDemo annotationDemo() default @AnnotationDemo(name = "defaultName01");
    // 数组作为注解内元素的类型
    int[] count();
}

自定义一个简单注解

public @interface AnnotationTest {
    String name() default "";
}

编译后反编译,获取到反编译的代码

public interface Annotation.AnnotationTest extends java.lang.annotation.Annotation {
  public abstract java.lang.String name();
}

 

可以看到,注解在编译后,会自动继承java.lang.annotation.Annotation接口,但我们无法通过在代码中直接继承此接口来实现注解功能。

赋值

使用注解时,对注解内元素进行赋值,方式是key=value。

public @interface AnnotationTest {
    String name() default "";
}

@AnnotationTest(name = "test01")
class TestClass {}

注解中有名为value的元素,当使用该注解时,若只需要给value进行赋值时,可以不使用key=value形式,只写value即可。当给value及其他元素赋值时,则必须需要key=value。

@Target(ElementType.METHOD)
public @interface AnnotationTest {
    int value() default 0;
    String name() default "";
}

class TestClass {
    // 只给value赋值,可以使用快捷方式
    @AnnotationTest(10)
    public void method01() {}

    // 给多个元素赋值,必须使用key=value
    @AnnotationTest(value = 20, name = "name01")
    public void method02() {}
}

注解与反射
为了能在运行时获取到注解相关的信息,Java在java.lang.reflect反射包下新增了AnnotatedElement接口,它主要用来表示目前在VM中运行的程序中已使用注解的元素,通过该接口提供的方法,可以利用反射技术读取注解的信息,反射中常用的Class类、Constructor类、Method类与Field类等都实现了这个接口,在运行期间都可以通过反射获取修饰他们的注解信息。

AnnotatedElement接口中常用的几个方法:

 

代码演示如下:

package Annotation;

import java.lang.annotation.*;
import java.util.Arrays;

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

@Retention(RetentionPolicy.RUNTIME)
@interface AnnotationB {}

@AnnotationA
class SuperDemo {}

@AnnotationB
class SubDemo extends SuperDemo{}

public class Test {
    public static void main(String[] args) {
        Class<?> clazz = SubDemo.class;
        // 是否被指定类型的注解修饰
        boolean annotationPresent = clazz.isAnnotationPresent(AnnotationB.class);
        System.out.println("SubDemo被AnnotationB类型注解修饰:" + annotationPresent);
        // 获取指定类型的注解,包括父类的
        AnnotationA annotation = clazz.getAnnotation(AnnotationA.class);
        System.out.println(annotation);
        // 获取全部注解,包括父类的
        Annotation[] annotations = clazz.getAnnotations();
        System.out.println(Arrays.toString(annotations));
        // 获取指定类型的注解,不包括父类的
        AnnotationA declaredAnnotation = clazz.getDeclaredAnnotation(AnnotationA.class);
        System.out.println(declaredAnnotation);
        // 获取全部注解,不包括父类的
        Annotation[] declaredAnnotations = clazz.getDeclaredAnnotations();
        System.out.println(Arrays.toString(declaredAnnotations));
    }
}

运行结果:

SubDemo被AnnotationB类型注解修饰:true
@Annotation.AnnotationA()
[@Annotation.AnnotationA(), @Annotation.AnnotationB()]
null
[@Annotation.AnnotationB()]

在运行期间,可以通过上述方法拿到相关的注解以及注解的相关值,从而对不同注解修饰的方法或类进行不同的处理。

@Retention(RetentionPolicy.RUNTIME)
@interface AnnotationValue {
    String name() default "name01";
    int count() default 0;
}

@AnnotationValue(name = "ClassTest01", count = 10)
class TestValue {
    @AnnotationValue(name = "MethodShow", count = 20)
    public void show() {}
}

public class Test {
    public static void main(String[] args) throws NoSuchMethodException {
        // 获取Class对象
        Class<TestValue> testValueClass = TestValue.class;
        // 判断是否被相关注解修饰
        boolean annotationPresent = testValueClass.isAnnotationPresent(AnnotationValue.class);
        if (annotationPresent) {
            // 获取相关注解
            AnnotationValue annotation = testValueClass.getAnnotation(AnnotationValue.class);
            // 输出相关注解的值
            System.out.println("name:" + annotation.name() + ". count:" + annotation.count());
        }
        // 获取Method对象
        Method show = testValueClass.getMethod("show");
        // 判断是否被相关注解修饰
        boolean annotationPresent1 = show.isAnnotationPresent(AnnotationValue.class);
        if (annotationPresent1) {
            // 获取相关注解
            AnnotationValue annotation = show.getAnnotation(AnnotationValue.class);
            // 输出相关注解的值
            System.out.println("name:" + annotation.name() + ". count:" + annotation.count());
        }
    }
}

运行结果:

name:ClassTest01. count:10
name:MethodShow. count:20

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/weixin_46927507/article/details/115665091

标签:Java,void,class,类型,修饰,注解,public
From: https://www.cnblogs.com/isme-zjh/p/18117330

相关文章

  • 2024年150道高频Java面试题(十八)
    35.List、Set、Map之间的区别是什么?List、Set和Map是Java中CollectionFramework中的三大接口,它们用于存储集合数据,但是它们之间有着明显的区别:List(列表):List是一个有序集合,它允许元素重复。它维护了元素插入的顺序,可以通过索引(基于0的整数)访问。List接......
  • java计算机毕业设计(附源码)榆林学院学术成果申报与管理(ssm+mysql+maven+LW文档)
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义选题背景:榆林学院,位于陕西省的一所综合性高等学府,承载着培养高素质人才和推动地区科教发展的重要使命。随着国家对高等教育质量的持续关注和科研创新能力的不断提......
  • java计算机毕业设计(附源码)羽毛球馆场地管理系统(ssm+mysql+maven+LW文档)
    本系统(程序+源码)带文档lw万字以上  文末可领取本课题的JAVA源码参考系统程序文件列表系统的选题背景和意义选题背景:在当今社会,随着人们生活水平的提高和对健康生活方式的追求,体育运动逐渐成为人们日常生活的重要组成部分。羽毛球作为一项深受大众喜爱的运动项目,在全球范......
  • java5年经验要求是什么
    对于具有5年经验的Java开发人员,通常会有更高级的技术要求和期望。以下是一些常见的技术和能力要求:1.**深入理解Java语言和生态系统**:对Java语言的高级特性、JVM原理、Java生态系统(如Spring框架、Hibernate等)有深入理解,能够解决复杂的技术挑战。2.**系统架构设计和优化**:具......
  • SSM(Spring+SpringMVC+MyBatis)常用注解大全
    提示使用浏览器查找系统也快速查找,可避免漏看和疲劳Win:Ctrl+FMac:Command+F@Bean功能:用于在配置类中声明一个bean。用法:@ConfigurationpublicclassAppConfig{@BeanpublicMyServicemyService(){returnnewMyServiceImpl();}}@Com......
  • 在 JavaScript 中,exec() 和 match() 区别
    在JavaScript中,exec() 和 match() 都是与正则表达式相关的常用方法,但它们的使用方式和返回的结果有所不同。exec() 方法exec() 是 RegExp 对象的一个方法,用于在字符串中执行一次正则表达式匹配。它的语法是:regexp.exec(string)其中 regexp 是一个正则表达式对象,s......
  • Golang和Java的对决:从设计理念到工具链的全面比较
    文章目录使用率排名Golang和Java设计理念语法和类型系统并发处理资源消耗生态系统和工具链结语使用率排名据最新的2024年3月Tiobe编程语言排行榜,目前Golang的使用率排名为第8呈上升趋势,Java的使用率排名为第4呈下降趋势2024年3月2023年3月排名变化编程语言......
  • 【Java】jdk1.8 Java代理模式,Jdk动态代理讲解(非常详细,附带class文件)
      ......
  • 华为OD机试 - 猴子爬山(Java & JS & Python & C & C++)
    须知哈喽,本题库完全免费,收费是为了防止被爬,大家订阅专栏后可以私信联系退款。感谢支持文章目录须知题目描述输入描述输出描述用例解题思路:Java代码:JS代码:Python代码:C代码:C++代码:题目描述一天一只顽猴想去从山脚爬到山顶,途中经过一个有个N个台......
  • 华为OD机试 - 火星文计算(Java & JS & Python & C & C++)
    须知哈喽,本题库完全免费,收费是为了防止被爬,大家订阅专栏后可以私信联系退款。感谢支持文章目录须知题目描述输入描述输出描述用例解题思路:Java代码:JS代码:Python代码:C代码:C++代码:题目描述已知火星人使用的运算符为#、$,其与地球人的等价公式如下......