Java程序设计——注解(Java高级应用)

目录

3.@SuppressWarnings注解

4.@SafeVarargs注解(?)

5.@FunctionalInterface注解

2.@Documented文档注解

注解就像修饰符一样,使用时在前面增加@符号,用于修饰包、类、构造方法、域、方法、参数、局部变量的声明,这些信息被存在注解的“name=values”键值对中

注解不影响程序代码的运行,无论增加还是删除注释,代码都始终如一的执行

如果希望程序中的注解在运行时起到一定的作用,需要通过配套的工具对注解中的信息进行访问和处理,这种工具称为APT(注解处理工具)

APT注解处理工具负责提取注解中包含的元数据,并根据这些元数据增加额外功能

元数据的作用如下:

  • 编写文档——通过注解中标识的元数据可以生成doc文档
  • 代码分析——通过注解中标识元数据,使用反射对代码进行分析
  • 编译检查——通过注解中标识的元数据,让编译器能够实现基本的编译检查,如@Overrride重写

一、基本注解

1.@Override注解

@Override注解用于指定方法的重写,强制一个子类必须覆盖父类的方法

public class OverrideDemo {
    public static void main(String[] args) {
        Father obj = new Son();
        obj.Info();
    }
}
class Father{
    public void Info(){
        System.out.println("父类的Info方法");
    }
}
class Son extends Father{

    //  使用@Override指定下面必须重写父类的方法
    @Override
    public void Info(){
        System.out.println("子类的Info方法");
    }
}
复制代码

通过运行结果,可能看不出@Overrride注解的作用,此时将@Overrride注解删除,程序运行结果保持不变

因为@Override注解的作用是告诉编译器检查Son类的Info()方法,并保证父类也要有一个Info()方法,否则编译出错

2.@Deprecated注解

@Deprecated注解标识某个程序元素(接口、类、方法)已过时

public class DeprecatedDemo {
    public static void main(String[] args) {
        new DeprecatedDemo().oldMethod();
    }

    @Deprecated
    public void oldMethod(){
        System.out.println("使用过时的方法会被编译警告...");
    }
}
复制代码

上述代码中,oldMethod()方法使用@Deprecated注解修饰该方法已过时,因此会引起编译器警告,而不是错误,结果依然可以输出

3.@SuppressWarnings注解

@SuppressWarning注解允许开发人员控制编译器警告的发布且会一直作用于该元素的所有子元素,可以标注在类、字段、方法、参数、构造方法以及局部变量上

语法格式如下:

import java.util.ArrayList;
import java.util.List;

// 忽略因使用泛型而未限制类型而产生的警告
@SuppressWarnings("rawtypes")
public class SuppressWarningsDemo {
    public static void main(String[] args) {
        List list = new ArrayList();
    }
}
复制代码

4.@SafeVarargs注解(?)

“堆污染”是将一个不带泛型的变量赋值给一个带泛型的变量,将导致泛型变量污染

抑制堆污染警告的三种方法:

  • 使用@SafeVarargs注解(Java 7专供)
  • 使用@SuppressWarnings(“unchecked”)修饰
  • 编译时使用-Xlint:varargs选项
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class SafeVarargsDemo {
    public static void main(String[] args) {
      Test(Arrays.asList("Hello"), Arrays.asList(" World!"));
      // Arrays.asList:将数组或元素的内容转换为集合
    }

    @SafeVarargs
    public static void Test(List<String>... list){ //  可变参数(形参个数==数组个数)
        List[] listArray = list;   //  Java不支持泛型数组,所以将List<String> == List[],从而导致堆污染
        List<Integer> listInteger = new ArrayList<>();
        listInteger.add(88);
        //  把listInteger赋值给listArray的第一个元素
        listArray[0] = listInteger;
        System.out.println(listArray[0].get(0));
    }
}
复制代码

注意:@SafeVarargs注解不适用于非 static 或非 final 声明的方法,对于未声明为 static 或 final 的方法,如果要抑制 unchecked 警告,可以使用 @SuppressWarnings 注解。

5.@FunctionalInterface注解

@FunctionalInterface注解用于指定某个接口必须是 函数式接口 ,Java8规定,如果一个接口中只有一个抽象方法,则该接口就是函数式接口

@FunctionalInterface注解只能修饰接口,不能修饰程序其他元素

// 函数式接口:只包含一个抽象方法
@FunctionalInterface
public interface FunctionalInterfaceDemo {
    public abstract void abstractMethod1();
   // public abstract void abstractMethod2();
}
复制代码

