如何使用注解

注解使用

简介

Java中,注解就是给程序添加一些信息,用字符@开头,这些信息用于修饰它后面紧挨着的其他代码元素,比如类、接口、字段、方法、方法中的参数、构造方法等。注解可以被编译器、程序运行时和其他工具使用,用于增强或修改程序行为等。

内置注解

Java有一些自己的原生注解。

  • @Override:重写,子类继承父类,重写父类方法时,会出现这个注解。
@Override
    public void test01() {
        super.test01();
  }
  • @Deprecated:它表示对应的代码已经过时了,程序员不应该使用它,不过,它是一种警告,而不是强制性的。比如Date的构造方法就已经过时了,但我们依然能够使用。
public class AnnotationTest01 {
    public static void main(String[] args) {
        Date date=new Date(2000,4,12);
        int year=date.getYear();
    }
}
  • @SuppressWarnings:表示压制Java的编译警告,它有一个必填参数,表示压制哪种类型的警告,它可以修饰大部分代码元素,在更大范围的修饰也会对内部元素起效,比如,在类上的注解会影响到方法,在方法上的注解会影响到代码行。
public class AnnotationTest01 {
    // 压制注解  
    @SuppressWarnings({"deprecation","unused"})
    public static void main(String[] args) {
        Date date=new Date(2017,4,12);
        int year=date.getYear();
    }
}

具体参数:

  • @FunctionalInterface:函数式接口,比如java的四大函数式接口中的消费接口。
@FunctionalInterface
public interface Consumer<T{
    void accept(T t);
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

创建注解的方式

定义注解与定义接口有点类似,都用了interface,不过注解的interface前多了@这个符号。另外,它还有两个元注解@Target和@Retention,这两个注解专门用于定义注解本身。

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

元注解

所谓元注解就是用来修饰其他注解的注解

@Target

@Target: 表示这个注解可以修饰哪些地方,它有一个枚举,里面有一些可选值可以选择。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

ElementType 可选值如下:

  • TYPE:表示类、接口(包括注解)、或者枚举声明
  • FIELD:字段,包括枚举常量
  • METHOD:方法
  • PARAMETER:方法中的参数
  • CONSTRUCTOR:构造方法
  • LOCAL_VARLABLE:本地变量
  • MODULE:模块(Java9 引入的)

它的可选值可以有多个,用{}表示,枚举常量之间用逗号隔开,比如SuppressWarnings压制Java警告的注解;

如果没有声明@Target,默认为适用于所有类型。

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

@Retention

@Retention:表示这个注解的生命周期,就是这个注解保留到什么时候。它的可选参数取值只能选择其中一个。

类型为RetenionPolicy,是一个枚举,有三个取值:

  • SOURCE:只在源代码中保留,编译器将代码编译为字节码文件后就会被丢失
  • CLASS:保留到字节码文件中,但Java虚拟机将class文件加载到内存时不一定会在内存保留
  • RUNTIME:一直保留到运行时,编译后也会生成 注解名.class的文件,因此我们之后我们可以使用反射机制来读取这些注解。

如果没有声明@Retention,则默认为CLASS。

@Inherited

可以让注解被继承,但这不是真正的继承,只是说子类通过使用@Inherited注解,可以让子类使用一个反射方法getAnnotations获取父类被@Inherited修饰的注解。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited // 继承
public @interface AnimalDemo {
    String value() default "1";
}

@AnimalDemo("2")
public class Animal {
  
}
class Dog extends Animal{
    public static void main(String[] args) {
        // 判断是否有指定类型的注解
       boolean annotationPresent = Dog.class.isAnnotationPresent(AnimalDemo.class);
        // 这里返回的true,因为父类上的注解有@Inherited修饰
        System.out.println(annotationPresent);

        // 可以通过子类的class对象,获取到父类身上被@Inherited修饰的注解
        // 获取所有的注解,返回来一个数组
        Annotation[] annotations = Dog.class.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation.annotationType().getSimpleName());
        }
    }
}

@Documented

