您当前的位置: 首页 >  Java

liaowenxiong

暂无认证

  • 2浏览

    0关注

    1171博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Java注解(Annotation)的学习

liaowenxiong 发布时间:2021-12-23 22:45:36 ,浏览量:2

文章目录
  • 注解定义
  • 作用分类
  • API 文档注解
  • JDK 预定义的注解
  • 自定义注解
    • 注解的格式
    • 注解的本质
    • 注解的属性
      • 属性的返回值类型
      • 属性的特点
      • 属性的赋值
    • 元注解
      • @Target
      • @Retention
      • @Documented
      • @Inherited
  • 解析注解
  • 总结

注解定义

注解( Annotation),也叫元数据。一种代码级别的说明。它是 JDK1.5 及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

注解的叫法很多:元数据、标签、标记…

使用注解的叫法很多:使用xxx注解标注、使用xxx注解标记、使用xxx注解描述…

作用分类

①编写文档:通过代码里标识的元数据生成文档 编写程序时以一套特定的标签(即注解)作注释,在程序编写完成后,通过 Javadoc 就可以同时形成程序的开发文档了。

②代码分析:通过代码里标识的元数据对代码进行分析 使用反射技术,获取注解属性的值,然后处理有关的业务逻辑

③编译检査:通过代码里标识的元数据让编译器能够实现基本的编译检查 例如:@Override 注解,就可以检测被注解的方法是否正确覆盖重写父类的方法。

API 文档注解

演示代码:

package priv.lwx.javaprac.annotation;

/**
 * 生成文档(javadoc)的注解演示代码
 *
 * @author liaowenxiong
 * @date 2021/9/16 下午3:58
 * @since JDK 1.5
 */
public class Demo01Annotation {
    public static void main(String[] args) {

    }

    /**
     * 计算两个整数的和
     * @param a 整数
     * @param b 整数
     * @return 两个整数的和
     */
    public int add(int a, int b) {
        return a + b;
    }
}

如上的演示代码,在注释中有很多的 @xxx,这些就是文档注解,可以将这些注解的内容提取成为 API 文档。

关于如何编写文档注解,以及如何生成 javadoc 请参见《JDK 命令之 javadoc – 生成API文档》。

JDK 预定义的注解

@Override 用来检查被该注解标注的方法是不是有效的方法重写。在方法签名相同的情况下覆盖重写父类的方法,在其它地方如果有问题,会直接报编译错误,无需该注解来检测。只有方法签名不同的情况下,而又希望覆盖重写父类的方法,使用该注解检测才有意义。那么什么情况下会出现这个问题,那么就是父类方法的参数很多,确实容易写错,而你的本意又确实是重写父类的方法,那么此时使用此注解就可以帮到你了。

@Deprecated 用来表明被该注解标注的类成员已经过时,如果标注的是方法则会在方法名上显示一条“删除线”

@SuppressWarnings 抑制警告,禁止警告 注:一般传递参数“all”

@SuppressWarnings("all")
public void test() {
	show();
}
自定义注解

可以通过反编译来查看注解实际的代码。 例如,查看注解 @Deprecated 的实际代码,你需要先编译它的源代码,再反编译字节码文件才能看到。

@Deprecated 注解的源代码如下:

package java.lang;

import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;

