11.1 基本注解(Annotation)

一、基本注解

注解必须使用工具来处理,工具负责提取注解里包含的元数据,工具还会根据这些元数据增加额外的功能。
先看Java提供的5个基本注解的用法——使用注解时要在前面加@符号,并把注解当成一个修饰符使用,用于修饰它支持的程序元素:
★@Override
★@Deprecated
★@Suppress Warnings
★@Safe Varargs(Java 7新增的)
★@FunctionalInterface(Java 8新增的)

1.1 限定重写父类方法:@Override

@Override用来指定方法覆载的,它可以强制一个子类必须覆盖子类的方法。如下程序指定子类Apple的info()方法必须重写父类方法。

class Fruit
{
    public void info()
    {
        System.out.println("水果的info方法...");
    }
}
public class Apple extends Fruit
{
    @Override
    public void info()
    {
        System.out.println("苹果类重写水果的info方法...");
    }
    public static void main(String[] args)
    {
        Fruit f1=new Apple();//向上转换
        f1.info();
    }
}
输出结果:
苹果类重写水果的info方法...

@Override告诉编译器检查这个方法,保证父类要包含一个被该方法重写的方法,否则就会出现编译错误。

1.2 Java 9增强的@Deprecated

@Deprecated用于表示某个程序元素(类、方法)已过时,当其他程序使用已过时的类、方法时,编译器会发出警告。
Java 9为@Deprecated注解增加了两个如下属性:
(1)forRemoval:该boolean类型的属性指定该API在将来是否会被删除。
(2)since:该String类型的属性指定该API从哪个版本被标记为过时。

注意:@Deprecated的作用与文档注释中的@derecated标记作用基本相同,但他们作用的用法不同,前提是JDK5才支持的注解,无须放在文档注释语法(/.../部分)中,而是直接修饰程序中的程序单元,如方法、类、接口等。

1.3 抑制编译器警告:@SuppressWarnings

@SuppressWarnings用于指示该注解修饰的程序元素(以及该程序元素中的所有元素)取消显式指定的编译器警告。@SuppressWarnings会一直作用于该程序元素的所有子元素。例如,使用@SuppressWarnings修饰某个类取消某个编译器警告,同时又修饰该类里的某个方法取消另一个编译器警告,那么该方法会同时取消这两个编译器警告。
在通常情况下,如果程序中使用没有泛型限制的集合将会引起编译器警告,为了编码编译器警告,可以使用@SuppressWarnings修饰。下面程序取消了没有使用泛型的编译器警告:

package section1;
import java.util.List;
@SuppressWarnings(value = "unchecked")
public class SuppressWarningsTest
{
    public static void main(String[] args)
    {
        List<String> myList =new ArrayList();
        myList.add("字符型");
        myList.add("2");
        System.out.println(myList);
    }
}

当使用@SuppressWarnings注解来瓜纳比编译器警告时,一定要在括号里使用name=value的形式为该注解的成员变量设置值。

1.4 “堆物染”警告于Java 9增强的@SafeVaragrs

回顾泛型的擦除

List list=new ArrayList<Integer>();//把一个值能装Integer的集合直接赋值给List集合
list.add(20);//添加元素时引发“为经检查的转换”的警告,编译、运行完全正确
List<String> ls=list;//①
//但只要访问ls里的元素,如下代码将会引起运行时异常
System.out.println(ls.get(0));

java把这种错误的原因称为“堆物染”(Heap pollution),当把一个不带泛型的对象赋值给一个带泛型的变量时,往往就会发生“堆物染”,如上①号代码。
对于形参个数可变的方法,这种形参又时泛型,这将更容易导致“堆物染”。例如如下工具类:

