Java注解(Annotation)学习

2023-05-16

  (本文内容来源于疯狂Java讲义,感谢。
注解(Annotation)
  Annotation是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用注解,程序开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。代码分析工具,开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。
  Annotation提供了一种为程序元素设置元数据的方法,从某些方面来看,Annotation就像修饰符一样,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被存储在Annotation的“name=value”对中。
  Annotation能被用来为程序元素(类、方法、成员变量等)设置元数据。值得指出的是,Annotation不影响程序代码的执行,无论增加、删除Annotation,代码都始终如一地执行。如果希望让程序中的Annotation在运行时起一定的作用,只有通过某种配套的工具对Annotation中的信息进行访问和处理,访问和处理Annotation的工具统称APT(Annotation Processing Tool)。

1.基本Annotation
  Annotation必须使用工具类来处理,工具负责提取Annotation里包含的元数据,工具还会根据这些元数据增加额外的功能。在系统学习新的Annotation语法之前,先看一下Java提供的5个基本Annotation的用法——使用Annotation时要在其前面增加@符号,并把该Annotation当成一个修饰符使用,用于修饰它支持的程序元素。
@Override
@Deprecated
@SuppressWarnings
@SafaVarargs
@FunctionalInterface
2.暂无
3.自定义Annotation
3.1定义Annotation
  定义新的Annotation类型使用@interface关键字(在原有的interface关键字前增加@符号)定义一个新的Annotation类型与定义一个接口非常像,如下代码可定义一个简单的Annotation类型。

// 定义一个简单的Annotation类型
public @interface Test {
}

  定义了该Annotation之后,就可以在程序的任何地方使用该Annotation,使用Annotation的语法非常类似于public 、final这样的修饰符,通常可用于修饰程序中的类、方法、变量、接口等定义。通常会把Annotation放在所有修饰符之前,而且由于使用Annotation时可能还需要为成员变量指定值,因而Annotation的长度可能较长,所以通常把Annotation另放一行,如下所示。

// 使用@Test修饰类定义
@Test
public class MyClass{
}

  在默认情况下,Annotation可用于修饰任何程序元素,包括类、接口、方法等,如下程序使用@Test来修饰方法。

public class MyClass {
	// 使用@Test Annotation修饰方法
	@Test
	public void info(){
	}
}

  Annotation不仅可以是这种简单的Annotation,还可以带成员变量,Annotation的成员变量在Annotation定义中以无形参的方法形式来声明,其方法名和返回值定义了该成员变量的名字和类型,如下可以定义一个有成员变量的Annotation。

public @interface MyTag {
	// 定义带2个成员变量的Annotation
	// Annotation中的成员变量以方法的形式来定义
	String name();
	int age();
}

  上面定义Annotation的代码与定义接口的语法非常像,只是MyTag使用@interface关键字来定义,接口使用interface来定义。
一旦在Annotation里定义了成员变量之后,使用该Annotation时就应该为该Annotation的成员变量指定值,如下所示。

public class Test {
	//使用带成员变量的Annotation时,需要为成员变量赋值
	@MyTag(name="赵一伤", age=38)
	public void info(){
	}
}

  也可以在定义Annotation的成员变量时为其指定初始值(默认值),指定成员变量的初始值可使用default关键字。如下代码定义了@MyTag Annotation,该Annotation里包含了两个成员变量:name和age,这两个成员变量使用default指定了初始值。

public @interface MyTag{
	// 定义了两个成员变量的Annotation
	// 使用default为两个成员变量指定初始值
	String name() default "Obama";
	int age() default 45;
}

  如果为Annotation的成员变量指定了默认值,使用该Annotation时则可以不为这些成员变量指定值而是直接使用默认值。

public class Test{
	// 使用带成员变量的Annotation
	// 因为它的成员变量有默认值,所以可以不为它的成员变量指定值
	@MyTag
	public void into(){}
}

  当然也可以在使用MyTag Annotation时为成员变量指定值,如果为MyTag的成员变量指定了值,则默认值不会起作用。
  根据Annotation是否可以包含成员变量,可以把Annotation分为如下两类。
标记Annotation: 没有定义成员变量的Annotation类型被称为标记。这种Annotation仅利用自身的存在与否来提供信息,如前面介绍的@Override、@Test等Annotation。
元数据Annotation: 包含成员变量的Annotation,因为它们可以接受更多的元数据,所以也被称为元数据Annotation。

3.2提取Annotation信息
  使用Annotation修饰了类、方法、成员变量等成员之后,这些Annotation不会自己生效,必须由开发者提供相应的工具来提取并处理Annotation信息。
Java使用Annotation接口来代表程序元素前面的注解,该接口是所有注解的父接口。Java5在java.lang.reflect包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素。该接口主要有如下几个实现类。
Class: 类定义
Constructor: 构造器定义
Field: 类的成员变量定义
Method: 类的方法定义
Package: 类的包定义
  java.lang.reflect包下主要包含一些实现反射功能的工具类,从Java5开始,java.lang.reflect包所提供的反射API增加了读取运行时Annotation的能力。只有定义Annotation时使用了@Retention(RetentionPolicy.RUNTIME)修饰,该Annotation才会在运行时可见,JVM才会在装载*.class文件时读取保存在class文件中的Annotation。
  AnnotatedElement接口是所有程序元素(如Class、Method、Constructor等)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象(如Class、Method、Constructor等)之后,程序就可以调用该对象的如下几个方法来访问Annotation信息。

/*1*/<A extends Annotation> A getAnnotation(Class<A> annotationClass):
/*2*/<A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass):
/*3*/Annotation[] getAnnotations():
/*4*/Annotation[] getDeclaredAnnotations():
/*5*/boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):
/*6*/<A extends Annotation>A[] getAnnotationsByType(Class<A> annotationClass):
/*7*/<A extends Annotation>A[] getDeclaredAnnotationsByType(Class<A> annotationClass):

  下面的程序片段用于获取Test类的info方法里的所有注解,并将这些注解打印出来。

        Annotation[] aArr = Class.forName("com.bu.ant.Test").getMethod("info").getAnnotations();
        System.out.println(aArr.length);
        for(Annotation an : aArr){
            System.out.println(an);
        }

  如果需要获取某个注解里的元数据,则可以将注解强制类型转换为所需的注解类型,然后通过注解对象的抽象方法来访问这些元数据。

        MyClass mc = new MyClass();
        Annotation[] annotations = mc.getClass().getMethod("info").getAnnotations();
        for (Annotation tag : annotations){
            if (tag instanceof MyClass){
                System.out.println("tag is: "+tag);
                System.out.println("tag.name()"+((MyTag)tag).name());
                System.out.println("tag.age()"+((MyTag)tag).age());
            }
        }

