一、简介

Java注解用于为Java代码提供元数据。元数据是指用来描述数据的数据,简单来说就是描述代码间的关系,或者代码与其他资源之间内在联系的数据。在Java中,元数据以标签的形式存在于Java代码中,元数据标签的存在并不影响程序代码的编译和执行。简而言之,言而总之,注解就是标签的意思。

二、创建注解

注解语法:

package day02;


public @interface 注解名称 {

    属性类型 属性值() default "";
}
package day02;


/**
 * @author qx
 */
public @interface HelloAnnotation {

    String name() default "";

    int age() default 0;

}

三、元注解

元注解说可以注解到注解上的注解,或者说元注解是一种基本注解,它能够应用到其他注解上面。元注解有@Retention、@Documented、@Target、@Inherited、@Repeatable 5种。

1.@Retention

这个注解说明了这个注解的生命周期。

RetentionPolicy.SOURCE:只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。

RetentionPolicy.CLASS:只被保留到编译进行的时候,并不会被加载到JVM中。

RetentionPolicy.RUNTIME:可以保留到程序运行的时候,它会被加载到JVM中。

2.@Documented

这个注解的作用是能够将注解中的元素包含到Javadoc中去。

3.@Target

标明注解运用到的地方。

ElementType.ANNOTATION_TYPE:可以给一个注解进行注解

ElementType.CONSTRUCTOR:可以给构造方法进行注解

ElementType.FIELD:可以给属性进行注解

ElementType.METHOD:可以给方法进行注解

ElementType.TYPE:可以给一个类型进行注解,比如类、接口、枚举

4.@Inherited

Inherited是继承的意思。如果一个超类被@Inherited注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。

我们创建父类和子类,并且编写一个带有@Inherited注解的注解。然后我们在父类上标注这个注解。

package day02;


import java.lang.annotation.*;

/**
 * @author qx
 */
@Target(ElementType.TYPE)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface HelloAnnotation {

    String name() default "";

}
package day02;

/**
 * @author qx
 * @date 2023/10/26
 * @des 父类
 */
@HelloAnnotation(name = "parent")
public class Parent {
}
package day02;

/**
 * @author qx
 * @date 2023/10/26
 * @des 子类
 */
public class Child extends Parent {
}

测试:我们测试父类和子类的注解信息。我们没有在子类上标注任何注解信息。

package day02;

import java.lang.annotation.Annotation;

/**
 * @author qx
 * @date 2023/10/26
 * @des Inherited 测试
 */
public class InheritedTest {
    public static void main(String[] args) {
        Annotation[] parentAnnotations = Parent.class.getAnnotations();
        System.out.println("---父类信息---");
        System.out.println("父类注解个数:" + parentAnnotations.length);
        for (Annotation parentAnnotation : parentAnnotations) {
            System.out.println(parentAnnotation.annotationType().getSimpleName());
        }
        // 打印子类注解信息
        Annotation[] childAnnotations = Child.class.getAnnotations();
        System.out.println("---子类信息---");
        System.out.println("子类注解个数:" + childAnnotations.length);
        for (Annotation childAnnotation : childAnnotations) {
            System.out.println(childAnnotation.annotationType().getSimpleName());
        }
    }
}

输出:

---父类信息---
父类注解个数:1
HelloAnnotation
---子类信息---
子类注解个数:1
HelloAnnotation

从输出结果我们可以看到子类会继承超类的注解。

5.@Repeatable

Repeatable是可以重复的意思,通常注解的值可以同时取多个。

package day02;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

    Person[] value();
}
package day02;

import java.lang.annotation.*;

/**
 * @author qx
 * @date 2023/10/26
 * @des
 */
@Repeatable(Persons.class)
public @interface Person {

    String role() default "";
}
package day02;

/**
 * @author qx
 * @date 2023/10/26
 * @des
 */
@Person(role = "cto")
@Person(role = "ceo")
public class Man {

}

测试:

package day02;

import java.lang.annotation.Annotation;

/**
 * @author qx
 * @date 2023/10/26
 * @des
 */
public class RepeatableTest {
    public static void main(String[] args) {
        Annotation[] annotations = Man.class.getAnnotations();
        System.out.println(annotations.length);
        Persons persons = (Persons) annotations[0];
        for (Person person : persons.value()) {
            System.out.println(person.role());
        }
    }
}

输出:

1
cto
ceo

四、注解的属性

package day02;


import java.lang.annotation.*;

/**
 * @author qx
 */
@Target(ElementType.TYPE)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface HelloAnnotation {

    // default 后面设置注解属性的默认值 
    String name() default "admin";

}