public class ErrorUtils
{
    @SafeVarargs
    public static void faultyMethod(List<String>... listStrArray)
    {
        // Java语言不允许创建泛型数组,因此listArray只能被当成List[]处理
        // 此时相当于把List<String>赋给了List,已经发生了“擦除”
        List[] listArray = listStrArray;//①
        List<Integer> myList = new ArrayList<>();
        myList.add(new Random().nextInt(100));
        System.out.println(myList);
        // 把listArray的第一个元素赋为myList
        listArray[0] = myList;
        String s = listStrArray[0].get(0);
    }
}

上面程序①号代码处已经发生了堆物染。由于该方法有一个形参List...类型,个数可变的形参相当于数组,但Java又不支持泛型数组,因此程序只能把List...当成List[]处理,这里就发生了堆物染。
在Java 6以及更早的版本中,Java编译器会认为finalMethod()没有任何问题,既不会提示错误,也没有警告。
等使用该方法时,例如:

public class ErrorUtils
{
    @SafeVarargs
    public static void faultyMethod(List<String>... listStrArray)
    {
        // Java语言不允许创建泛型数组,因此listArray只能被当成List[]处理
        // 此时相当于把List<String>赋给了List,已经发生了“擦除”
        List[] listArray = listStrArray;
        List<Integer> myList = new ArrayList<>();
        myList.add(new Random().nextInt(100));
        System.out.println(myList);
        // 把listArray的第一个元素赋为myList
        listArray[0] = myList;
        String s = listStrArray[0].get(0);
    }
    public static void main(String[] args)
    {
        ErrorUtils.faultyMethod(Arrays.asList("Hello"),
                Arrays.asList("world!"));//①
    }
}


该编译会在代码①处引发一个警告:

---------- 编译Java ----------
注: ErrorUtils.java使用了未经检查或不安全的操作。
注: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。

输出完成 (耗时 1 秒) - 正常终止

这个unchecked警告比较突兀,定义faultyMethod()方法没有任何警告,调用该方法时却引发一个警告。
而且程序运行时,会在String s = listStrArray[0].get(0);处引发一个ClassCastException异常。
从Java 7开始程序将会进行更严格的检查,Java编译器会在编译器编译ErrorUtils时就会发出一个警告。
由此可见,Java 7会在定义该方法时就会发出“堆物染”警告,这样保证开发者“更早”地注意到程序中可能存在的漏洞。
有时开发者不希望看到这个警告,则会使用如下三个方式来抑制警告:
(1)@SafeVaragrs修饰引发该警告的构造器或方法。Java 9增强了该注解,允许使用该注解修饰私有实例方法。
(2)@SuppressWarnings("unchecked")修饰
(3)编译时使用-Xlint:vargars选项

1.5 函数式接口与@FunctionalInterface

从Java 8开始:如果接口中只有一个抽象方法(可以包含多个默认方法或多个Static方法),该接口就是函数时接口。@FunctionalInterface就是用于指定某个接口必须是函数式接口。
提示:函数式接口就是Java 8专门为Lambda表达式准备的,Java 8允许使用Lambda表达式创建函数式接口的实例,因此Java 8专门增加了@FunctionalInterface.

package section1;
@FunctionalInterface
interface Interface
{
    static void foo()
    {
        System.out.println("Foo类方法");
    }
    default void bar()
    {
        System.out.println("bar类方法");
    }
    void test(int a,int b);
}
public class FunctionalIterfaceTest
{
    public static void main(String[] args)
    {
        Interface i=(a,b)->{
            System.out.println("a+b="+(a+b));
        };
        Interface.foo();
        i.bar();
        i.test(3,5);
    }
}
Foo类方法
bar类方法
a+b=8

@FuctionalInterface只能修饰接口,不能修饰其他程序元素。

{
public static void main(String[] args)
{
//打印IngeritableTest类是否具有@Ingeritable修饰
System.out.println(IngeritableTest.class.isAnnotationPresent(Inheritable.class));//true
}
}

posted @ 2020-04-16 23:10  小新和风间  阅读(233)  评论(0编辑  收藏  举报