3.3使用Annotation的示例
  下面是两个使用Annotation的例子,第一个是标记型Annotation,第二个是元数据Annotation。
第一个

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
// 定义一个标记注解,不包含任何成员变量,即不可传入元数据
public @interface Testable {
}

此程序定义了一个@Testable Annotation,定义该Annotation时使用了@Retention和@Target两个JDK的元注解,其中@Retention注解指定Testable注解可以保留到运行时(JVM可以提取到该Annotation的信息),而@Target注解指定@Testable只能修饰方法。
测试类如下:

public class MyTest {
    @Testable
    public static void m1(){}
    public static void m2(){}
    @Testable
    public static void m3(){
        throw new IllegalArgumentException("参数出错了");
    }
    public static void m4(){}
    @Testable
    public static void m5(){}
    public static void m6(){}
    @Testable
    public static void m7(){
        throw new RuntimeException("程序业务出现异常!");
    }
    public static void m8(){}
}

  如前所述,仅仅使用注解来标记程序元素对程序是不会有任何影响的,这也是Java注解的一条重要原则。为了让程序中的这些注解起作用,接下来必须为这些注解提供一个注解处理工具。
  处理工具类如下:

public class ProcessorTest {
    public static void process(String clazz) throws ClassNotFoundException{
        int passed=0;
        int failed=0;

        // 遍历clazz对应的类里面的所有方法
        for (Method m:Class.forName(clazz).getMethods()){
            if (m.isAnnotationPresent(Testable.class)) {
                try {
                    m.invoke(null);
                    passed++;
                }  catch (Exception e) {
                    System.out.println("方法"+m+"运行失败,异常:"+e.getCause());
                    failed++;
                    //e.printStackTrace();
                }
            }
        }
        // 统计测试结果
        System.out.println("共运行了:"+(passed+failed)+"个方法,其中:\n"+"失败了:"+failed+"个,\n"
        +"成功了:"+passed+"个!");
    }
}

  ProcessTest类里只包含一个process(String clazz)方法,该方法可接收一个字符串参数,该方法将会分析clazz参数所代表的类,并运行该类里使用@Testable修饰的方法。

    void fun3() throws ClassNotFoundException {
        ProcessorTest.process("com.bu.ant.MyTest");
    }

执行结果

方法public static void com.bu.ant.MyTest.m3()运行失败,异常:java.lang.IllegalArgumentException: 参数出错了
方法public static void com.bu.ant.MyTest.m7()运行失败,异常:java.lang.RuntimeException: 程序业务出现异常!
共运行了:4个方法,其中:
失败了:2个,
成功了:2个!

  从结果可以看出,程序中的@Testable起作用了,MyTest类里面以@Testable注解修饰的方法都被测试了。