/**
 * A program element annotated {@code @Deprecated} is one that programmers
 * are discouraged from using. An element may be deprecated for any of several
 * reasons, for example, its usage is likely to lead to errors; it may
 * be changed incompatibly or removed in a future version; it has been
 * superseded by a newer, usually preferable alternative; or it is obsolete.
 *
 * 

Compilers issue warnings when a deprecated program element is used or * overridden in non-deprecated code. Use of the {@code @Deprecated} * annotation on a local variable declaration or on a parameter declaration * or a package declaration has no effect on the warnings issued by a compiler. * *

When a module is deprecated, the use of that module in {@code * requires}, but not in {@code exports} or {@code opens} clauses causes * a warning to be issued. A module being deprecated does not cause * warnings to be issued for uses of types within the module. * *

This annotation type has a string-valued element {@code since}. The value * of this element indicates the version in which the annotated program element * was first deprecated. * *

This annotation type has a boolean-valued element {@code forRemoval}. * A value of {@code true} indicates intent to remove the annotated program * element in a future version. A value of {@code false} indicates that use of * the annotated program element is discouraged, but at the time the program * element was annotated, there was no specific intent to remove it. * * @apiNote * It is strongly recommended that the reason for deprecating a program element * be explained in the documentation, using the {@code @deprecated} * javadoc tag. The documentation should also suggest and link to a * recommended replacement API, if applicable. A replacement API often * has subtly different semantics, so such issues should be discussed as * well. * *

It is recommended that a {@code since} value be provided with all newly * annotated program elements. Note that {@code since} cannot be mandatory, * as there are many existing annotations that lack this element value. * *

There is no defined order among annotation elements. As a matter of * style, the {@code since} element should be placed first. * *

The {@code @Deprecated} annotation should always be present if * the {@code @deprecated} javadoc tag is present, and vice-versa. * * @author Neal Gafter * @since 1.5 * @jls 9.6.4.6 @Deprecated */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE}) public @interface Deprecated { /** * Returns the version in which the annotated element became deprecated. * The version string is in the same format and namespace as the value of * the {@code @since} javadoc tag. The default value is the empty * string. * * @return the version string * @since 9 */ String since() default ""; /** * Indicates whether the annotated element is subject to removal in a * future version. The default value is {@code false}. * * @return whether the element is subject to removal * @since 9 */ boolean forRemoval() default false; }

把这份源代码文件复制到其它地方,使用命令编译和反编译,命令如下:

liaowenongdeair:test liaowenxiong$ javac Deprecated.java # 先编译源代码
liaowenongdeair:test liaowenxiong$ javap Deprecated.class # 反编译字节码文件
Compiled from "Deprecated.java"
public interface Deprecated extends java.lang.annotation.Annotation {
  public abstract java.lang.String since();
  public abstract boolean forRemoval();
}
liaowenongdeair:test liaowenxiong$ 
注解的格式
元注解
public @interface 注解名称 {
	属性列表(本质就是抽象方法)
}
注解的本质

注解本质就是接口,继承自父接口 Annotation

注解的属性

所谓“属性”就是注解接口体(即大括号 {})中声明的常量和方法。所以“属性”的本质就是抽象方法。声明了属性则使用注解时必须给属性赋值。

为什么将方法称之为属性,看下面的示例代码:

// 使用自定义的注解
@MyAnno1(name = "李瓶儿") // 其中name是注解声明的抽象方法名,使用注解时需要赋值,赋值语法格式类似属性赋值的格式,所以将注解中声明的抽象方法称为"属性"
public void test() {
	show();
}
属性的返回值类型

返回值类型: 1.基本数据类型 2.String 3.枚举 4.注解 5.以上类型的数组

除了以上五种,其它类型不能作为注解接口中声明的抽象方法的返回值类型。

属性的特点

1.属性的默认值:在声明注解的属性时,如果使用关键字 default 给属性默认值,则使用注解时可以不进行属性的赋值,会取默认值。

2.在使用注解时,如果只有一个属性,且属性名称为 value,那么在给该属性赋值时,可以省略属性名称,即本来要 这么写 @MyAnno1(value = "李瓶儿"),可以省略成 @MyAnno1("李瓶儿")

3.注解类中声明的抽象方法名,返回值是字符串数组,那么给属性赋值时,如果多个值使用大括号包裹,如果只有一个值,则可以省略大括号。

声明定义注解及属性示例代码:

package priv.lwx.javaprac.annotation;

/**
 * 自定义注解
 *
 * @author liaowenxiong
 * @date 2021/9/20 下午5:18
 */
public @interface MyAnno1 {
    String name(); // 这是抽象方法,在注解中可以称为属性,使用此注解时,需要赋值,赋值格式:name = 一个字符串
    int age() default 12; // 默认值12,在使用注解时,没有指定该属性,那么该属性的默认值就是12
}
属性的赋值

各种返回值类型的属性如何赋值,请看下面的示例代码:

@MyAnno1(name = "高圆圆", setColor = Color.C1, test = @MyAnno2, names = {"双儿", "小栗子"})
/* 
name方法的返回值是字符串,所以赋值字符串;

setColor方法的返回值是枚举类,所以赋值时取枚举值,类似类的静态常量

test方法的返回值是注解类,所以赋值时格式为 @注解类名称

names是注解类中声明的抽象方法名,返回值是字符串数组,那么给属性names赋值时,
如果多个值使用大括号包裹,如果只有一个值,则可以省略大括号。
*/
public void test() {
	show();
}
元注解

用于描述注解的注解。

@Target

描述注解可以作用的位置

@Target(value={ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD, ElementType.PACKAGE, ElementType.MODULE, ElementType.PARAMETER, ElementType.TYPE})
public @interface MyAnno3 {
}

如上示例,表示被描述的注解(MyAnno3)可以作用于构造器、字段、局部变量、方法、包、模块、参数、类上。{} 内的都是枚举类 ElementType 的枚举值。

@Retention

描述注解被保留的阶段(源码阶段、编译阶段、运行时阶段)

有三个值: SOURCE:被描述的注解仅在源码阶段保留,编译时就被舍弃了 CLASS:被描述的注解会保留到字节码文件中,类加载进内存时被舍弃了 RUNTIME:被描述的注解会保留到运行时阶段,即类加载进入内存时,注解也会被加载进内存,可以通过反射获取相关信息

@Documented

描述注解是否可以被 javadoc 抽取到文档中,即被描述的注解会原样出现在API文档中

@Inherited

描述注解是否被子类继承

解析注解

获取注解属性中定义的值。 本质:就是获取注解类的实例对象,然后调用注解属性对应的成员方法,获得对应的返回值

步骤: 1.获得被注解的类/方法/字段对应的反射对象,即类就是 Class 对象,方法就是 Method 对象,变量就是 Field 对象 2.通过反射对象获得注解的实例对象,即调用反射对象的 getAnnotation/getAnnotations 等方法获取注解对象 3.调用注解对象的方法,获得返回值,该返回值就是对应注解属性中定义的值

演示代码:

package priv.lwx.javaprac.annotation;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 该类可以创建任意类型的对象,执行其中的任意方法.
 *
 * 使用注解的方式取代属性文件的方式来获取类名和方法名.
 * @author liaowenxiong
 * @date 2021/9/21 上午7:49
 */
@Pro(className = "priv.lwx.javaprac.annotation.Person", methodName = "eat")
public class Demo03Annotation { 
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        // 1.获取当前类的Class对象
        Class c = Demo03Annotation.class;

        // 2.获取当前类的注解类的实例对象
        // 其实就是在内存中生成了一个注解接口的实现类对象
        /*
        其实编译器会自动生成如下的代码:
        public class ProImpl implements Pro {
            public String className() {
                return "priv/lwx/javaprac/annotation/Demo03Annotation";
            }

            public String methodName() {
                return "eat";
            }
        }
         */
        Pro pro = c.getAnnotation(Pro.class);

        // 3.调用注解对象上的抽象方法,获取返回值
        String className = pro.className(); // 返回值就是使用Pro注解时所定义的className属性值
        String methodName = pro.methodName(); // 返回值就是使用Pro注解时所定义的methodName属性值
        System.out.println(className);
        System.out.println(methodName);

        // 使用Class的静态方法forName将类加载进内存中
        Class c2 = Class.forName(className);

        // 获取无参构造器
        Constructor constructor = c2.getConstructor();

        // 通过无参构造器创建对象
        Object obj = constructor.newInstance();

        // 获取方法对象
        Method method = c2.getMethod(methodName);

        // 执行方法
        Object result = method.invoke(obj);
        System.out.println(result);
    }

}
总结

在实际开发过程中,多数情况我们不会自定义注解,而是使用注解。

注解给谁用? 给解析程序用,编译器也属于解析程序,解析程序识别注解,然后实现有关的业务逻辑。

注解可以理解为程序中一种标签、标记

关注
打赏
1661566967
查看更多评论
立即登录/注册

微信扫码登录

0.0589s