@AnimalDemo
public class Main{
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited 
@Documented
@interface AnimalDemo{
 String value() default "";
}

此注解,可以让被修饰的注解生成到java的doc文档上面。

使用命令javadoc 类名.java如何使用注解

注意事项

1. 注解内的参数的类支持八大基本类型(基本类型对应的包装类是不可以的),Class,String,枚举,注解以及这些类型的数组。 如果使用了其他数据类型,编译器会抛出错误。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited // 继承
public @interface AnimalDemo {
    String value() default "1";
    int number() default 0;
    boolean flag() default false;
    byte byteNum() default 1;
    Class<?> test() default int.class;
    AnimalDe an() default @AnimalDe(shortNum = 5);
    double[] doubleNum() default {1.0,2.0,3.0};
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface AnimalDe{
    short shortNum() default 2;
}

2. 与接口和类不同,注解不能继承,因此不能使用关键字extends来继承另外一个注解。如何使用注解

但是注解在编译后,注解会自动继承Annotation接口。我们来反编译看一下。如何使用注解即使Java接口可以多继承,但是在定义注解时依然无法使用extends继承其他注解。

3. 如果在注解中定义了参数,但是没有提供默认值,在使用注解的时候必须提供一个具体的值,不能为null,否则编译不过去。此时的name并没有提供默认值。

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

如何使用注解

但是我们在参数后面加上default “”,表示一个默认值,这样就不会出现错误了。

4. 当注解中的有参数名称为value时,可以直接省略key=value这种写法,直接写值即可。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnimalDemo1 {
    String value() default "";
}

@AnimalDemo1("1")
public class Dog{

}

注解与反射

在我们反编译后,看到定义到的注解都会默认继承Annotation接口,这个接口是所有注解的父接口。为了能够在运行时准确获取到注解的相关信息,java在反射包中有一个AnnotationElement接口,主要表示正在虚拟机中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术读取注解的信息,比如反射包下的Constructor类、Field类、Method类、Package类、Class类等等都实现了AnnotationElement接口。

AnnotationElement接口中通用的5个方法。

    <T extends Annotation> getAnnotation(Class<T> annotationClass)// 获取指定类型的注解,没有返回null
    Annotation[] getAnnotations(); // 获取所有的注解
    default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) // 判断是否有指定类型的注解
    Annotation[] getDeclaredAnnotations() // 获取所有本元素上直接声明的注解,不包括@Inherited修饰的注解
    default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) // 获取指定类型的注解,不包括@Inherited修饰的注解,如果有则返回,没有返回null

案例(自定义注解的应用)

自定义一个单例注解

这个案例有1个注解,只要某个类带上这个注解,那么就是单例的。 我们先来看注解。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Singleton {
}

创建一个检查此类是否带有单例注解的类

public class ServiceSingleton {
    private static ConcurrentHashMap<Class<?>,Object> map=new ConcurrentHashMap<>();
    public static<T> T getInstance(Class<T> cls) throws IllegalAccessException, InstantiationException {
        // 1. 传入一个对象
        // 2. 判断该类身上是否有单例注解
        // 3. 如果没有则创建,如果存在则直接返回
        boolean singletonFlag = cls.isAnnotationPresent(Singleton.class);
        if(!singletonFlag){
            return cls.newInstance();
        }
        if(map.contains(cls)){
            return (T) map.get(cls);
        }else{
            synchronized (cls){
                Object o = map.get(cls);
                if(o!=null){
                    return (T) o;
                }else{
                    o= cls.newInstance();
                    map.put(cls,o);
                    return (T) o;
                }
            }
        }
    }
}

使用ServiceA类的main方法进行测试,ServiceA类带有Singleton注解

@Singleton
public class ServiceA {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        ServiceA instance1 = ServiceSingleton.getInstance(ServiceA.class);
        ServiceA instance2 = ServiceSingleton.getInstance(ServiceA.class);
        System.out.println(instance1==instance2);
    }
}
结果返回true

总结

注解提升了Java语言的表达能力,有效的实现了应用功能和底层功能的分离,应用程序员可以专注于应用功能。并且注解简化了代码并提高编码的效率。

注解分析就到这里了。。。



原文始发于微信公众号(阿黄学编程):如何使用注解

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/35618.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!