第二个(元数据注解)
  通过使用Annotation来简化时间编程,在传统的事件编程中总是需要通过addActionListener()方法来为事件源绑定事件监听器,下面的示例则通过@ActionListenerFor来为程序中的按钮绑定事件监听器。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ActionListenerFor {
    // 定义一个成员变量,用于设置元数据
    // 该listener成员变量用于保存监听器实现类
    Class<? extends ActionListener> listener();
}

  定义了这个@ActionListenerFor之后,使用该注解时需要指定一个listener成员变量,该成员变量用于指定监听器的实现类。下面程序使用@ActionListenerFor注解来为2个按钮绑定事件监听器。

public class AnnotationTest {
    private JFrame mainWin = new JFrame("使用注解绑定事件监听器");
    // 使用Annotation为ok按钮绑定事件监听器
    @ActionListenerFor(listener = OkListener.class)
    private JButton ok = new JButton("确定");
    // 使用Annotation为cancel按钮绑定事件监听器
    @ActionListenerFor(listener = CancelListener.class)
    private JButton cancel = new JButton("取消");

    public void init(){
        // 初始化界面的方法
        JPanel jp = new JPanel();
        jp.add(ok);
        jp.add(cancel);
        mainWin.add(jp);
        ActionListenerInstaller.processAnnotations(this);//1
        mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        mainWin.pack();
        mainWin.setVisible(true);
    }

    public static void main(String[] args) {
        new AnnotationTest().init();
    }
}
// 定义ok按钮的事件监听器实现类
public class OkListener implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {
        JOptionPane.showMessageDialog(null, "点击了确认按钮");
    }
}
// 定义cancel按钮的事件监听器实现类
public class CancelListener implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {
        JOptionPane.showMessageDialog(null, "点击了取消按钮");
    }
}

  上面的程序中定义了2个JButton按钮,并使用@ActionListenerFor注解为这两个按钮绑定了事件监听器,使用@ActionListenerFor注解时传入了listener元数据,该数据用于设定每个按钮的监听器实现类。
下面是注解处理工具类,该工具分析对象中所有的成员变量,如果该成员变量前使用了@ActionListenerFor修饰,则取出该Annotation中的listener元数据,并根据该数据来绑定事件监听器。