二、定义注解

定义一个新的注解类型与定义一个接口非常相似,在原有的interface关键字前增加@符号

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnol {

    //  1.注解的成员由未实现的方法组成
    String test1();
    int test2();

    //  2.包含默认值的注解
    double test3() default 5.20;


/*
    //  3. 注解中的成员在使用时实现
    @MyAnnol(test1 = "这是test1()方法",test2 = 666)
*/
}
复制代码

@Retention注解:指定 自定义的注解 可以保留多长时间,此处使用 RetentionPolicy.RUNTIME 保留策略值,该值是最长注解持续期,表明在程序运行时JVM通过反射获取注解信息

三、使用注解

注解大多是为其他工具提供运行信息或决策依据而设计的,任何JAVA程序都可使用反射来查询注解实例的信息

AnnotatedElement接口:用于在反射过程中获取注解信息,并为注解相关操作提供支持

方法 功能
Annotation getAnnotation(Class annotype) 返回调用对象的注解
Annotation getAnnotations() 返回调用对象的所有注解
Annotation getDeclareedAnnotations() 返回调用对象的所有非继承注解
Boolean isAnnotationPresent(Class annotype) 判断与调用对象相关联的注解是由annotype指定的
import java.lang.reflect.Method;

//  使用自定义的注解修饰类
@MyAnnol(test1 = "类注解", test2 = 886)
class MyClass{
    
    //  使用自定义的@MyAnnol注解修饰方法
    @MyAnnol(test1 = "不带参数的方法", test2 = 666,test3 = 22.2)
    public void methods(){

    }
}

public class MyAnnolDemo {
    public static void main(String[] args) throws NoSuchMethodException {
        //  1.获取MyClass类的注解
        MyAnnol myAnnol1 = MyClass.class.getAnnotation(MyAnnol.class);

        //  2.输出类注解信息
        System.out.println("MyClass类的注解信息:");
        System.out.println(myAnnol1.test1());
        System.out.println(myAnnol1.test2());
        System.out.println(myAnnol1.test3());
        System.out.println("---------------------");

        
        //  1.获取MyClass类的methods()方法
        Method myMethod = MyClass.class.getMethod("methods");

        //  2.获取MyClass类的methods()方法的注解
        MyAnnol myAnnol2 = myMethod.getAnnotation(MyAnnol.class);

        //  3.输出MyClass类的methods()方法注解的信息
        System.out.println("MyClass类的methods()方法的注解信息:");
        System.out.println(myAnnol2.test1());
        System.out.println(myAnnol2.test2());
        System.out.println(myAnnol2.test3());
    }
}
复制代码

上述代码使用 反射 分别获取类和方法所关联的注解对象,无论类还是方法,都是通过 getAnnotation()  方法来获取 注解对象

为了使用 反射机制 获取注解的相关信息,在定义注解时必须将 注解的保留策略 设置为RetentionPlolicy.RUNTIME,否则获取不到注解对象

四、元注解

元注解的作用:负责注册其它注解

1.@Retention注解

@Rention注解:用于指定被修饰的注解可以保留多长时间

注册保留策略值:

策略值 功能
RetentionPolicy.SOURCE 注解只在源文件中保留,在编译期间删除
RetentionPolicy.CLASS 注解只在编译期间存在于.class文件中,运行时JVM不可获取注解信息,该策略值也是默认值
RetentionPolicy..RUNTIME 运行时JVM可以获取注解信息,是最长注解持续期
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.SOURCE)
@Retention(RetentionPolicy.CLASS)
@Retention(RetentionPolicy.RUNTIME)

public @interface myAnnotation {


}
复制代码

2.@Documented文档注解

@Documented注解:用于指定被修饰的注解可以被javadoc工具提取成文档

import java.lang.annotation.Documented;

@Documented
public @interface myAnnotation {


}
复制代码

3.@Target注解

@Target注解:用来限制注解的使用范围,即指定被修饰的注解能用于哪些程序单元

应用类型由ElementType枚举进行指定:

枚举值 功能
ElementType.TYPE 可以修饰类、接口、注解或枚举类型
ElementType.FIELD 可以修饰属性、包括枚举常量
ElementType.METHOD 可以修饰方法
ElementType.PARAMETER 可以修饰参数
ElementType.CONSTRUCTOR 可以修饰构造方法
ElementType.LOCAL_VARIABLE 可以修饰局部变量
ElementType.ANNOTATION_TYPE 可以修饰注解类
ElementType.PACKAGE 可以修饰包
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target(ElementType.TYPE) //  可以修饰类、接口、注解或枚举类型
@interface myAnnotation {

}
复制代码

4.@Inherited注解

@Inherited注解:指定注解具有继承性

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface InheritedAnnol {
    String test1();
    int test2();
}
复制代码
@InheritedAnnol(test1 = "继承注解", test2 = 666)
class Base{

}
public class InheritedDemo extends Base{
    public static void main(String[] args) {

        //  类InheritedDemo从父类Base中继承注解
        InheritedAnnol inheritedAnnol = InheritedDemo.class.getAnnotation(InheritedAnnol.class);

        //   输出注解中的成员信息
        System.out.println(inheritedAnnol.test1() + "--------" + inheritedAnnol.test2());

        //   判断InheritedDemo中是否具有@InhertedAnnol注解
        System.out.println(InheritedDemo.class.isAnnotationPresent(InheritedAnnol.class));
    }
}
复制代码

5.@Repeatable注解

@Repeatable注解:用于开发重复注解

在Java8之前,同一个程序元素前只能使用一个相同类型的注解,如果需要在同一个元素前使用多个相同类型的注解,则必须通过注解容器来实现

从Java8开始,允许使用多个相同的类型的注解来修饰同一个元素,即在定义注解时要使用@Repeatable元注解进行修饰

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface RepeatableContainer {    //  该注解是一个容器
    RepeatableAnnol[] value();      //  该容器可以接受多个@RepeatableAnnol注解
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Repeatable(RepeatableContainer.class)
public @interface RepeatableAnnol {
    String name();
    char sex();
}
复制代码
@RepeatableAnnol(name = "张三", sex = '男')
@RepeatableAnnol(name = "李四", sex = '女')
public class RepeatableDemo {
    public static void main(String[] args) {

        //  1.用Java8新增的getDeclaredAnnotationByType()方法获取修饰该类的多个RepeatableAnnol注解
        RepeatableAnnol[] repeatableAnnols =
                RepeatableDemo.class.getDeclaredAnnotationsByType(RepeatableAnnol.class);

        //  2.打印出数组中的@RepeatableAnnol注解
        for (RepeatableAnnol rs : repeatableAnnols) {
            System.out.println(rs.name() + "----------" + rs.sex());
        }


        //  3.使用传统的getDeclaredAnnotation()来获取修饰该类的@RepeatableContainer注解
        RepeatableContainer repeatableContainer =
                RepeatableDemo.class.getDeclaredAnnotation(RepeatableContainer.class);
        System.out.println(repeatableContainer);
    }
}
复制代码

6.类型注解

类型注解:在定义枚举时使用@Target(ElementType.TYPE_USE)来修饰

枚举值:

  • TYPE_PARAMETER
  • TYPE_USE

除了在定义类、接口和方法等常见的程序元素时可以使用类型注解,以下位置也可使用类型注解进行修饰:

  • 创建对象(new)
  • 类型转换
  • implements实现接口
  • throws声明抛出异常
  • 方法参数
import java.io.Serializable;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;

//  定义一个简单的类型注解,不带任何成员变量
@Target(ElementType.TYPE_USE)
@interface notNull{

}

//  1.定义类和实现接口时使用@notNull类型注解
@notNull public class ElementTypeDemo implements @notNull Serializable {

    //  2.throws抛出异常时使用@notNull类型注解
    public static void main(String[] args) throws @notNull Exception{

        //  3.创建对象时使用@notNull类型注解
        Object object = new @notNull Object();

        //  4.强制类型转换时使用@notNull类型注解
        String str = (@notNull String) object;

        //  5.泛型中使用@notNull类型注解
        List<String> list = new ArrayList<@notNull>();
    }
}
复制代码

上述代码在各种情况下使用  @notNull 类型注解,这种无处不在的类型注解可以使编译器执行更严格的代码检查,从而提高程序的健壮性

上述代码虽然大量使用了@notNull类型注解,但这些注解不会起到任何作用。因为Java8本身并没有为这些类型注解提供处理工具,不能对类型注解执行检查框架

程序员可以通过第三方框架提供的检查工具,从而让编译器执行更严格的代码检查

猜你喜欢

转载自juejin.im/post/7127549706271457294