计划重点自JDK5推出以来,注解已经成为Java生态系统中不可或缺的一部分。虽然开发者为Java框架开发了无数的自定义注解(比如Spring的@Autowired),但有些被编译器识别的注解是非常重要的。在本文中,我们将看到Java编译器支持的5个注释并了解它们的预期用途。在此过程中,我们将探讨其创建背后的基本原理、围绕其使用的一些特性以及正确应用的一些示例。虽然其中一些注解比其他注解更常见,但每个非初级Java开发人员都应该消化每一个注解。@Override@FunctionalInterface@SuppressWarnings@SafeVarargs@Deprecated首先,我们将深入研究Java中最常用的注解之一:@Override。@Override方法的实现或为抽象方法提供实现的能力是任何面向对象(OO)语言的核心。由于Java是一门面向对象的语言,具有许多常见的面向对象抽象机制,非***超类中定义的非final方法或接口中的任何方法(接口方法不能是final)都可以被子类覆盖。单击此处阅读有关Java10新功能的实践教程。虽然重写方法乍一看似乎很简单,但如果操作不当,可能会引入许多小错误。例如,使用覆盖类类型的单个参数覆盖Object#equals方法是一个常见的错误:Foo类重写了Object#equals方法,因此可以测试Foo是否与Java中的任何其他对象相等。虽然我们的意图是正确的,但我们的实施却不正确。事实上,我们的实现根本没有覆盖Object#equals方法。相反,我们提供了方法的重载:我们没有替换Object类提供的equals方法的实现,而是提供了第二种方法,它专门接受Foo对象,而不是Object对象。我们的错误可以通过一个简单的实现来举例说明,该实现对所有相等性检查都返回true,但是当提供的对象被视为对象时(Java将执行的操作,例如在Java集合框架中,即JCF),那么它永远不会被调用:publicclassFoo{publicbooleanequals(Foofoo){returntrue;}}Objectfoo=newFoo();ObjectidenticalFoo=newFoo();System.out.println(foo.equals(identicalFoo));//false这是一个非常微妙但常见的错误被编译器捕获。我们的意图是覆盖Object#equals方法,但是因为我们指定了Foo类型的参数而不是Object,所以我们实际上提供了重载的Object#equals方法而不是覆盖它。为了捕获此类错误,我们引入了@Override注解,它指示编译器检查是否实际执行了覆盖。如果未执行有效覆盖,则会抛出错误。因此我们可以按如下方式更新Foo类:Override^1error本质上,我们已经将我们已经覆盖该方法的这种隐含假设转变为编译器的显式验证。如果我们的意图实现不正确,Java编译器将发出错误-不允许我们错误实现的代码被成功编译。通常,如果以下任何条件不成立,Java编译器将对使用@Override注释的方法发出错误(引用自Override注释文档):该方法确实覆盖或实现了在超类中声明的方法。此方法的签名与Object中声明的任何公共方法(即equals或hashCode方法)的签名等效。因此,我们也可以使用这个注释来确保子类方法实际上也覆盖超类中的非最终具体或抽象方法:;}@OverridepublicintdoSomethingElse(){return20;}}Foobar=newBar();System.out.println(bar.doSomething());//10System.out.println(bar.doSomethingElse());//20@重写注解不限于超类中的具体或抽象方法,还可以用于确保接口的方法也被重写(自JDK6起):}}Foobar=newBar();System.out.println(bar.doSomething());//10一般重写非final类方法、抽象超类方法、接口方法的方法都可以用@Override注解.有关有效覆盖的更多信息,请参阅《Overriding and Hiding》文档和《Java Language Specification (JLS)》的第9.6.4.4节。@FunctionalInterface随着JDK8中lambda表达式的引入,函数式接口在Java中变得越来越流行。这些特殊类型的接口可以被lambda表达式、方法引用或构造函数引用所取代。根据@FunctionalInterface文档,函数式接口定义如下:一个函数式接口只有一个抽象方法。由于默认方法有一个实现,它们不是抽象的。例如,以下接口被认为是功能接口:publicinterfaceFoo{publicintdoSomething();}publicinterfaceBar{publicintdoSomething();publicdefaultintdoSomethingElse(){return1;}}因此,以下每一项都可以用lambda表达式替换,如下所示:(bar.doSomething());}}FunctionalConsumerconsumer=newFunctionalConsumer();consumer.consumeFoo(()->10);//10consumer.consumeBar(()->20);//20需要注意的重要一点是抽象类,即使它们只包含一个抽象方法,也不是功能接口。有关详细信息,请参阅《Allow lambdas to implement abstract classes》由***Java语言架构师BrianGoetz撰写。与@Override注解类似,Java编译器提供了@FunctionalInterface注解来保证接口确实是函数式接口。例如,我们可以在上面创建的接口中添加这个注解:@FunctionalInterfacepublicinterfaceFoo{publicintdoSomething();}@FunctionalInterfacepublicinterfaceBar{publicintdoSomething();publicdefaultintdoSomethingElse(){return1;}}如果我们错误地将接口定义为非功能接口并且使用@FunctionalInterface注解了错误的接口,Java编译器会发出错误。例如,我们可以定义以下带注释的非功能接口:@FunctionalInterfacepublicinterfaceFoo{publicintdoSomething();publicintdoSomethingElse();}如果我们尝试编译这个接口,我们将收到以下错误:$javacFoo.javaFoo.java:1:error:Unexpected@FunctionalInterfaceannotation@FunctionalInterface^Fooisnotfunctionalinterfacemultiplenon-overridingabstractmethodsfoundininterfaceFoo1error使用这个注释,我们可以确保我们不会错误地创建旨在成为功能接口的非功能接口。重要的是要注意,即使没有@FunctionalInterface注释,该接口也可以用作功能接口(可以用lambda、方法引用和构造函数引用代替),正如我们在前面的示例中看到的那样。这类似于@Override注解,即一个方法即使不包含@Override注解也可以被覆盖。在这两种情况下,注释都是可选的技术,允许编译器强制执行所需的意图。有关@FunctionalInterface注释的更多信息,请参阅@FunctionalInterface文档和《JLS》的第4.6.4.9节。单击此处阅读有关Java10新功能的实践教程。@SuppressWarnings警告是所有编译器的重要组成部分,它向开发人员提供有关未来编译器版本中可能出现的潜在危险行为或错误的反馈。例如,在Java中使用没有其关联的正式泛型参数(称为原始类型)的泛型会导致警告,使用已弃用的代码也会产生警告(请参阅下面的@Deprecated部分)。尽管这些警告很重要,但它们可能并不总是适用,甚至可能并不正确。比如可能会出现不安全的类型转换会出现警告的情况,但是根据使用的上下文,我们可以保证它是安全的。为了在某??些上下文中忽略特定的警告,JDK5中引入了@SuppressWarnings注释。该注释接受一个或多个字符串参数——描述要忽略的警告的名称。虽然这些警告的名称通常因编译器实现而异,但有3个警告在Java语言中是标准化的(因此在所有Java编译器实现中都是通用的):类型转换是安全的),这可能是由于访问原始类型的成员(见《JLS》4.8),窄引用转换或不安全的向下转型(见《JLS》5.1.6),未经检查的类型转换(见《JLS》5.1.9),使用带有可变参数的泛型参数(见《JLS》8.4.1和下面的@SafeVarargs部分),使用无效的协变返回类型(见《JLS》8.4.8.3),不确定的参数评估(见《JLS》15.12。4.2),方法引用类型的未检查转换(见《JLS》15.13.2部分),或未检查的lambda类型对话(见《JLS》15.27.3部分)。deprecation:指示使用已弃用的方法、类、类型等的警告(请参阅《JLS》9.6.4.6和下面的@Deprecated部分)。移除:指示使用最终弃用的方法、类、类型等的警告(请参阅《JLS》9.6.4.6和下面的@Deprecated部分)。要忽略特定警告,请将@SuppressedWarning注释和一个或多个抑制警告的名称(以字符串数组的形式提供)添加到发生警告的上下文中:publicclassFoo{publicvoiddoSomething(@SuppressWarnings("rawtypes")ListmyList){//DosomethingwithmyList}}@SuppressWarnings注解可以用在以下任何一种情况下:typefieldmethodparameterconstructorlocalvariablemodule通常,@SuppressWarnings注解应该应用于最直接的警告范围。例如,如果方法中的局部变量应该忽略警告,@SuppressWarnings注释应该应用于局部变量,而不是包含局部变量的方法或类:publicclassFoo{publicvoiddoSomething(){@SuppressWarnings("rawtypes")ListmyList=newArrayList();//DosomethingwithmyList}}@SafeVarargsVarargs在Java中是一种有用的技术,但当与泛型参数一起使用时,它们也会导致一些严重的问题。由于泛型在Java中是不明确的,因此无法在运行时确定具有泛型类型的变量的实际(已实现)类型。由于无法做出此确定,因此变量可能会存储对不是其实际类型的类型的引用,如以下代码片段所示(取自《Java Generics FAQs》):Listln=newArrayList