public class ActionListenerInstaller {
    // 处理Annotation的方法,其中obj是包含Annotation的对象
    public static void processAnnotations(Object obj){
        try {
            // 获取obj对象的类
            Class cl = obj.getClass();
            // 获取指定obj对象的所有成员变量,并遍历每个成员变量
            for(Field f : cl.getDeclaredFields()){
                // 将该成员变量设置成可自由访问
                f.setAccessible(true);
                // 获取该成员变量上ActionListenerFor类型的Annotation
                ActionListenerFor a = f.getAnnotation(ActionListenerFor.class);
                // 获取成员变量f的值
                Object fObj = f.get(obj);
                // 如果f是AbstractButton的实例并且a不为null
                if(a!=null&&fObj!=null&&fObj instanceof AbstractButton){
                    // 获取a注解里的listener元数据(它是一个监听器类)
                    Class<? extends ActionListener> listenerClazz = a.listener();
                    // 使用反射来创建listener类的对象
                    ActionListener al = listenerClazz.newInstance();
                    AbstractButton ab = (AbstractButton) fObj;
                    // 为ab按钮添加事件监听器
                    ab.addActionListener(al);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

程序运行后
运行后效果
按钮点击后的效果
点击按钮的效果
  以上代码的两个监听器实现类原来是内部类,结果运行不成功,后提取出单独的类,才运行成功,不知道是什么原理。难道内部类没有默认的构造方法吗?
推荐一位大佬的通俗易懂的注解文章:https://blog.csdn.net/qq1404510094/article/details/80577555

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java注解(Annotation)学习 的相关文章

随机推荐

  • 设置AndroidStudio左侧和右侧的字体

    1 File Settings Appearance amp Behavior Appearance xff0c 右边Override default fonts by not recommended 2 设置代码大小 xff1a File
  • Android下载网络资源文件

    直接上代码 xff1a lt uses permission android name 61 34 android permission WRITE EXTERNAL STORAGE 34 gt lt uses permission and
  • 出现:trying to draw too large(138078000bytes) bitmap:错误时

    这里就不翻译了 xff0c 意思就是说你将高分辨率图片放在了低分辨率文件夹下 例如 xff1a 图片的分辨率是属于xxhdpi的 xff0c 而你将这张图片放在了drawable xhdpi或者比这个还低的文件夹下 xff0c 就会报这个错
  • Android把图片压缩到一定大小并不失真

    本文转载只供参考 一 图片压缩方式 图片按比例大小压缩方法 64 param srcPath xff08 根据路径获取图片并压缩 xff09 64 return public static Bitmap getimage String sr
  • Android 动态设置TextView的位置

    RelativeLayout LayoutParams layoutParams 61 new RelativeLayout LayoutParams 40 40 宽高 layoutParams setMargins int dstX 20
  • 神经网络应用较多的算法,图卷积神经网络应用

    神经网络原理及应用 神经网络原理及应用1 什么是神经网络 xff1f 神经网络是一种模拟动物神经网络行为特征 xff0c 进行分布式并行信息处理的算法 这种网络依靠系统的复杂程度 xff0c 通过调整内部大量节点之间相互连接的关系 xff0
  • Java泛型学习

    纯属个人理解 xff0c 代码参考自视频 用途 xff1a 1 用于集合容器中 xff0c 可以使集合记住存储数据的类型 xff0c 防止频繁转换类型可能导致的ClassCastException 用于javac编译器的类型检查 xff0c
  • Java反射学习

    文字和代码来源于视频 反射 xff0c 通过它我们可以得到一个Java类的全部信息 xff0c 可以调用类的普通方法 xff0c 构造方法 xff0c 对类进行实例化 xff0c 操作类的属性 类中的所有内容 xff1a 属性 构造方法 普
  • 面试题之反转单向链表

    题目为 xff1a 将一个单向链表反转 xff0c 写出算法步骤或代码 懵批了 今学习如下 xff0c 文章代码参考https blog csdn net K346K346 article details 93371829 xff0c 感谢
  • 冒泡排序总结

    本文内容和代码均来自于 漫画算法 xff0c 小灰和大黄的对话 xff0c 非常有趣味的一本书 现理论结合实践 xff0c 做一下测试 span class token keyword private span span class tok
  • net6的Web MVC项目实现限流功能

    原理 xff1a 利用MemoryCache服务组件 xff0c 记录用户最后一次访问接口的时间 xff0c 如果本次访问距离最后一次访问不超过1秒 xff0c 提示用户访问过于频繁 xff0c 否则 xff0c 接口可以正常访问 然后利用
  • 快速排序总结

    文章内容和代码来自 漫画算法 和数据结构教材 现进行一下代码编写练习 1 双边循环法 span class token comment 双边循环法 xff0c 从左右两端分别向中间进行比较和交换数据 递归实现 span span class
  • 堆排序总结

    本文内容来源于 漫画算法 和数据结构教材 这里提到的堆是一个二叉堆 xff0c 本质上是一颗完全二叉树 堆排序只需要一个记录大小的辅助空间 1 java实现 span class token comment 下沉调整 64 param ar
  • 计数排序

    本文内容和代码来自 漫画算法 之前练习的冒泡排序 鸡尾酒排序 快速排序 堆排序都是基于元素比较和位置元素交换实现的 xff0c 有一些特殊的排序并不基于元素比较 xff0c 如计数排序 桶排序 基数排序 以计数排序来说 xff0c 这种排序
  • 桶排序

    本文内容和代码来源于 漫画算法 针对计数排序的局限性 xff0c 桶排序做出了弥补 xff0c 时间复杂度同样是线性级 类似于计数排序所创建的统计数组 xff0c 桶排序需要创建若干个桶来协助排序 那么桶排序中所谓的 桶 xff0c 又是什
  • 归并排序

    本文内容和代码来源于数据结构教材 归并排序 Merging Sort 是又一类不同的排序方法 34 归并 34 的含义是将2个或2个以上的有序表组合成1个新的有序表 无论是顺序存储还是链表存储结构 xff0c 都可在O m 43 n 的时间
  • 插入排序

    文章内容来源于数据结构教材 C语言版 教材讲解了4种插入排序算法 xff0c 分别为 1 直接插入排序 2 折半插入排序 3 2 路插入排序 4 表插入排序 还有一个希尔排序 属于插入排序分类 本文只将1 2 xff0c 两种算法进行了实践
  • 希尔排序

    本文内容来源于数据结构教材 C语言版 希尔排序 Shell s Sort xff0c 又称缩小增量排序 Diminishing Increment Sort xff0c 它也是一种属插入排序类的方法 xff0c 但在时间效率上较前几种插入排
  • Java swing绘制柱状图和饼图

    15 14编写程序 xff0c 使用条形图显示作业 平时测验 其中考试和期末考试占总成绩的百分比 假设作业占20 用红色显示 xff0c 平时测验占10 用蓝色显示 xff0c 其中考试占30 用绿色显示 xff0c 期末考试占40 用橙色
  • Java注解(Annotation)学习

    xff08 本文内容来源于疯狂Java讲义 xff0c 感谢 xff09 注解 Annotation Annotation是代码里的特殊标记 xff0c 这些标记可以在编译 类加载 运行时被读取 xff0c 并执行相应的处理 通过使用注解