Java 中文官方教程 2022 版(四)(1)https://developer.aliyun.com/article/1486286
注解可以使用的位置
注解可以应用于声明:类、字段、方法和其他程序元素的声明。当用于声明时,每个注解通常按照惯例出现在自己的一行上。
从 Java SE 8 发布开始,注解也可以应用于类型的使用。以下是一些示例:
- 类实例创建表达式:
new @Interned MyObject();
- 类型转换:
myString = (@NonNull String) str;
implements
子句:
class UnmodifiableList<T> implements @Readonly List<@Readonly T> { ... }
- 抛出异常声明:
void monitorTemperature() throws @Critical TemperatureException { ... }
这种形式的注解称为类型注解。更多信息,请参见类型注解和可插入类型系统。
声明注解类型
原文:
docs.oracle.com/javase/tutorial/java/annotations/declaring.html
许多注解取代了代码中的注释。
假设一个软件组传统上在每个类的主体部分以提供重要信息的注释开头:
public class Generation3List extends Generation2List { // Author: John Doe // Date: 3/17/2002 // Current revision: 6 // Last modified: 4/12/2004 // By: Jane Doe // Reviewers: Alice, Bill, Cindy // class code goes here }
要使用注解添加相同的元数据,必须首先定义注解类型。这样做的语法是:
@interface ClassPreamble { String author(); String date(); int currentRevision() default 1; String lastModified() default "N/A"; String lastModifiedBy() default "N/A"; // Note use of array String[] reviewers(); }
注解类型定义看起来类似于接口定义,其中关键字interface
之前有一个 at 符号(@
)(@ = AT,表示注解类型)。注解类型是接口的一种形式,稍后会介绍。目前,您不需要理解接口。
前一个注解定义的主体包含注解类型元素声明,看起来很像方法。请注意,它们可以定义可选的默认值。
在定义注解类型之后,您可以像这样使用该类型的注解,填入值:
@ClassPreamble ( author = "John Doe", date = "3/17/2002", currentRevision = 6, lastModified = "4/12/2004", lastModifiedBy = "Jane Doe", // Note array notation reviewers = {"Alice", "Bob", "Cindy"} ) public class Generation3List extends Generation2List { // class code goes here }
注意: 要使@ClassPreamble
中的信息出现在 Javadoc 生成的文档中,必须使用@Documented
注解注释@ClassPreamble
定义:
// import this to use @Documented import java.lang.annotation.*; @Documented @interface ClassPreamble { // Annotation element definitions }
预定义的注解类型
原文:
docs.oracle.com/javase/tutorial/java/annotations/predefined.html
一组注解类型在 Java SE API 中预定义。一些注解类型由 Java 编译器使用,而一些适用于其他注解。
Java 语言使用的注解类型
在java.lang
中定义的预定义注解类型为@Deprecated
、@Override
和@SuppressWarnings
。
@Deprecated @Deprecated
注解表示标记的元素已被弃用,不应再使用。每当程序使用带有@Deprecated
注解的方法、类或字段时,编译器都会生成警告。当元素被弃用时,还应使用 Javadoc 的@deprecated
标签进行文档化,如下例所示。在 Javadoc 注释和注解中使用@
符号并非巧合:它们在概念上是相关的。另外,请注意,Javadoc 标签以小写d开头,而注解以大写D开头。
// Javadoc comment follows /** * *@deprecated* * *explanation of why it was deprecated* */ @Deprecated static void deprecatedMethod() { } }
@Override @Override
注解告诉编译器,该元素意在覆盖在超类中声明的元素。覆盖方法将在接口和继承中讨论。
// *mark method as a superclass method* // *that has been overridden* @Override int overriddenMethod() { }
虽然在覆盖方法时不是必须使用此注解,但它有助于防止错误。如果标记为@Override
的方法未能正确覆盖其超类中的方法,编译器将生成错误。
@SuppressWarnings @SuppressWarnings
注解告诉编译器抑制其通常生成的特定警告。在下面的示例中,使用了一个已弃用的方法,编译器通常会生成警告。然而,在这种情况下,该注解导致警告被抑制。
// *use a deprecated method and tell* // *compiler not to generate a warning* @SuppressWarnings("deprecation") void useDeprecatedMethod() { // deprecation warning // - suppressed objectOne.deprecatedMethod(); }
每个编译器警告都属于一个类别。Java 语言规范列出了两个类别:deprecation
和unchecked
。当与在泛型出现之前编写的旧代码进行接口时,可能会出现unchecked
警告。要抑制多个类别的警告,请使用以下语法:
@SuppressWarnings({"unchecked", "deprecation"})
@SafeVarargs @SafeVarargs
注解,当应用于方法或构造函数时,断言代码不对其varargs
参数执行潜在不安全的操作。使用此注解类型时,与varargs
使用相关的未经检查的警告将被抑制。
@FunctionalInterface @FunctionalInterface
注解,引入于 Java SE 8,指示类型声明旨在成为功能接口,如 Java 语言规范所定义。
适用于其他注解的注解
适用于其他注解的注解称为元注解。在java.lang.annotation
中定义了几种元注解类型。
@Retention @Retention
注解指定标记的注解如何存储:
RetentionPolicy.SOURCE
– 标记的注解仅在源级别保留,并被编译器忽略。RetentionPolicy.CLASS
– 标记的注解在编译时由编译器保留,但在 Java 虚拟机(JVM)中被忽略。RetentionPolicy.RUNTIME
– 标记的注解由 JVM 保留,因此可以被运行时环境使用。
@Documented @Documented
注解指示每当使用指定的注解时,应使用 Javadoc 工具记录这些元素。(默认情况下,注解不包含在 Javadoc 中。)有关更多信息,请参阅Javadoc 工具页面。
@Target @Target
注解标记另一个注解,限制注解可以应用于哪种 Java 元素。目标注解将以下元素类型之一指定为其值:
ElementType.ANNOTATION_TYPE
可以应用于注解类型。ElementType.CONSTRUCTOR
可以应用于构造函数。ElementType.FIELD
可以应用于字段或属性。ElementType.LOCAL_VARIABLE
可以应用于局部变量。ElementType.METHOD
可以应用于方法级别的注解。ElementType.PACKAGE
可以应用于包声明。ElementType.PARAMETER
可以应用于方法的参数。ElementType.TYPE
可以应用于类的任何元素。
@Inherited @Inherited
注解指示注解类型可以从超类继承。(默认情况下不是这样。)当用户查询注解类型并且类没有此类型的注解时,将查询类的超类以获取注解类型。此注解仅适用于类声明。
@Repeatable @Repeatable
注解,引入于 Java SE 8,表示标记的注解可以多次应用于同一声明或类型使用。有关更多信息,请参阅重复注解。
类型注解和可插拔类型系统
原文:
docs.oracle.com/javase/tutorial/java/annotations/type_annotations.html
在 Java SE 8 发布之前,注解只能应用于声明。从 Java SE 8 发布开始,注解也可以应用于任何类型使用。这意味着注解可以在使用类型的任何地方使用。一些类型使用的示例包括类实例创建表达式(new
)、强制转换、implements
子句和throws
子句。这种形式的注解称为类型注解,注解基础知识提供了几个示例。
类型注解是为了支持改进 Java 程序的分析方式以确保更强的类型检查而创建的。Java SE 8 发布没有提供类型检查框架,但允许您编写(或下载)一个作为一个或多个可插拔模块实现的类型检查框架,这些模块与 Java 编译器一起使用。
例如,您希望确保程序中的特定变量永远不会被赋予 null;您希望避免触发NullPointerException
。您可以编写一个自定义插件来检查这一点。然后,您将修改代码以注释该特定变量,指示它永远不会被赋予 null。变量声明可能如下所示:
@NonNull String str;
当您在命令行中编译代码时,包括NonNull
模块,如果编译器检测到潜在问题,它会打印警告,让您修改代码以避免错误。在您纠正代码以消除所有警告后,当程序运行时,这种特定错误将不会发生。
您可以使用多个类型检查模块,每个模块检查不同类型的错误。通过这种方式,您可以在需要时在 Java 类型系统的基础上构建,添加特定的检查。
通过谨慎使用类型注解和存在可插拔类型检查器,您可以编写更强大且更不容易出错的代码。
在许多情况下,您不必编写自己的类型检查模块。有第三方已经为您完成了这项工作。例如,您可能希望利用华盛顿大学创建的检查器框架。该框架包括一个NonNull
模块,以及一个正则表达式模块和一个互斥锁模块。更多信息,请参阅检查器框架。
可重复注解
原文:
docs.oracle.com/javase/tutorial/java/annotations/repeating.html
有一些情况下,您希望将相同的注解应用于声明或类型使用。从 Java SE 8 发布开始,可重复注解使您能够做到这一点。
例如,您正在编写代码以使用一个定时器服务,该服务使您能够在指定时间运行一个方法或按照某个计划运行,类似于 UNIX 的cron
服务。现在您想设置一个定时器在每个月的最后一天和每个星期五晚上 11 点运行一个doPeriodicCleanup
方法。要设置定时器运行,创建一个@Schedule
注解并将其应用两次于doPeriodicCleanup
方法。第一次使用指定了每月的最后一天,第二次指定了星期五晚上 11 点,如下面的代码示例所示:
@Schedule(dayOfMonth="last") @Schedule(dayOfWeek="Fri", hour="23") public void doPeriodicCleanup() { ... }
前面的示例对一个方法应用了一个注解。您可以在任何需要使用标准注解的地方重复使用注解。例如,您有一个处理未经授权访问异常的类。您为经理们注解了一个@Alert
注解,为管理员注解了另一个:
@Alert(role="Manager") @Alert(role="Administrator") public class UnauthorizedAccessException extends SecurityException { ... }
由于兼容性原因,重复注解存储在 Java 编译器自动生成的容器注解中。为了让编译器做到这一点,您的代码中需要两个声明。
第 1 步:声明一个可重复的注解类型
注解类型必须标记为@Repeatable
元注解。以下示例定义了一个自定义的@Schedule
可重复注解类型:
import java.lang.annotation.Repeatable; @Repeatable(Schedules.class) public @interface Schedule { String dayOfMonth() default "first"; String dayOfWeek() default "Mon"; int hour() default 12; }
@Repeatable
元注解的值(括号中)是 Java 编译器生成的用于存储重复注解的容器注解的类型。在本例中,包含注解类型是Schedules
,因此重复的@Schedule
注解存储在一个@Schedules
注解中。
在未声明为可重复之前将相同的注解应用于声明会导致编译时错误。
第 2 步:声明包含注解类型
包含注解类型必须具有一个带有数组类型的value
元素。数组类型的组件类型必须是可重复注解类型。Schedules
包含注解类型的声明如下:
public @interface Schedules { Schedule[] value(); }
检索注解
反射 API 中有几种可用的方法可用于检索注解。返回单个注解的方法的行为,例如AnnotatedElement.getAnnotation(Class),在只有一个请求类型的注解存在时保持不变。如果存在多个请求类型的注解,可以通过首先获取它们的容器注解来获取它们。通过这种方式,旧代码仍然可以正常工作。在 Java SE 8 中引入了其他方法,通过扫描容器注解一次返回多个注解,例如AnnotatedElement.getAnnotationsByType(Class)。请参阅AnnotatedElement类规范,了解所有可用方法的信息。
设计考虑事项
当设计注解类型时,您必须考虑该类型的注解的基数。现在可以使用一个注解零次,一次,或者,如果注解的类型标记为@Repeatable
,可以使用多次。还可以通过使用@Target
元注解来限制注解类型可以在哪里使用。例如,您可以创建一个可重复使用的注解类型,只能用于方法和字段。设计注解类型时要仔细考虑,以确保使用注解的程序员发现它尽可能灵活和强大。
问题和练习:注解
原文:
docs.oracle.com/javase/tutorial/java/annotations/QandE/questions.html
问题
- 以下接口有什么问题?
public interface House { @Deprecated void open(); void openFrontDoor(); void openBackDoor(); }
- 考虑
House
接口的以下实现,如问题 1 所示。
public class MyHouse implements House { public void open() {} public void openFrontDoor() {} public void openBackDoor() {} }
- 如果您编译此程序,编译器会产生警告,因为
open
已被弃用(在接口中)。您可以采取什么措施消除该警告? - 以下代码是否会编译出错?为什么?
public @interface Meal { ... } @Meal("breakfast", mainDish="cereal") @Meal("lunch", mainDish="pizza") @Meal("dinner", mainDish="salad") public void evaluateDiet() { ... }
练习
- 为增强请求定义一个注解类型,具有
id
、synopsis
、engineer
和date
元素。为工程师指定默认值为unassigned
,为日期指定默认值为unknown
。
检查您的答案。
课程:接口和继承
接口
在上一课中,您看到了实现接口的示例。您可以在这里阅读更多关于接口的信息——它们的作用是什么,为什么您可能想要编写一个,以及如何编写一个。
继承
这一部分描述了如何从一个类派生另一个类。也就是说,子类如何从超类继承字段和方法。您将了解到所有类都是从Object
类派生的,以及如何修改子类从超类继承的方法。本节还涵盖类似接口的抽象类。
接口
原文:
docs.oracle.com/javase/tutorial/java/IandI/createinterface.html
在软件工程中有许多情况下,不同组的程序员需要达成一致的“合同”,明确规定他们的软件如何交互。每个组都应该能够编写他们的代码,而不需要了解其他组的代码是如何编写的。一般来说,接口就是这样的合同。
例如,想象一个未来社会,在这个社会中,由计算机控制的机器人汽车在城市街道上运载乘客,没有人类操作员。汽车制造商编写软件(当然是 Java),操作汽车—停止、启动、加速、左转等等。另一个工业团体,电子导航仪制造商,制造接收 GPS(全球定位系统)位置数据和交通状况无线传输的计算机系统,并利用这些信息驾驶汽车。
汽车制造商必须发布一个行业标准接口,详细说明可以调用哪些方法来使汽车移动(任何制造商的任何汽车)。导航制造商可以编写调用接口中描述的方法来命令汽车的软件。两个工业团体都不需要知道*对方的软件是如何实现的。事实上,每个团体都认为自己的软件是高度专有的,并保留随时修改的权利,只要它继续遵守已发布的接口。
Java 中的接口
在 Java 编程语言中,接口是一种引用类型,类似于类,只能包含常量、方法签名、默认方法、静态方法和嵌套类型。方法体仅存在于默认方法和静态方法中。接口不能被实例化—它们只能被类实现或其他接口扩展。扩展将在本课程的后面讨论。
定义接口类似于创建新类:
public interface OperateCar { // constant declarations, if any // method signatures // An enum with values RIGHT, LEFT int turn(Direction direction, double radius, double startSpeed, double endSpeed); int changeLanes(Direction direction, double startSpeed, double endSpeed); int signalTurn(Direction direction, boolean signalOn); int getRadarFront(double distanceToCar, double speedOfCar); int getRadarRear(double distanceToCar, double speedOfCar); ...... // more method signatures }
请注意,方法签名没有大括号,并以分号结尾。
要使用接口,您需要编写一个实现接口的类。当一个可实例化的类实现一个接口时,它为接口中声明的每个方法提供一个方法体。例如,
public class OperateBMW760i implements OperateCar { // the OperateCar method signatures, with implementation -- // for example: public int signalTurn(Direction direction, boolean signalOn) { // code to turn BMW's LEFT turn indicator lights on // code to turn BMW's LEFT turn indicator lights off // code to turn BMW's RIGHT turn indicator lights on // code to turn BMW's RIGHT turn indicator lights off } // other members, as needed -- for example, helper classes not // visible to clients of the interface }
在上面的机器人汽车示例中,将实现接口的是汽车制造商。雪佛兰的实现肯定与丰田的实现大不相同,但两家制造商都会遵守相同的接口。作为接口的客户,导航制造商将构建使用汽车位置的 GPS 数据、数字街道地图和交通数据来驾驶汽车的系统。在这样做的过程中,导航系统将调用接口方法:转向、变道、刹车、加速等等。
接口作为 API
机器人汽车示例展示了一个作为行业标准*应用程序编程接口(API)*使用的接口。API 在商业软件产品中也很常见。通常,一家公司销售一个包含复杂方法的软件包,另一家公司希望在自己的软件产品中使用这些方法。一个例子是数字图像处理方法包,这些方法被销售给制作最终用户图形程序的公司。图像处理公司编写其类来实现一个接口,然后将其公开给客户。图形公司然后使用接口中定义的签名和返回类型调用图像处理方法。虽然图像处理公司的 API 是公开的(给其客户),但其 API 的实现被保持为严格保密的秘密—事实上,它可以在以后的某个日期修改实现,只要它继续实现客户依赖的原始接口。
定义一个接口
原文:
docs.oracle.com/javase/tutorial/java/IandI/interfaceDef.html
一个接口声明由修饰符、关键字interface
、接口名称、一个逗号分隔的父接口列表(如果有)、和接口主体组成。例如:
public interface GroupedInterface extends Interface1, Interface2, Interface3 { // constant declarations // base of natural logarithms double E = 2.718282; // method signatures void doSomething (int i, double x); int doSomethingElse(String s); }
public
访问修饰符表示接口可以被任何包中的任何类使用。如果不指定接口为 public,则接口只能被与接口在同一包中定义的类访问。
一个接口可以扩展其他接口,就像一个类可以子类化或扩展另一个类一样。然而,一个类只能扩展一个其他类,而一个接口可以扩展任意数量的接口。接口声明包括一个逗号分隔的所有它扩展的接口的列表。
接口主体
接口主体可以包含抽象方法,默认方法,和静态方法。接口中的抽象方法后跟一个分号,但不包含大括号(抽象方法不包含实现)。默认方法使用default
修饰符定义,静态方法使用static
关键字定义。接口中的所有抽象、默认和静态方法都隐式地是public
的,因此可以省略public
修饰符。
此外,一个接口可以包含常量声明。在接口中定义的所有常量值都隐式地是public
、static
和final
的。再次,你可以省略这些修饰符。
实现一个接口
原文:
docs.oracle.com/javase/tutorial/java/IandI/usinginterface.html
要声明一个实现接口的类,你需要在类声明中包含一个implements
子句。你的类可以实现多个接口,因此implements
关键字后面跟着一个逗号分隔的接口列表。按照惯例,如果有extends
子句,则implements
子句跟在其后。
一个示例接口,Relatable
考虑一个定义如何比较对象大小的接口。
public interface Relatable { // this (object calling isLargerThan) // and other must be instances of // the same class returns 1, 0, -1 // if this is greater than, // equal to, or less than other public int isLargerThan(Relatable other); }
如果你想要比较相似对象的大小,无论它们是什么,实例化它们的类应该实现Relatable
。
任何类都可以实现Relatable
,只要有一种方法可以比较从该类实例化的对象的相对“大小”。对于字符串,可以是字符数;对于书籍,可以是页数;对于学生,可以是体重;等等。对于平面几何对象,面积是一个不错的选择(参见下面的RectanglePlus
类),而对于三维几何对象,体积也可以工作。所有这些类都可以实现isLargerThan()
方法。
如果你知道一个类实现了Relatable
,那么你就知道可以比较从该类实例化的对象的大小。
实现 Relatable 接口
这里是在创建对象部分中介绍的Rectangle
类,重写以实现Relatable
。
public class RectanglePlus implements Relatable { public int width = 0; public int height = 0; public Point origin; // four constructors public RectanglePlus() { origin = new Point(0, 0); } public RectanglePlus(Point p) { origin = p; } public RectanglePlus(int w, int h) { origin = new Point(0, 0); width = w; height = h; } public RectanglePlus(Point p, int w, int h) { origin = p; width = w; height = h; } // a method for moving the rectangle public void move(int x, int y) { origin.x = x; origin.y = y; } // a method for computing // the area of the rectangle public int getArea() { return width * height; } // a method required to implement // the Relatable interface public int isLargerThan(Relatable other) { RectanglePlus otherRect = (RectanglePlus)other; if (this.getArea() < otherRect.getArea()) return -1; else if (this.getArea() > otherRect.getArea()) return 1; else return 0; } }
因为RectanglePlus
实现了Relatable
,所以可以比较任意两个RectanglePlus
对象的大小。
注意: 在Relatable
接口中定义的isLargerThan
方法接受一个Relatable
类型的对象。在前面的示例中加粗显示的代码行将other
强制转换为RectanglePlus
实例。类型转换告诉编译器对象的真实类型。直接在other
实例上调用getArea
(other.getArea()
)将无法编译通过,因为编译器不知道other
实际上是RectanglePlus
的实例。
Java 中文官方教程 2022 版(四)(3)https://developer.aliyun.com/article/1486288