就是值使用这个注解属性的时候,如果没有指定属性值那么会使用这个默认的属性值。

五、Java预置的注解

1.@Override

子类要重写父类中被@Override修饰的方法。

2.@Deprecated

加上这个注解之后,表示此方法或类不再建议使用,调用时会出现删除线,但不代表不能用,只是说不推荐使用。

package day02;

/**
 * @author qx
 * @date 2023/10/26
 * @des
 */
public class Hello {

    @Deprecated
    public void show(){
        System.out.println("这是一个过时方法");
    }
    public void test(){
        System.out.println("这是一个正常方法");
    }
}
package day02;

/**
 * @author qx
 * @date 2023/10/26
 * @des
 */
public class DeprecatedTest {
    public static void main(String[] args) {
        Hello hello = new Hello();
        hello.test();
        hello.show();
    }
}

Java基础知识回顾9-注解_注解

输出:

这是一个正常方法
这是一个过时方法

3.@SuppressWarnings

阻止警告的意思。

该注解的作用说给编译器一条指令,告诉它对被批注的代码元素内部的某些警告保存静默。

package day02;

/**
 * @author qx
 * @date 2023/10/26
 * @des
 */
public class DeprecatedTest {
    @SuppressWarnings("deprecation")
    public static void main(String[] args) {
        Hello hello = new Hello();
        hello.test();
        // @SuppressWarnings使用后警告没有出现了
        hello.show();
    }
}

4.@SafeVarargs

参赛安全类型注解,它的目的说提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生unchecked这样的警告。在声明具有模糊类型的可变参数的构造函数或方法时,Java编译器会报unchecked警告。鉴于这种情况,如果程序猿断定声明的构造函数和方法的主体no problem,可使用@SafeVarargs进行标记,这样Java编译器就不会报unchecked警告了!

package day02;

import java.util.Arrays;

/**
 * @author qx
 * @date 2023/10/26
 * @des
 */
public class SafeVarargsAnnotation<T> {

    private  T[] args;

   
    public SafeVarargsAnnotation(T... args) {
        this.args = args;
    }

    
    public static <T> void loopPrintInfo(T... infos){
        for(T info:infos){
            System.out.println(info);
        }
    }

    public static void main(String[] args) {
        SafeVarargsAnnotation.loopPrintInfo("a","b","c");
    }
}

编译的时候会出现使用了未经检查或不安全的操作。

Java基础知识回顾9-注解_注解使用_02

然后我们在构造方法和静态方法上加上@SafeVarargs注解。

package day02;

import java.util.Arrays;

/**
 * @author qx
 * @date 2023/10/26
 * @des
 */
public class SafeVarargsAnnotation<T> {

    private T[] args;


    @SafeVarargs
    public SafeVarargsAnnotation(T... args) {
        this.args = args;
    }


    @SafeVarargs
    public static <T> void loopPrintInfo(T... infos) {
        for (T info : infos) {
            System.out.println(info);
        }
    }

    public static void main(String[] args) {
        SafeVarargsAnnotation.loopPrintInfo("a", "b", "c");
    }
}

我们再次编译的时候没有出现unchecked的提示。

Java基础知识回顾9-注解_注解_03

5.@FunctionalInterface

Java8为函数式接口引入了一个新注解@FunctionalInterface,主要用于编译级错误检查,加上该注解,该注解规定接口只有一个接口方法,也被称作函数式接口。当你写的接口不符合函数式接口定义的时候,编译器会报错。

Java基础知识回顾9-注解_元注解_04

package day02;

/**
 * @author qx
 * @date 2023/10/26
 * @des
 */
@FunctionalInterface
public interface MyInterface {

    void say(String msg);
}

测试:

package day02;

/**
 * @author qx
 * @date 2023/10/26
 * @des
 */
public class FunctionalInterfaceTest {
    public static void main(String[] args) {
        MyInterface myInterface = msg -> System.out.println(msg);
        myInterface.say("hello");
    }
}

六、注解的使用场景

注解是一系列元数据,它提供数据来解释程序代码,但是注解并非说所解释的代码本身一部分,注解对代码的运行结果没有直接影响。

用处:

编译器可以利用注解来探测错误或警告信息

软件工具可以利用注解信息来生成代码、HTML文档或其它响应处理。

某些注解可以在程序运行时接受代码的提取。

总结:

1、注解就是标签,注解为了解释代码

2、注解的基本语法@interface

3、注解的元注解

4、注解的属性

5、注解主要给编译器及工具类型的软件用的

6、注解的提取要借助于Java的反射技术,反射比较慢,所以注解使用时也需要谨慎计较时间成本