注解

一、创建注解

注解是一种手段,它可以给程序添加一些信息,用字符@开头,这些信息用于修饰它后面紧挨着的其他代码元素,比如类、接口、字段、方法、参数、构造方法等。注解本身只是一种标记,不会改变当前代码的行为,类似标记接口的作用,但是它可以被编译器、程序运行时和其他工具使用,可以被用于增强或修改代码行为等。

我们可以像定义一个接口一样定义注解:

    @Target(ElementType.METHOD)  //声明注解的目标
    @Retention(RetentionPolicy.SOURCE)  //声明注解信息保留到什么时候
    @interface TestAnnotation{  //定义注解
        String value();  //定义注解的参数,可以有多个
    }

@interface 为定义注解的关键字,和定义接口相似;

@Target和@Retention是两个元注解,其中@Target声明该注解的作用目标,有以下几种:

  • TYPE——表示适用于类、接口或枚举
  • FIELD——字段,包括枚举常量
  • METHOD——方法
  • PARAMETER——方法中的参数
  • CONSTRUCTOR——构造方法
  • LOCAL_VARIABLE——本地变量
  • MODULE——模块(java 9引入)

目标可以有多种类型,用{,}隔开,默认是所有类型。

@Retention表示注解信息保留到什么时候,有以下三种:

  • SOURCE——只在源代码中保留,编译器将代码编译成字节码文件后就会丢掉
  • CLASS——保留到字节码文件中,但Java虚拟机将class文件加载到内存是不一定在内存中保留
  • RUNTIME——一直保留到运行时

只能选择一种,默认是CLASS。

可以为注解定义一些参数,如上例中的Sting value(),其中String 是参数类型,value是参数名,也可以指定默认值(如没有声明默认值,使用时必须赋值):

String value() default "v0";

上面注解的使用方法如下:

    @TestAnnotation(value = "v1")
    public void operation() {...}

如果只有一个参数,且名称是value时可以省略参数名:

    @TestAnnotation("v1")
    public void operation() {...}

二、查看注解

 创建了注解并在代码中使用,并不会影响代码的行为。要影响代码行为首先要能够查看注解信息,我们一般使用反射机制在运行时查看和利用注解信息。反射中有很多与注解相关的方法:

   //判断是否有指定类型的注解
    public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}

   //获取该元素所有注解
    public Annotation[] getAnnotations() {}

    //获取该元素指定注解
    public <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) {}
    
    //获取参数类型注解,仅Method和Constructor元素有此方法
    public Annotation[][] getParameterAnnotations() {}

下面利用反射查看并调用带有特定类型注解的方法:

    public static <T> void parseMethod(Class<T> clazz) {
        try {
            T obj = clazz.newInstance();  //创建反射类实例
            for (Method method : clazz.getDeclaredMethods()) {  //获取所有声明的方法
                MyMethodAnnotation methodAnnotation = method.getAnnotation(MyMethodAnnotation.class);  //获取每个方法的MyMethodAnnotation类型注解信息
                if (methodAnnotation != null) {
                    // 通过反射调用带有此注解的方法,并引用注解参数uri
                    method.invoke(obj, methodAnnotation.uri());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

三、应用注解

下面以格式化输出为例讲解注解的实战用法。

如果我们需要打印一个对象的变量信息,通常是调用它的toString方法,但是往往不能达到我们想要的信息展示效果,我们需要定制toString方法,如果有多个不同对象,则需要定制每个对象对应类的toString方法。

使用注解可以简化这一过程,首先定义两个注解:

//表示变量名称
@Retention(RUNTIME)
@Target(FIELD)
public @interface Label {
    String value() default "";
}

//表示日期变量格式
@Retention(RUNTIME)
@Target(FIELD)
public @interface Format {
    String pattern() default "yyyy-MM-dd HH:mm:ss";
    String timezone() default "GMT+8";
}

然后使用这两个注解:

    static class Student {
        @Label("姓名")
        String name;

        @Label("出生日期")
        @Format(pattern = "yyyy/MM/dd")
        Date born;

        @Label("分数")
        double score;
    }

定义一个SimpleFormatter类在运行时查看并使用注解信息对输出格式化:

public class SimpleFormatter {
    public static String format(Object obj) {
        try {
            Class<?> cls = obj.getClass();  //通过实例获取Class类
            StringBuilder sb = new StringBuilder();
            for (Field f : cls.getDeclaredFields()) {  //获取所有变量
                if (!f.isAccessible()) {  //取消访问限制
                    f.setAccessible(true);
                }
                Label label = f.getAnnotation(Label.class);  //获取Lable注解
                String name = label != null ? label.value() : f.getName();  //读取变量名称
                Object value = f.get(obj);  //获取变量值
                if (value != null && f.getType() == Date.class) {
                    value = formatDate(f, value);  //如果是日期
                }
                sb.append(name + ":" + value + "
"); //格式化
            }
            return sb.toString();
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

}

使用方法如下:

    public static void main(String[] args) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Student zhangsan = new Student("张三", sdf.parse("1990-12-12"), 80.9d);
        
        System.out.println(SimpleFormatter.format(zhangsan));
}

输出结果:

姓名:张三
出生日期:1990/12/12
分数:80.9

四、总结

  注解提升了Java语言的表达能力,有效地实现了应用功能和底层功能的分离,框架/库的程序员可以专注于底层实现,借助反射实现通用功能,提供注解给应用程序员;应用程序员可以专注于应用功能开发,通过简单的声明式注解与框架/库进行协作。

原文地址:https://www.cnblogs.com/not2/p/11159122.html