Oracle 专业认证 JavaSE8 编程测验(四)
七、异常和断言
| 认证目标 |
|---|
| 使用 try-catch 和 throw 语句 |
| 使用 catch、multi-catch 和 finally 子句 |
| 将自动关闭资源与 try-with-resources 语句一起使用 |
| 创建自定义异常和可自动关闭的资源 |
| 使用断言测试不变量 |
在这一章中,你将详细了解 Java 对异常处理的支持。OCAJP 8 级考试(OCPJP 8 级考试的先决条件)将异常处理的基础知识作为考试主题。因此,我们假设您已经熟悉用于异常处理的基本语法和异常类型。在这一章中,你将学习如何提供 try、catch、multi-catch 和 finally block。您还将了解最近添加的语言特性,例如 try-with-resources 和 multi-catch 语句。接下来,您将学习如何定义自己的异常类(自定义异常)。最后,我们将讨论断言的相关主题,并教你如何在你的程序中使用它们。本章中的许多编程示例利用 I/O 函数(第九章和第十章)来说明异常处理的概念。
Throwable 及其子类
在 Java 中,抛出的对象应该是类Throwable或其子类之一的实例(Throwable是 Java 中异常层次结构的顶点类)。像throw语句、throws子句和catch子句这样的异常处理构造只处理Throwable及其子类。您需要详细了解Throwable的三个重要子类:Error、Exception和RuntimeException类。图 7-1 提供了这些类的高级概述。
图 7-1。
Java’s exception hierarchy
下面是对扩展了Throwable类的三个重要类的简要描述:
- 类型为
Exception的异常被称为检查异常。如果代码可以抛出一个Exception,您必须使用 catch 块来处理它,或者声明该方法抛出该异常,强制该方法的调用者处理该异常。 RuntimeException是Exception类的派生类。从此类派生的异常称为未检查异常。处理未检查的异常是可选的。如果您在方法中编写的代码段会引发未检查的异常,则不一定要捕获该异常或在该方法的 throws 子句中声明该异常。- 当 JVM 在程序中检测到严重的异常情况时,它会引发类型为
Error的异常。类型Error的异常表示程序中的异常情况。捕捉这个异常并试图继续执行和假装什么都没发生是没有意义的。这样做实在是不好的做法!
现在,让我们开始讨论如何抛出和捕捉异常。
抛出异常
| 认证目标 |
|---|
| 使用 try-catch 和 throw 语句 |
清单 7-1 是一个非常简单的编程示例,其中您希望将作为命令行参数键入的文本回显给用户。假设用户必须键入一些文本作为要回应的命令行参数,否则您需要通知用户“错误情况”
Listing 7-1. Echo.java
// A simple program without exception handling code
class Echo {
public static void main(String []args) {
if(args.length == 0) {
// no arguments passed – display an error to the user
System.out.println("Error: No input passed to echo command… ");
System.exit(-1);
}
else {
for(String str : args) {
// command-line arguments are separated and passed as an array
// print them by adding a space between the array elements
System.out.print(str + " ");
}
}
}
}
在这种情况下,您使用一个println()语句在控制台中打印错误。这是一个简单的程序,错误发生在main()方法中,所以错误处理很容易。在这种情况下,您可以在将错误信息打印到控制台后终止程序。然而,如果您在一个复杂的应用中深入函数调用,您需要一种更好的方法来指示“异常情况”已经发生,然后通知调用者该情况。此外,您通常需要从错误状态中恢复,而不是终止程序。因此,您需要能够“处理”一个异常,或者在调用堆栈中进一步“再抛出”该异常,以便调用者可以处理该异常。(我们将在本章的后面再讨论这个重新抛出异常的主题。)目前,您将修改清单 7-1 中的程序来抛出一个异常,而不是打印一条错误消息(在一个单独的程序Echo1.java)中),如下所示:
if(args.length == 0) {
// no arguments passed - throw an exception
throw new IllegalArgumentException("No input passed to echo command");
}
args.length == 0的if条件内的这个程序块是该程序中唯一需要更改的部分。注意抛出异常的语法:关键字throw后跟异常对象。这里您使用了已经在 Java 库中定义的IllegalArgumentException。在本章的后面,你将看到如何定义你自己的异常。
现在,如果您在命令行中没有传递任何参数的情况下运行这个程序,程序将抛出一个IllegalArgumentException:
Exception in thread "main" java.lang.IllegalArgumentException: No input passed to echo command
at Echo1.main(Echo1.java:5)
由于这个异常没有处理程序,这个未被捕获的异常终止了程序。在这种情况下,您显式地抛出了一个异常。当您编写一些代码或调用 Java APIs 时,也可能会抛出异常。现在我们来看一个例子。
未处理的异常
考虑清单 7-2 中的程序,它试图读取用户在控制台中键入的整数值,并将读取的整数打印回控制台。为了从控制台读取一个整数,您使用了在java.util.Scanner类中提供的nextInt()方法。要实例化Scanner类,您需要传入System.in,这是对系统输入流的引用。
Listing 7-2. ScanInt1.java
// A simple progam to accept an integer from user
import java.util.Scanner;
class ScanInt1 {
public static void main(String [] args) {
System.out.println("Type an integer in the console: ");
Scanner consoleScanner = new Scanner(System.in);
System.out.println("You typed the integer value: " + consoleScanner.nextInt());
}
}
当您运行此程序并在控制台中键入一个整数,比如 10,程序会正常工作并成功地将该整数打印出来。
D:\> java ScanInt1
Type an integer in the console:
10
You typed the integer value: 10
但是,如果您(或程序的用户)错误地键入了字符串“ten”而不是整数值“10”怎么办?程序将在抛出如下异常后终止:
D:\> java ScanInt1
Type an integer in the console:
ten
Exception in thread "main" java.util.InputMismatchException
at java.util.Scanner.throwFor(Scanner.java:909)
at java.util.Scanner.next(Scanner.java:1530)
at java.util.Scanner.nextInt(Scanner.java:2160)
at java.util.Scanner.nextInt(Scanner.java:2119)
at ScanInt.main(ScanInt1.java:7)
如果你读了nextInt()的文档,你会看到这个方法可以抛出InputMismatchException -"如果下一个令牌不匹配Integer正则表达式,或者超出范围。"在这个简单的程序中,假设您(或用户)将总是按预期键入整数值,当假设失败时,将抛出一个异常。如果一个程序抛出了一个异常,并且它没有被处理,那么在抛出一个堆栈跟踪之后,程序将会异常终止,就像这里显示的那样。
堆栈跟踪显示在控件到达引发异常的语句之前调用的方法列表(带有行号)。作为一名程序员,您会发现跟踪控制流对于调试程序和修复导致该异常的问题非常有用。
那么,你如何处理这种情况呢?您需要将这段代码放在 try 和 catch 块中,然后处理异常。
Try 和 Catch 语句
| 认证目标 |
|---|
| 使用 catch、multi-catch 和 finally 子句 |
Java 提供了try和catch关键字来处理您编写的代码中可能抛出的任何异常。清单 7-3 是清单 7-2 程序的改进版本。
Listing 7-3. ScanInt2.java
// A simple progam to accept an integer from user in normal case,
// otherwise prints an error message
import java.util.Scanner;
import java.util.InputMismatchException;
class ScanInt2 {
public static void main(String [] args) {
System.out.println("Type an integer in the console: ");
Scanner consoleScanner = new Scanner(System.in);
try {
System.out.println("You typed the integer value: " + consoleScanner.nextInt());
} catch(InputMismatchException ime) {
// nextInt() throws InputMismatchException in case anything
// other than an integer is typed in the console; so handle it
System.out.println("Error: You typed some text that is not an integer value…");
}
}
}
如果在输入中键入了有效整数以外的任何内容,该程序会向用户打印一条可读的错误消息。
D:\> java ScanInt2
Type an integer in the console:
ten
Error: You typed some text that is not an integer value…
现在让我们来分析这段代码。后面跟有try关键字的代码块限制了代码段,您预计可能会抛出一些异常。如果从try块抛出任何异常,Java 运行时将搜索匹配的处理程序(稍后我们将更详细地讨论)。在这种情况下,出现了一个InputMismatchException的异常处理程序,它与抛出的异常类型完全相同。这个完全匹配的 catch 处理程序可以在 try 块之外以关键字catch开头的块的形式获得,并且这个 catch 块被执行。在 catch 块中,您捕获了异常,所以您在这里处理异常。你提供了一个人类可读的错误字符串,而不是抛出一个原始的堆栈跟踪(正如你在清单 7-2 中的早期程序中所做的),所以你为程序提供了一个优雅的退出。
多抓块
在清单 7-2 中,您使用了一个Scanner对象从控制台读取一个整数。注意,你也可以使用一个Scanner对象来读取一个字符串(参见清单 7-4 )。
Listing 7-4. ScanInt3.java
// A program that scans an integer from a given string
import java.util.Scanner;
import java.util.InputMismatchException;
class ScanInt3 {
public static void main(String [] args) {
String integerStr = "100";
System.out.println("The string to scan integer from it is: " + integerStr);
Scanner consoleScanner = new Scanner(integerStr);
try {
System.out.println("The integer value scanned from string is: " +
consoleScanner.nextInt());
} catch(InputMismatchException ime) {
// nextInt() throws InputMismatchException in case
// anything other than an integeris provided in the string
System.out.println("Error: Cannot scan an integer from the given string");
}
}
}
该程序打印以下内容:
The string to scan integer from it is: 100
The integer value scanned from string is: 100
如果您修改清单 7-4 中的程序,使字符串包含一个非整数值,如
String integerStr = "hundred";
try 块将抛出一个InputMismatchException,它将在 catch 块中处理,您将得到以下输出:
The string to scan integer from it is: hundred
Error: Cannot scan an integer from the given string
现在,如果您修改清单 7-4 中的程序,使字符串包含一个空字符串,如
String integerStr = "";
为此,nextInt()会抛出一个NoSuchElementException,这个程序没有处理,所以这个程序会崩溃。
The string to scan integer from it is:
Exception in thread "main" java.util.NoSuchElementException
at java.util.Scanner.throwFor(Scanner.java:907)
at java.util.Scanner.next(Scanner.java:1530)
at java.util.Scanner.nextInt(Scanner.java:2160)
at java.util.Scanner.nextInt(Scanner.java:2119)
at ScanInt3.main(ScanInt.java:11)
此外,如果您查看 JavaDoc for Scanner.nextInt()方法,您会发现它也可以抛出一个IllegalStateException(如果在一个已经关闭的Scanner对象上调用nextInt()方法,就会抛出这个异常)。因此,让我们为InputMismatchException、NoSuchElementException和IllegalStateException提供捕捉处理程序(参见清单 7-5 )。
Listing 7-5. ScanInt4.java
// A program that scans an integer from a given string
import java.util.Scanner;
import java.util.InputMismatchException;
import java.util.NoSuchElementException;
class ScanInt4 {
public static void main(String [] args) {
String integerStr = "";
System.out.println("The string to scan integer from it is: " + integerStr);
Scanner consoleScanner = new Scanner(integerStr);
try {
System.out.println("The integer value scanned from string is: " +
consoleScanner.nextInt());
} catch(InputMismatchException ime) {
System.out.println("Error: Cannot scan an integer from the given string");
} catch(NoSuchElementException nsee) {
System.out.println("Error: Cannot scan an integer from the given string");
} catch(IllegalStateException ise) {
System.out.println("Error: nextInt() called on a closed Scanner object");
}
}
}
以下是运行该程序时的输出:
The string to scan integer from it is:
Error: Cannot scan an integer from the given string
正如您在输出中看到的,由于字符串是空的,NoSuchElementException被抛出。它在这个异常的 catch 处理程序中被捕获,catch 块中提供的代码被执行以导致一个优雅的退出。
请注意,您是如何通过堆叠它们来提供多个 catch 处理程序的:您提供了特定的(即派生类型)异常处理程序,然后是更通用的(即基本类型)异常处理程序。如果在基本异常类型之后提供派生异常类型,则会出现编译器错误。你可能还不知道,但是NoSuchElementException是InputMismatchException的基类!看看当您试图颠倒InputMismatchException和NoSuchElementException的 catch 处理程序的顺序时会发生什么。
try {
System.out.println("The integer value scanned from string is: "
+ consoleScanner.nextInt());
} catch(NoSuchElementException nsee) {
System.out.println("Error: Cannot scan an integer from the given string");
} catch(InputMismatchException ime) {
System.out.println("Error: Cannot scan an integer from the given string");
}
这段代码将导致以下编译器错误:
ScanInt4.java:14: error: exception InputMismatchException has already been caught
} catch(InputMismatchException ime) {
^
1 error
提供多个 catch 处理程序时,先处理特定异常,再处理一般异常。如果在基类异常处理程序之后提供派生类异常捕获处理程序,您的代码将不会编译。
多抓块
Java 提供了一个名为 multi-catch blocks 的特性,您可以在其中组合多个 catch 处理程序。让我们使用这个特性来组合NoSuchElementException和IllegalStateException的 catch 子句(参见清单 7-6 ):
Listing 7-6. ScanInt5.java
// A program that illustrates multi-catch blocks
import java.util.Scanner;
import java.util.NoSuchElementException;
class ScanInt5 {
public static void main(String [] args) {
String integerStr = "";
System.out.println("The string to scan integer from it is: " + integerStr);
Scanner consoleScanner = new Scanner(integerStr);
try {
System.out.println("The integer value scanned from string is: " +
consoleScanner.nextInt());
} catch(NoSuchElementException | IllegalStateException multie) {
System.out.println("Error: An error occured while attempting to scan the integer");
}
}
}
注意如何使用这里的| (OR)操作符(对整数值执行逐位OR操作的操作符)将 catch 处理程序组合在一起,以组合NoSuchElementException和IllegalStateException的 catch 子句。
与组合的NoSuchElementException和IllegalStateException的 catch 子句不同,您不能组合NoSuchElementException和InputMismatchException的 catch 子句。正如我们已经讨论过的,NoSuchElementException是InputMismatchException的基类,你不能在多重捕获块中同时捕获它们。如果您尝试编译这样一个多重捕获子句,您将得到以下编译器错误:
ScanInt5.java:11: error: Alternatives in a multi-catch statement cannot be related by subclassing
} catch(InputMismatchException | NoSuchElementException exception) {
^
那么还有什么选择呢?当您需要这样的异常捕获处理程序,其中一个异常是另一个异常类的基类时,只为基类提供捕获处理程序就足够了(因为基类捕获处理程序将在派生类异常发生时处理它)。
在 multi-catch 块中,不能为共享基类和派生类关系的两个异常组合 catch 处理程序。对于不共享父子关系的异常,只能组合 catch 处理程序。
如何知道合并异常处理块好还是堆叠好?这是一个设计选择,您必须考虑以下几个方面:(a)抛出异常的原因是相似的还是不同的?(b)处理代码相似还是不同?如果两个问题你都回答“差不多”,不如合并;如果你对这两个问题中的任何一个说“不一样”,那么还是分开来说比较好。
清单 7-6 具体情况如何?是组合还是分离InputMismatchException和IllegalStateException异常的处理程序更好?您可以看到两个 catch 块的异常处理是相同的。但是这两个异常的原因是相当不同的。InputMismatchException抛出无效输入被传递(例如,我们前面讨论的“百”)。在调用了Scanner上的close()方法之后,当您调用nextInt()方法时,IllegalStateException会因为编程错误而被抛出。因此,在这种情况下,将这两个异常的处理程序分开是更好的设计选择。
一般捕获处理程序
您是否注意到,当您使用与 I/O 操作相关的 API 时,会抛出许多异常?我们刚刚讨论过,为了调用Scanner类的一个方法nextInt(),您需要处理三个异常:InputMismatchException、NoSuchElementException和IllegalStateException。如果您继续处理像这样的特定异常,那么在您运行程序时,可能会也可能不会导致异常情况,您的大部分代码将由 try-catch 代码块组成!“处理所有其他异常”有没有更好的说法?是的,您可以提供一个通用的异常处理程序。
下面的代码片段只显示了清单 7-4 中的类ScanInt3的 try-catch 块,增强了一个通用异常处理程序:
try {
System.out.println("You typed the integer value: " + consoleScanner.nextInt());
} catch(InputMismatchException ime) {
// if something other than integer is typed, we'll get this exception, so handle it
System.out.println("Error: You typed some text that is not an integer value…");
} catch(Exception e) {
// catch IllegalStateException here which is unlikely to occur…
System.out.println("Error: Encountered an exception and could not read an integer from the console… ");
}
这段代码为类型Exception的基本异常提供了一个 catch 处理程序。因此,如果 try 块抛出除了InputMismatchException之外的任何异常,并且该异常是Exception类的派生类,这个通用的 catch 处理程序将处理它。推荐的做法是捕捉特定的异常,然后提供一个通用的异常处理程序,以确保所有其他异常也得到处理。
释放资源
您是否注意到我们在清单 7-2 、 7-3 和 7-4 中讨论的程序有资源泄漏(因为我们打开了一个Scanner对象但没有关闭它)?单词“resource”是指从底层操作系统获取一些系统资源的任何类,例如网络、文件、数据库和其他句柄。但是你怎么知道哪些课需要停课呢?答案是,如果一个类实现了java.io.Closeable,那么你必须调用那个类的close()方法;否则,将会导致资源泄漏。
垃圾收集器(GC)只负责释放内存资源。如果您正在使用任何获取系统资源的类,那么您有责任通过调用该对象上的
close()方法来释放它们。
ScanInt6(清单 7-7 )在其main()方法中调用Scanner对象的close()方法;您希望缩短代码,所以您将使用一个通用的异常处理程序来处理所有可能在 try 块中抛出的异常。
Listing 7-7. ScanInt6.java
import java.util.Scanner;
class ScanInt6 {
public static void main(String [] args) {
System.out.println("Type an integer in the console: ");
Scanner consoleScanner = new Scanner(System.in);
try {
System.out.println("You typed the integer value: " + consoleScanner.nextInt());
System.out.println("Done reading the text… closing the Scanner");
consoleScanner.close();
} catch(Exception e) {
// call all other exceptions here …
System.out.println("Error: Encountered an exception and could not read an integer from the console… ");
System.out.println("Exiting the program - restart and try the program again!");
}
}
}
让我们看看这个程序是否有效。
D:\> java ScanInt6
Type an integer in the console:
10
You typed the integer value: 10
Done reading the text… closing the Scanner
因为程序打印了"Done reading the text… closing the Scanner",并且正常完成了执行,所以可以假设语句consoleScanner.close() ;已经执行成功。如果抛出异常会发生什么?
D:\> java ScanInt6
Type an integer in the console:
ten
Error: Encountered an exception and could not read an integer from the console…
Exiting the program - restart and try the program again!
从输出中可以看到,程序没有打印"Done reading the text… closing the Scanner",所以语句consoleScanner.close();没有执行。你能如何修理它?一种方法是在 catch 块中调用consoleScanner.close(),就像这样:
try {
System.out.println("You typed the integer value: " + consoleScanner.nextInt());
System.out.println("Done reading the text… closing the Scanner");
consoleScanner.close();
} catch(Exception e) {
// call all other exceptions here …
consoleScanner.close();
System.out.println("Error: Encountered an exception and could not read an integer from the console… ");
System.out.println("Exiting the program - restart and try the program again!");
}
这种解决方案可行,但并不优雅。您知道您可以有多个 catch 块,并且您必须在所有 catch 块中提供对consoleScanner.close();的调用!有没有更好的释放资源的方法?是的,你可以在一个finally块中使用发布资源(参见清单 7-8 )。
Listing 7-8. ScanInt7.java
import java.util.Scanner;
class ScanInt7 {
public static void main(String [] args) {
System.out.println("Type an integer in the console: ");
Scanner consoleScanner = new Scanner(System.in);
try {
System.out.println("You typed the integer value: " + consoleScanner.nextInt());
} catch(Exception e) {
// call all other exceptions here …
System.out.println("Error: Encountered an exception and could not read an integer from the console… ");
System.out.println("Exiting the program - restart and try the program again!");
} finally {
System.out.println("Done reading the integer… closing the Scanner");
consoleScanner.close();
}
}
}
在这种情况下,在 catch 块之后提供了一个finally块。无论异常是否发生,这个 finally 块都将被执行。所以,finally块是调用Scanner对象上的close()方法的好地方,以确保这个资源总是被释放。
如果你在一个方法内部调用
System.exit(),它将异常终止程序。因此,如果调用方法有一个 finally 块,它将不会被调用,资源可能会泄漏。由于这个原因,调用System.exit()来终止一个程序是一个糟糕的编程实践。
现在,让我们看看在程序正常完成(即没有抛出异常)和程序在抛出异常后终止的情况下,扫描器是否关闭。
D:\> java ScanInt7
Type an integer in the console:
10
You typed the integer value: 10
Done reading the integer… closing the Scanner
D:\> java ScanInt7
Type an integer in the console:
ten
Error: Encountered an exception and could not read an integer from the console…
Exiting the program - restart and try the program again!
Done reading the integer… closing the Scanner
是的,不管是否抛出异常,都会调用语句"Done reading the integer… closing the Scanner"。请注意,您可以在没有catch块的情况下,在try块之后直接有一个finally块;虽然这个特性很少使用,但它是一个有用的特性。
注意:finally块总是被执行,不管try块中的代码是否抛出异常。考虑下面的方法。它会向调用者返回 true 还是 false?
boolean returnTest() {
try {
return true;
}
finally {
return false;
}
}
这个方法将总是返回 false,因为finally总是被调用,尽管它是不直观的。事实上,如果您使用"-Xlint"选项,您将得到这个编译器警告:“finally 子句不能正常完成。”(注意,你可以有一个try块,后面跟着catch块或finally块,或者两个块都跟着。)
抛出条款
方法可以抛出检查过的异常;子句 throw 在方法签名中指定了这些检查过的异常。在throws子句中,您列出了一个方法可能抛出的检查过的异常。为什么我们需要抛出条款?通过查看 throws 子句,您可以清楚地了解该方法可以抛出什么异常。理解检查异常是理解 throws 子句的先决条件。由于我们已经在前一节异常类型中讨论了检查异常,现在我们将讨论 throws 子句。
让我们试着读取当前目录中名为integer.txt的文件中存储的一个整数。有一个Scanner类的重载构造函数,它接受一个File对象作为输入,所以让我们试着使用它。清单 7-9 显示了程序。有用吗?
Listing 7-9. ThrowsClause1.java
import java.io.File;
import java.util.Scanner;
class ThrowsClause1 {
public static void main(String []args) {
System.out.println("Reading an integer from the file 'integer.txt': ");
Scanner consoleScanner = new Scanner(new File("integer.txt"));
System.out.println("You typed the integer value: " + consoleScanner.nextInt());
}
}
这段代码将导致编译器错误"unreported exception FileNotFoundException; must be caught or declared to be thrown"。如果您查看这个Scanner方法的声明,您会看到一个 throws 子句:
public Scanner(File source) throws FileNotFoundException {
因此,任何调用这个构造函数的方法都应该要么处理这个异常,要么添加一个 throws 子句来声明该方法可以抛出这个异常。向main()方法添加一个 throws 子句;参见清单 7-10 。
Listing 7-10. ThrowsClause2.java
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
class ThrowsClause2 {
public static void main(String []args) throws FileNotFoundException {
System.out.println("Reading an integer from the file 'integer.txt': ");
Scanner consoleScanner = new Scanner(new File("integer.txt"));
System.out.println("You typed the integer value: " + consoleScanner.nextInt());
}
}
如果您运行这个程序,并且没有名为integer.txt的文件,程序将在抛出这个异常后崩溃:
Reading an integer from the file 'integer.txt':
Exception in thread "main" java.io.FileNotFoundException: integer.txt (The system cannot find the file specified)
at java.io.FileInputStream.open(Native Method)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at java.util.Scanner.<init>(Scanner.java:656)
at ThrowsClause2.main(ThrowsClause2.java:7)
现在让我们将main()方法中的代码提取到一个名为readIntFromFile()的新方法中。您已经将它定义为一个实例方法,所以您还创建了一个ThrowsClause3类的对象来从main()方法中调用这个方法。由于readIntFromFile()内部的代码可以抛出一个FileNotFoundException,它必须要么引入一个 catch 处理程序来处理这个异常,要么在其 throws 子句中声明这个异常(参见清单 7-11 )。
Listing 7-11. ThrowsClause3.java
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
class ThrowsClause3 {
// since this method does not handle FileNotFoundException,
// the method must declare this exception in the throws clause
public int readIntFromFile() throws FileNotFoundException {
Scanner consoleScanner = new Scanner(new File("integer.txt"));
return consoleScanner.nextInt();
}
// since readIntFromFile() throws FileNotFoundException and main() does not handle
// it, the main() method declares this exception in its throws clause
public static void main(String []args) throws FileNotFoundException {
System.out.println("Reading an integer from the file 'integer.txt': ");
System.out.println("You typed the integer value: "
+ new ThrowsClause3().readIntFromFile());
}
}
在清单 7-10 和 7-11 中,程序的行为保持不变。然而,清单 7-11 显示了main()方法也必须声明如何在其 throws 子句中抛出FileNotFoundException(否则,程序将无法编译)。
方法重写和 Throws 子句
当一个可重写的方法有一个 throws 子句时,在重写该方法时有许多事情要考虑。考虑清单 7-12 中的程序,它实现了一个名为IntReader的接口。这个接口声明了一个名为readIntFromFile()的方法,其中的 throws 子句列出了一个FileNotFoundException。
Listing 7-12. ThrowsClause4.java
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Scanner;
// This interface is meant for implemented by classes that would read an integer from a file
interface IntReader {
int readIntFromFile() throws IOException;
}
class ThrowsClause4 implements IntReader {
// implement readIntFromFile with the same throws clause
// or a more specific throws clause
public int readIntFromFile() throws FileNotFoundException {
Scanner consoleScanner = new Scanner(new File("integer.txt"));
return consoleScanner.nextInt();
}
// main method elided in this code since the focus here is to understand
// issues related to overriding when throws clause is present
}
在这段代码中,您可以观察到一些重要的事实:
-
可以为接口中声明的方法声明 throws 子句;事实上,您也可以为在抽象类中声明的抽象方法提供 throws 子句。
-
The method declared in the
IntReaderinterface declares to throwIOException, which is a more general exception than aFileNotFoundException(Figure 7-2). While implementing a method, it is acceptable to either provide the throws clause listing the same exception type as the base method or a more specific type than the base method. In this case, thereadIntFromFile()method lists a more specific exception (FileNotFoundException) in its throws clause against the more general exception ofIOExceptionlisted in the throws clause of the base method declared in theIntReaderinterface.图 7-2。
Class hierarchy of FileNotFoundException
如果你试着改变 throws 条款呢?有许多方法可以更改 overriding 方法中的 throws 子句,包括:
Listing more general checked exceptions to throw. Listing more checked exceptions in addition to the given checked exception(s) in the base method.
如果您尝试这些情况中的任何一种,您都会得到一个编译器错误。例如,如果提供比基类中指定的更一般的异常,将导致编译器错误。
如果被重写的方法不抛出任何已检查的异常,或者如果抛出,它提供一个 try-catch 块,则可以选择不在被重写的方法中使用 throws 子句指定任何异常。
总而言之,基类方法的 throws 子句是它提供给该方法的调用方的契约:它规定调用方应该处理列出的异常或在其 throws 子句中声明这些异常。当重写基方法时,派生方法也应该遵守该协定。基方法的调用方只准备处理基方法中列出的异常,因此重写方法不能引发更一般的异常或除列出的已检查异常之外的异常。
但是,请注意,关于派生类方法的 throws 子句应该遵循基方法的 throws 子句的约定的讨论仅限于检查异常。与基类方法的 throws 子句相比,仍可以在协定中添加或移除未检查的异常。例如,考虑以下情况:
public int readIntFromFile() throws IOException, NoSuchElementException {
Scanner consoleScanner = new Scanner(new File("integer.txt"));
return consoleScanner.nextInt();
}
这是一个可接受的 throws 子句,因为NoSuchElementException可以从readIntFromFile()方法中抛出。这个异常是一个未检查的异常,当nextInt()方法不能从文件中读取整数时抛出。这是一种常见的情况,例如,如果您有一个名为integer.txt;的空文件,试图从该文件中读取一个整数将导致这个异常。
@Throws Tag
使用@throws JavaDoc 标签(或者它的同义词@exception标签)来记录一个方法可能抛出(未检查的或者检查的)异常的具体情况或者情况是一个好的实践。下面是提供@throws标签的格式和一个例子:
@throws exception-name description-text
@throws IllegalStateException if this scanner is closed
此标记只能用于方法和构造函数。
下面是Scanner类中nextInt()方法的 JavaDoc 注释示例:
/**
* Scans the next token of the input as an <tt>int</tt>.
*
* <p> An invocation of this method of the form
* <tt>nextInt()</tt> behaves in exactly the same way as the
* invocation <tt>nextInt(radix)</tt>, where <code>radix</code>
* is the default radix of this scanner.
*
* @return the <tt>int</tt> scanned from the input
* @throws InputMismatchException
* if the next token does not match the <i>Integer</i>
* regular expression, or is out of range
*``@throws NoSuchElementException
*``@throws``IllegalStateException
*/
public int nextInt() {
return nextInt(defaultRadix);
}
注意InputMismatchException、NoSuchElementException和IllegalStateException的@throws标签。当一个方法可以抛出多个异常时,它们按照惯例按字母顺序列出(就像本例中一样)。
要记住的要点
以下是关于 throws 语句的一些值得注意的要点,可能对你参加 OCPJP 八级考试有所帮助:
- 如果一个方法没有 throws 子句,并不意味着它不能抛出任何异常;这只是意味着它不能抛出任何检查过的异常。
- 静态初始化块不能抛出任何已检查的异常。为什么呢?请记住,静态初始化块是在加载类时调用的,因此没有办法处理调用程序中抛出的异常。此外,没有办法在 throws 子句中声明被检查的异常(因为它们是块,而不是方法)。
- 非静态初始化块可能会引发检查过的异常;然而,所有的构造函数都应该在它们的 throws 子句中声明这些异常。为什么呢?编译器在其代码生成阶段合并非静态初始化块和构造函数的代码,因此构造函数的 throws 子句可用于声明非静态初始化块可能引发的检查异常。
- 重写方法在 throws 子句中声明的检查异常不能多于基方法的 throws 子句中声明的异常列表。为什么呢?基方法的调用方只能看到该方法的 throws 子句中给出的异常列表,并将在其代码中声明或处理这些检查过的异常(仅此而已)。
- 重写方法可以声明比基方法的 throws 子句中列出的异常更具体的异常;换句话说,您可以在重写方法的 throws 子句中声明派生异常。
- 如果一个方法在两个或多个接口中声明,并且如果该方法在 throws 子句中声明抛出不同的异常,则该方法实现应该列出所有这些异常。
链接和重新引发异常
您可以捕捉异常,将它们包装成更一般的异常,并在调用堆栈中将其抛出。当您捕获一个异常并创建一个更一般的异常时,您可以保留对原始异常的引用;这被称为异常链接。
catch(LowLevelException lle) {
// wrap the low-level exception to a higher-level exception;
// also, chain the original exception to the newly thrown exception
throw new HighLevelException(lle);
}
链接异常对于调试非常有用。当您得到一个一般的异常时,您可以检查是否有一个连锁的较低级别的异常,并尝试理解为什么会发生那个较低级别的异常。
用资源尝试
| 认证目标 |
|---|
| 将自动关闭资源与 try-with-resources 语句一起使用 |
Java 程序员很容易忘记释放资源,即使是在 finally 块中。此外,如果您正在处理多个资源,记住在 finally 块中调用close()方法是很繁琐的。Try-with-resources 特性(在 Java 7 中引入)将有助于简化您的工作。清单 7-13 利用了这一特点;它是清单 7-8 的改进版本,清单 7-8 明确地调用了 close,如在consoleScanner.close()中。
Listing 7-13. TryWithResources1.java
import java.util.Scanner;
class TryWithResources1 {
public static void main(String [] args) {
System.out.println("Type an integer in the console: ");
try(Scanner consoleScanner = new Scanner(System.in)) {
System.out.println("You typed the integer value: " + consoleScanner.nextInt());
} catch(Exception e) {
// catch all other exceptions here …
System.out.println("Error: Encountered an exception and could not read an integer from the console… ");
System.out.println("Exiting the program - restart and try the program again!");
}
}
}
请务必仔细查看 try-with-resources 块的语法。
try(Scanner consoleScanner = new Scanner(System.in)) {
在这个语句中,您已经获得了在try关键字之后,但是在try块之前的括号内的资源。此外,在示例中,您没有提供 finally 块。Java 编译器会在内部将这个 try-with-resources 块翻译成 try-finally 块(当然,编译器会保留您提供的 catch 块)。您可以在 try-with-resources 块中获取多个资源。这种资源获取语句必须用分号分隔。
您能提供不带任何显式 catch 或 finally 块的 try-with-resources 语句吗?没错。请记住,try 块可以与 catch 块、finally 块或两者都关联。try-with-resources 语句块在内部扩展为 try-finally 块。因此,您可以提供一个没有显式 catch 或 finally 块的 try-with-resources 语句。清单 7-14 使用了一个没有任何显式 catch 或 finally 块的 try-with-resources 语句。
Listing 7-14. TryWithResources2.java
import java.util.Scanner;
class TryWithResources2 {
public static void main(String [] args) {
System.out.println("Type an integer in the console: ");
try(Scanner consoleScanner = new Scanner(System.in)) {
System.out.println("You typed the integer value: " + consoleScanner.nextInt());
}
}
}
尽管可以创建一个没有任何显式 catch 或 finally 的 try-with-resources 语句,但这并不意味着您应该这样做!例如,由于这段代码没有 catch 块,如果您键入一些无效的输入,程序将会崩溃。
D:\> java TryWithResources2
Type an integer in the console:
ten
Exception in thread "main" java.util.InputMismatchException
at java.util.Scanner.throwFor(Scanner.java:864)
at java.util.Scanner.next(Scanner.java:1485)
at java.util.Scanner.nextInt(Scanner.java:2117)
at java.util.Scanner.nextInt(Scanner.java:2076)
at TryWithResources2.main(TryWithResources2.java:7)
因此,try-with-resources 语句的好处是,它不必显式地提供 finally 块,从而简化了您的工作。但是,您仍然需要提供必要的 catch 块。
注意,对于可用于 try-with-resources 语句的资源,该资源的类必须实现java.lang.AutoCloseable接口。这个接口声明了一个名为close()的方法。除了资源尝试特性,AutoCloseable接口也被引入到 Java 7 中,该接口由Closeable接口的基础接口组成。这是为了确保现有的资源类与 try-with-resources 语句无缝协作。换句话说,您可以将所有旧的流类与 try-with-resources 一起使用,因为它们实现了AutoCloseable接口。
图 7-3。
Closeabe Interface extends AutoCloseable Interface
关闭多个资源
在 try-with-resources 语句中可以使用多个资源。下面是利用 try-with-resources 语句从给定的文本文件创建 zip 文件的代码片段:
// buffer is the temporary byte buffer used for copying data
// from one stream to another stream
byte [] buffer = new byte[1024];
// these stream constructors can throw FileNotFoundException
try (ZipOutputStream zipFile = new ZipOutputStream(new FileOutputStream(zipFileName));
FileInputStream fileIn = new FileInputStream(fileName)) {
zipFile.putNextEntry(new ZipEntry(fileName)); // putNextEntry can throw
// IOException
int lenRead = 0; // the variable to keep track of number of bytes sucessfully read
// copy the contents of the input file into the zip file
while((lenRead = fileIn.read(buffer)) > 0) { // read can throw IOException
zipFile.write(buffer, 0, lenRead); // write can throw IOException
}
// the streams will be closed automatically because they are within try-with-
// resources statement
}
在这段代码中,buffer是一个字节数组。该数组是临时存储,用于将原始数据从一个流复制到另一个流。在 try-with-resources 语句中,您打开了两个流:ZipOutputStream用于写入 zip 文件,而FileInputStream用于读取文本文件。(注意:在java.util.zip包中提供了对 zip(和 jar)文件的 API 支持。)您希望读取输入文本文件,将其压缩,并将条目放入压缩文件中。为了将文件/目录条目放入 zip 文件,ZipOutputStream类提供了一个名为putNextEntry()的方法,该方法将一个ZipEntry对象作为参数。语句zipFile.putNextEntry(new ZipEntry(fileName));将名为fileName的文件条目放入zipFile中。
为了读取文本文件的内容,可以使用FileInputStream类中的read()方法。read()方法将buffer数组作为参数。每次迭代要读取的数据量(即要读取的“数据块大小”)由传递的数组的大小给出;这段代码是 1024 字节。read()方法返回它读取的字节数,如果没有更多的数据要读取,它返回-1。在写入 zip 文件之前,while循环检查读取是否成功(使用> 0 条件)。
为了将数据写入 zip 文件,可以使用ZipOutputStream类中的write()方法。write()方法有三个参数:第一个参数是数据缓冲区;第二个参数是数据缓冲区中的起始偏移量(它是 0,因为您总是从缓冲区的起始处读取);第三个是要写入的字节数。
现在我们进入主要讨论。请注意如何在 try 块中打开两个资源,并用分号分隔这两个资源获取语句。您没有显式的 finally 块来释放资源,因为编译器会自动在 finally 块中插入对这两个流的close方法的调用。
清单 7-15 是一个完整的程序,它利用这段代码来说明如何使用 try-with-resources 语句来自动关闭多个流。
Listing 7-15. ZipTextFile.java
import java.util.*;
import java.util.zip.*;
import java.io.*;
// class ZipTextFile takes the name of a text file as input and creates a zip file
// after compressing that text file.
class ZipTextFile {
public static final int CHUNK = 1024; // to help copy chunks of 1KB
public static void main(String []args) {
if(args.length == 0) {
System.out.println("Pass the name of the file in the current directory to be zipped as an argument");
System.exit(-1);
}
String fileName = args[0];
// name of the zip file is the input file name with the suffix ".zip"
String zipFileName = fileName + ".zip";
byte [] buffer = new byte[CHUNK];
// these constructors can throw FileNotFoundException
try (ZipOutputStream zipFile = new ZipOutputStream(new FileOutputStream(zipFileName));
FileInputStream fileIn = new FileInputStream(fileName)) {
// putNextEntry can throw IOException
zipFile.putNextEntry(new ZipEntry(fileName));
int lenRead = 0; // variable to keep track of number of bytes
// successfully read
// copy the contents of the input file into the zip file
while((lenRead = fileIn.read(buffer)) > 0) {
// both read and write methods can throw IOException
zipFile.write (buffer, 0, lenRead);
}
// the streams will be closed automatically because they are
// within try-with-resources statement
}
// this can result in multiple exceptions thrown from the try block;
// use "suppressed exceptions" to get the exceptions that were suppressed!
catch(Exception e) {
System.out.println("The caught exception is: " + e);
System.out.print("The suppressed exceptions are: ");
for(Throwable suppressed : e.getSuppressed()) {
System.out.println(suppressed);
}
}
}
}
我们已经讨论过 try-with-resources 块。我们没有讨论的是被抑制的异常。在 try-with-resources 语句中,可能会抛出多个异常;例如,一个在 try 块中,一个在 catch 块中,另一个在 finally 块中。但是,只能捕获一个异常,因此其他异常将被列为隐藏的异常。从一个给定的异常对象中,您可以使用方法getSuppressed()来获得被抑制的异常的列表。
要记住的要点
以下是一些关于“用资源尝试”的有趣观点,将对你参加 OCPJP 八级考试有所帮助:
- 不能在 try-with-resources 语句体中为 try-with-resources 中声明的资源变量赋值。这是为了确保在 try-with-resources 头中获得的相同资源在 finally 块中被释放。
- 在 try-with-resources 语句中显式关闭资源是一个常见的错误。请记住,try-with-resources 扩展到调用 finally 块中的
close()方法,因此如果您显式提供一个close()方法,扩展后的代码将会对close()方法进行两次调用。考虑以下代码:try(Scanner consoleScanner = new Scanner(System.in)) {System.out.println("You typed the integer value: " + consoleScanner.nextInt());consoleScanner.close();// explicit call to close() method - remember that try-with-resources// statement will also expand to calling close() in finally method;// hence this will result in call to close() method in Scanner twice!} Scanner类中的close()方法的文档说明,如果 scanner 对象已经关闭,那么再次调用该方法将没有任何效果。所以,在这种情况下你是安全的。然而,一般来说,你不能期望所有的资源都实现了一个可以安全调用两次的close()方法。因此,在 try-with-resource 语句中显式调用close()方法是一种不好的做法。
自定义异常
| 认证目标 |
|---|
| 创建自定义异常和可自动关闭的资源 |
在大多数情况下,抛出 Java 库中已经提供的异常就足够了。例如,如果您正在检查传递给一个公共函数的参数的有效性,并且发现它们为空或者超出了预期的范围,那么您可以抛出一个IllegalArgumentException。然而,对于大多数重要的应用,您有必要开发自己的异常类(自定义异常)来指示异常情况。
如何定义自定义异常?有两个选项:您可以根据需要扩展Exception或RuntimeException类。
如果您想要强制您的自定义异常的用户处理该异常,那么您可以从Exception类扩展您的异常类——这将使您的自定义异常成为一个检查异常。
如果你想给你的自定义异常的用户一些灵活性,并让你的异常的用户来决定他们是否要处理这个异常,你可以从RuntimeException类中派生出你的异常。
因此,您需要决定是通过从Exception类还是RuntimeException类扩展来使您的自定义异常成为检查异常还是未检查异常。
为自定义异常扩展Throwable或Error类怎么样?Throwable类太普通了,不能作为你的异常的基类,所以不推荐。Error类是为 JVM 可能抛出的致命异常(如StackOverflowError)保留的,所以不建议将它作为异常的基类。
自定义异常应该扩展
Exception或RuntimeException类。通过扩展Throwable或Error类来创建定制异常是一种不好的做法。
对于从基类扩展,您需要了解基类提供了哪些方法。在这种情况下,您希望通过扩展Exception或RuntimeException类来创建一个定制异常。由于Exception类是RuntimeException类的基类,所以知道Exception类的成员就足够了。表 7-1 列出了Exception类的重要方法(包括构造函数)。
表 7-1。
Important Methods and Constructors of the Exception Class
| 成员 | 简短描述 |
|---|---|
Exception() | Exception类的默认构造函数,没有关于异常的附加(或详细)信息。 |
Exception(String) | 构造函数,它将有关构造函数的详细信息字符串作为参数。 |
Exception(String, Throwable) | 除了作为参数的详细信息字符串之外,这个异常构造函数还将异常的原因(这是另一个异常)作为参数。 |
Exception(Throwable) | 将异常原因作为参数的构造函数。 |
String getMessage() | 返回详细消息(在创建异常时作为字符串传递)。 |
Throwable getCause() | 返回异常的原因(如果有,否则返回 null)。 |
Throwable[] getSuppressed() | 以数组形式返回隐藏的异常(通常在使用 try-with-resources 语句时导致)的列表。 |
void printStackTrace() | 将堆栈跟踪(即带有相关行号的方法调用列表)打印到控制台(标准错误流)。如果异常的原因(是另一个异常对象)在异常中可用,那么也将打印该信息。此外,如果有任何隐藏的异常,也会打印出来。 |
为了演示如何创建自己的异常类,假设您想要创建一个名为InvalidInputException的定制异常。当您试图读取输入(在本例中是读取一个整数)时,如果它失败了,您想抛出这个InvalidInputException。清单 7-16 通过扩展RuntimeException类定义了这个异常类。
Listing 7-16. InvalidInputException.java
// a custom "unchecked exception" that is meant to be thrown
// when the input provided by the user is invalid
class InvalidInputException extends RuntimeException {
// default constructor
public InvalidInputException() {
super();
}
// constructor that takes the String detailed information we pass while
// raising an exception
public InvalidInputException(String str) {
super(str);
}
// constructor that remembers the cause of the exception and
// throws the new exception
public InvalidInputException(Throwable originalException) {
super(originalException);
}
// first argument takes detailed information string created while
// raising an exception
// and the second argument is to remember the cause of the exception
public InvalidInputException(String str, Throwable originalException) {
super(str, originalException);
}
}
在这个InvalidInputException类中,您没有引入任何新的字段,但是如果需要,您可以添加任何字段。这也是一个简单的自定义异常,其中构造函数简单地调用同一构造函数类型的基类版本。类CustomExceptionTest(见清单 7-17 )展示了如何利用这个自定义异常。
Listing 7-17. CustomExceptionTest.java
import java.util.Scanner;
import java.util.NoSuchElementException;
// class for testing the custom exception InvalidInputException
class CustomExceptionTest {
public static int readIntFromConsole() {
Scanner consoleScanner = new Scanner(System.in);
int typedInt = 0;
try {
typedInt = consoleScanner.nextInt();
} catch(NoSuchElementException nsee) {
System.out.println("Wrapping up the exception and throwing it…");
throw new InvalidInputException("Invalid integer input typed in console", nsee);
} catch(Exception e) {
// call all other exceptions here …
System.out.println("Error: Encountered an exception and could not read an integer from the console… ");
}
return typedInt;
}
public static void main(String [] args) {
System.out.println("Type an integer in the console: ");
try {
System.out.println("You typed the integer value: " + readIntFromConsole());
} catch(InvalidInputException iie) {
System.out.println("Error: Invalid input in console… ");
System.out.println("The current caught exception is of type: " + iie);
System.out.println("The originally caught exception is of type: " +
iie.getCause());
}
}
}
在阅读代码的讨论之前,先编译并运行这个程序。
D:\> java CustomExceptionTest
Type an integer in the console:
one
Wrapping up the exception and throwing it…
Error: Invalid input in console…
The current caught exception is of type: InvalidInputException: Invalid integer input typed in console
The originally caught exception is of type: java.util.InputMismatchException
在这段代码中,就像 Java 库中已经定义的任何其他异常一样使用InvalidInputException。您正在捕捉从main()方法中的readIntFromConsole()方法抛出的InvalidInputException InputMismatchException (which extends InvalidInputException(为其提供了捕捉处理程序)。下面的语句调用了InvalidInputException的toString()方法:
System.out.println("The current caught exception is of type: " + iie);
您没有覆盖toString()方法,所以InvalidInputException类从RuntimeException基类继承了toString()方法的默认实现。这个默认的toString()方法打印抛出的异常的名称(InvalidInputException),它还包括创建异常对象时传递的详细信息字符串("Invalid integer input typed in console")。main()方法中的最后一条语句是获取异常的原因。
System.out.println("The originally caught exception is of type: " + iie.getCause());
由于InvalidInputException的原因是InputMismatchException,这个异常名作为全限定名java.util.InputMismatchException打印在控制台上。你可以认为InputMismatchException导致InvalidInputException;这两个异常被称为链式异常。
断言
| 认证目标 |
|---|
| 使用断言测试不变量 |
在创建应用时,你会假设很多事情。然而,经常发生的是假设不成立,导致错误的条件。assert语句用于检查或测试你对程序的假设。
关键字assert为 Java 中的断言提供支持。每个断言语句都包含一个布尔表达式。如果布尔表达式的结果为真,就意味着假设为真,所以什么都不会发生。然而,如果布尔结果为假,那么您对程序的假设不再成立,并且抛出一个AssertionError。记住Error类和它的派生类指出了严重的运行时错误,不应该被处理。同样,如果抛出一个AssertionError,最好的做法是不要捕捉异常,让程序终止。之后,您需要检查为什么假设不成立,然后修复程序。
有许多原因使你应该在程序中添加断言。一个原因是它有助于及早发现问题;当你在程序中检查你的假设时,当任何一个假设失败时,你立即知道在哪里寻找问题和修复什么。此外,当其他程序员阅读您的带有断言的代码时,他们会更好地理解代码,因为您使用断言来明确您的假设。
断言语句
Java 中的断言语句有两种形式:
assert booleanExpression;
assert booleanExpression : "Detailed error message string";
如果在 assert 语句中使用了非布尔表达式,则会出现编译器错误。清单 7-18 包含断言的第一个例子。
Listing 7-18. AssertionExample1.java
class AssertionExample1 {
public static void main(String []args) {
int i = -10;
if(i < 0) {
// if negative value, convert into positive value
i = -i;
}
System.out.println("the value of i is: " + i);
// at this point the assumption is that i cannot be negative;
// assert this condition since its an assumption that will always hold
assert (i >= 0) : "impossible: i is negative!";
}
}
在这个程序中,您正在检查 I 的值是否< 0;您正在使用表达式–I 将其转换为正值。一旦条件检查 if(i < 0)完成,I 的值不能为负,或者那是你的假设。这样的假设可以用 assert 语句来断言。下面是 assert 语句:
assert (i >= 0) : "impossible: i is negative!";
如果布尔表达式(i >= 0)的计算结果为真,程序将正常运行。但是,如果计算结果为 false,程序将通过抛出一个AssertionError而崩溃。让我们检查一下这种行为(您需要使用–ea标志在运行时启用断言;我们一会儿将讨论更多关于这个标志的内容)。
D:\>java -ea AssertionExample1
the value of i is: 10
是的,这个程序成功执行,没有抛出任何异常。
有没有一个 I 值使条件不成立?有,有!如果 I 的值是整数的最小可能值,那么它不能被转换成正值。为什么?记住整数的范围是-2 31 到 231–1,所以整数值 I 的值为-2147483648 到 2147483647。换句话说,正值 2147483648 不在整数范围内。因此,如果i的值是-2147483648,那么表达式-i将溢出并再次得到值-2147483648。因此,你的假设是不正确的。
在清单 7-26 中,将 I 的值改为一个整数的最小值,如下所示:
int i = Integer.MIN_VALUE;
现在,试着运行这个程序。
D:\> java -ea AssertionExample1
the value of i is: -2147483648
Exception in thread "main" java.lang.AssertionError: impossible: i is negative!
at AssertionExample1.main(AssertionExample1.java:12)
在这个输出中,注意断言是如何失败的。应用崩溃是因为程序抛出了AssertionError,而且没有处理程序,所以程序终止。
从考试的角度来看,需要记住的重要一点是,断言在运行时是默认禁用的;要在运行时启用断言,请使用an -ea开关(或其更长形式的-enableasserts)。要在运行时禁用断言,请使用一个-da开关。如果运行时默认禁用断言,那么-da开关有什么用?有很多用途。例如,如果你想为一个给定的包中的所有类启用断言,并想禁用该包中特定类的断言,那么一个-da开关就很有用。表 7-2 列出了重要的命令行参数及其含义。请注意,您不需要重新编译程序来启用或禁用断言;调用 JVM 时,只需使用命令行参数来启用或禁用它们。
表 7-2。
Important Command-Line Arguments for Enabling/Disabling Assertions
| 命令行参数 | 简短描述 |
|---|---|
-ea | 默认情况下启用断言(系统类除外)。 |
-ea:<class name> | 为给定的类名启用断言。 |
-ea:<package name>… | 在给定包的所有成员中启用断言。 |
-ea:… | 在给定的未命名包中启用断言。 |
-esa | -enablesystemsassertions的简称;在系统类中启用断言。这个选项很少使用。 |
-da | 默认情况下禁用断言(系统类除外)。 |
-da:<class name> | 禁用给定类名的断言。 |
-da:<package name>… | 禁用给定包的所有成员中的断言。 |
-da:… | 禁用给定未命名包中的断言。 |
-dsa | -disablesystemsassertions的简称;禁用系统类中的断言。这个选项很少使用。 |
摘要
让我们简要回顾一下本章中每个认证目标的要点。请在参加考试之前阅读它。
使用 try-catch 和 throw 语句
- 当 try 块抛出异常时,JVM 从方法调用链中的 catch 处理程序列表中寻找匹配的 catch 处理程序。如果没有找到匹配的处理程序,该未处理的异常将导致应用崩溃。
- 在提供多个异常处理程序(堆栈式 catch 处理程序)时,应该在提供通用异常处理程序之前提供特定的异常处理程序。
- 您可以使用
printStackTrace()和getStackTrace()等方法以编程方式访问堆栈跟踪,这些方法可以在任何异常对象上调用。
使用 catch、multi-catch 和 finally 子句
- 一个 try 块可以有多个 catch 处理程序。如果两个或多个异常的原因相似,处理代码也相似,可以考虑合并处理程序,使之成为一个 multi-catch 块。
- catch 块应该处理异常或重新引发异常。通过捕捉异常而什么都不做来隐藏或吞下异常,这确实是一种不好的做法。
- 您可以包装一个异常,并将其作为另一个异常抛出。这两个异常成为链式异常。从抛出的异常中,可以得到异常的原因。
- 无论 try 块是成功执行还是导致异常,finally 块中的代码都将被执行。
将自动关闭资源与 try-with-resources 语句一起使用
- 忘记通过显式调用
close()方法来释放资源是一个常见的错误。您可以使用 try-with-resources 语句来简化代码并自动关闭资源。 - 您可以在 try-with-resources 语句中自动关闭多个资源。这些资源需要在 try-with-resources 语句头中用分号分隔。
- 如果 try 块抛出异常,而 finally 块也抛出异常,那么 finally 块中抛出的异常将作为隐藏异常添加到从 try 块中抛出的异常中,并传递给调用方。
创建自定义异常和可自动关闭的资源
- 建议您从
Exception或RuntimeException类中派生自定义异常。 - 方法的 throws 子句是其派生类中的重写方法应该遵守的契约的一部分。
- 重写方法可以提供与基方法的 throws 子句相同的 throw 子句,或者提供比基方法的 throws 子句更具体的 throws 子句。
- 与基方法的 throws 子句相比,重写方法不能提供更通用的 throws 子句,也不能声明引发附加的检查异常。
- 对于在 try-with-resources 语句中可用的资源,该资源的类必须实现
java.lang.AutoCloseable接口并定义close()方法。
使用断言测试不变量
- 断言是程序中的条件检查,应该用于显式检查编写程序时所做的假设。
assert语句有两种形式:一种采用一个Boolean参数,另一种采用一个额外的字符串参数。- 如果 assert 参数中给出的
Boolean条件失败(即评估为 false),程序将在抛出AssertionError后终止。当程序抛出一个AssertionError时,捕捉并恢复是不可取的。 - 默认情况下,断言在运行时是禁用的。在调用 JVM 时,可以使用命令行参数
–ea(用于启用断言)和–da(用于禁用断言)及其变体。
Question Time!Consider the following class hierarchy from the package java.nio.file and answer the question.
In the following class definitions, the base class Base has the method foo() that throws a FileSystemException; the derived class Deri extending the class Base overrides the foo() definition. class Base { public void foo() throws FileSystemException { throw new FileSystemException(""); } } class Deri extends Base { /* provide foo definition here */ } Which of the following overriding definitions of the foo() method in the Deri class are compatible with the base class foo() method definition? Choose ALL the foo() method definitions that could compile without errors when put in the place of the comment: /* provide foo definition here */ A. public void foo() throws IOException { super.foo(); } B. public void foo() throws AccessDeniedException { throw new AccessDeniedException(""); } C. public void foo() throws FileSystemException, RuntimeException { throw new NullPointerException(); } D. public void foo() throws Exception { throw new NullPointerException(); } Consider the following program: class ChainedException { public static void foo() { try { throw new ArrayIndexOutOfBoundsException(); } catch(ArrayIndexOutOfBoundsException oob) { RuntimeException re = new RuntimeException(oob); re.initCause(oob); throw re; } } public static void main(String []args) { try { foo(); } catch(Exception re) { System.out.println(re.getClass()); } } } When executed, this program prints which of the following? class java.lang.RuntimeException class java.lang.IllegalStateException class java.lang.Exception class java.lang.ArrayIndexOutOfBoundsException Consider the following program: class ExceptionTest { public static void foo() { try { throw new ArrayIndexOutOfBoundsException(); } catch(ArrayIndexOutOfBoundsException oob) { throw new Exception(oob); } } public static void main(String []args) { try { foo(); } catch(Exception re) { System.out.println(re.getCause()); } } } Which one of the following options correctly describes the behavior of this program? java.lang.Exception java.lang.ArrayIndexOutOfBoundsException class java.lang.IllegalStateException This program fails with compiler error(s) Consider the following program: import java.io.FileNotFoundException; import java.sql.SQLException; class MultiCatch { public static void fooThrower() throws FileNotFoundException { throw new FileNotFoundException(); } public static void barThrower() throws SQLException { throw new SQLException(); } public static void main(String []args) { try { fooThrower(); barThrower(); } catch(FileNotFoundException || SQLException multie) { System.out.println(multie); } } } Which one of the following options correctly describes the behavior of this program? This program prints the following: java.io.FileNotFoundException This program prints the following: java.sql.SQLException This program prints the following: java.io.FileNotFoundException || java.sql.SQLException This program fails with compiler error(s) Consider the following class hierarchy from the package javax.security.auth.login and answer the questions.
Which of the following handlers that makes use of multi-catch exception handler feature will compile without errors? catch (AccountException | LoginException exception) catch (AccountException | AccountExpiredException exception) catch (AccountExpiredException | AccountNotFoundException exception) catch (AccountExpiredException exception1 | AccountNotFoundException exception2) Consider the following code segment, which makes use of this exception hierarchy: try { LoginException le = new AccountNotFoundException(); throw (Exception) le; } catch (AccountNotFoundException anfe) { System.out.println("In the handler of AccountNotFoundException"); } catch (AccountException ae) { System.out.println("In the handler of AccountException"); } catch (LoginException le) { System.out.println("In the handler of LoginException"); } catch (Exception e) { System.out.println("In the handler of Exception"); } When executed, which of the following statements will this code segment print? In the handler of AccountNotFoundException In the handler of AccountException In the handler of LoginException In the handler of Exception Consider the following program: import java.sql.SQLException; class CustomSQLException extends SQLException {} class BaseClass { void foo() throws SQLException { throw new SQLException(); } } class DeriClass extends BaseClass { public void foo() throws CustomSQLException { // LINE A throw new CustomSQLException(); } } class EHTest { public static void main(String []args) { try { BaseClass base = new DeriClass(); base.foo(); } catch(Exception e) { System.out.println(e); } } } Which one of the following options correctly describes the behavior of this program? The program prints the following: SQLException The program prints the following: CustomSQLException The program prints the following: Exception When compiled, the program will result in a compiler error in line marked with comment Line A due to incompatible throws clause in the overridden foo method Consider the following program: class EHBehavior { public static void main(String []args) { try { int i = 10/0; // LINE A System.out.print("after throw -> "); } catch(ArithmeticException ae) { System.out.print("in catch -> "); return; } finally { System.out.print("in finally -> "); } System.out.print("after everything"); } } Which one of the following options best describes the behavior of this program? The program prints the following: in catch -> in finally -> after everything The program prints the following: after throw -> in catch -> in finally -> after everything The program prints the following: in catch -> after everything The program prints the following: in catch -> in finally -> When compiled, the program results in a compiler error in line marked with comment in LINE A for divide-by-zero Consider the following program: import java.util.Scanner; class AutoCloseableTest { public static void main(String []args) { try (Scanner consoleScanner = new Scanner(System.in)) { consoleScanner.close(); // CLOSE consoleScanner.close(); } } } Which one of the following statements is correct? This program terminates normally without throwing any exceptions This program throws an IllegalStateException This program throws an IOException This program throws an AlreadyClosedException This program results in a compiler error in the line marked with the comment CLOSE Consider the following program: class AssertionFailure { public static void main(String []args) { try { assert false; } catch(RuntimeException re) { System.out.println("RuntimeException"); } catch(Exception e) { System.out.println("Exception"); } catch(Error e) { // LINE A System.out.println("Error" + e); } catch(Throwable t) { System.out.println("Throwable"); } } } This program is invoked from the command line as follows: java AssertionFailure Choose one of the following options describes the behavior of this program: Compiler error at line marked with comment LINE A Prints "RuntimeException" in console Prints "Exception" Prints "Error" Prints "Throwable" Does not print any output on console Consider the following program: import java.io.*; class ExceptionTest { public static void thrower() throws Exception { try { throw new IOException(); } finally { throw new FileNotFoundException(); } } public static void main(String []args) { try { thrower(); } catch(Throwable throwable) { System.out.println(throwable); } } } When executed, this program prints which one of the following? java.io.IOException java.io.FileNotFoundException java.lang.Exception java.lang.Throwable
答案:
Options B and C In option A and D, the throws clause declares to throw exceptions IOException and Exception respectively, which are more general than the FileSystemException, so they are not compatible with the base method definition. In option B, the foo() method declares to throw AccessDeniedException, which is more specific than FileSystemException, so it is compatible with the base definition of the foo() method. In option C, the throws clause declares to throw FileSystemException, which is the same as in the base definition of the foo() method. Additionally it declares to throw RuntimeException, which is not a checked exception, so the definition of the foo() method is compatible with the base definition of the foo() method. B. class java.lang.IllegalStateException In the expression new RuntimeException(oob);, the exception object oob is already chained to the RuntimeException object. The method initCause() cannot be called on an exception object that already has an exception object chained during the constructor call. Hence, the call re.initCause(oob); results in initCause() throwing an IllegalStateException. D. This program fails with compiler error(s) The foo() method catches ArrayIndexOutOfBoundsException and chains it to an Exception object. However, since Exception is a checked exception, it must be declared in the throws clause of foo(). Hence this program results in this compiler error: ExceptionTest.java:6: error: unreported exception Exception; must be caught or declared to be thrown throw new Exception(oob); ^ 1 error D. This program fails with compiler error(s) For multi-catch blocks, the single pipe (|) symbol needs to be used and not double pipe (||), as provided in this program. Hence this program will fail with compiler error(s). C. catch (AccountExpiredException | AccountNotFoundException exception) For A and B, the base type handler is provided with the derived type handler, hence the multi-catch is incorrect. For D, the exception name exception1 is redundant and will result in a syntax error. C is the correct option and this will compile fine without errors. A. In the handler of AccountNotFoundException In this code, the created type of the exception is AccountNotFoundException. Though the exception object is stored in the variable of type LoginException and then type-casted to Exception, the dynamic type of the exception remains the same, which is AccountNotFoundException. When looking for a catch handler, the Java runtime looks for the exact handler based on the dynamic type of the object. Since it is available immediately as the first handler, this exactly matching catch handler got executed. B. The program prints the following: CustomSQLException The exception thrown is CustomSQLException object from the overridden foo method in DeriClass. Note that SQLException is a checked exception since it extends the Exception class. Inside the BaseClass, the foo method lists SQLException in its throws clause. In the DeriClass, the overridden foo method lists CustomSQLException in its throws clause: it is acceptable to have a more restrictive exception throws clause in a derived class. Hence, the given program compiles successfully and prints CustomSQLException. D. The program prints the following: in catch -> in finally -> The statement println("after throw -> "); will never be executed since the line marked with the comment LINE A throws an exception. The catch handles ArithmeticException, so println("in catch -> "); will be executed. Following that, there is a return statement, so the function returns. But before the function returns, the finally statement should be called, hence the statement println("in finally -> "); will get executed. So, the statement println("after everything"); will never get executed. A. This program terminates normally without throwing any exceptions The try-with-resources statement internally expands to call the close() method in the finally block. If the resource is explicitly closed in the try block, then calling close() again does not have any effect. From the description of the close() method in the AutoCloseable interface: “Closes this stream and releases any system resources associated with it. If the stream is already closed, then invoking this method has no effect.” F. Does not print any output on the console By default, assertions are disabled. If -ea (or the -enableassertions option to enable assertions), then the program would have printed "Error" since the exception thrown in the case of assertion failure is java.lang.AssertionError, which is derived from the Error class. B. java.io.FileNotFoundException If both the try block and finally block throw exceptions, the exception thrown from the try block will be ignored. Hence, the method thrower() throws a FileNotFoundException. The dynamic type of the variable throwable is FileNotFoundException, so the program prints that type name.
八、使用 JavaSE8 日期/时间 API
| 认证目标 |
|---|
| 使用 LocalDate、LocalTime、LocalDateTime、Instant、Period 和 Duration 创建和管理基于日期和基于时间的事件,包括将日期和时间组合到单个对象中 |
| 跨时区处理日期和时间,并管理夏令时带来的更改,包括日期和时间值的格式 |
| 使用 Instant、Period、Duration 和 TemporalUnit 定义、创建和管理基于日期和基于时间的事件 |
新的 Java 日期和时间 API 在java.time包中提供。Java 8 中的这个新 API 取代了支持日期和时间相关功能的旧类,例如作为java.util包的一部分提供的Date、Calendar和TimeZone类。
Java 8 已经有了早期 Java 的Date和Calendar等类,为什么还要引入新的日期和时间 API?主要原因是不方便的 API 设计。例如,Date类既有日期又有时间;如果只需要时间信息而不需要与日期相关的信息,则必须将与日期相关的值设置为零。这些课程的某些方面也不直观。例如,在Date构造函数中,日期值的范围是 1 到 31,而月份值的范围是 0 到 11(不是 1 到 12)!此外,java.util.Date和SimpleDateFormatter还有许多与并发相关的问题,因为它们不是线程安全的。
Java 8 在新引入的java.time包中为日期和时间相关的功能提供了很好的支持。这个包中的大多数类都是不可变的和线程安全的。本章说明了如何使用这个包中的重要类和接口,包括LocalDate、LocalTime、LocalDateTime、Instant、Period、Duration和TemporalUnit。您还将学习如何使用时区和夏令时,以及如何设置日期和时间值的格式。
API 包含了流畅接口的概念:它的设计方式使得代码可读性更强,也更容易使用。因此,这个包中的类有许多静态方法(其中许多是工厂方法)。此外,这些类中的方法遵循一个通用的命名约定(例如,它们使用前缀plus和minus来加减日期或时间值)。
理解 java.time 中的重要类
| 认证目标 |
|---|
| 使用 LocalDate、LocalTime、LocalDateTime、Instant、Period 和 Duration 创建和管理基于日期和基于时间的事件,包括将日期和时间组合到单个对象中 |
| 使用 Instant、Period、Duration 和 TemporalUnit 定义、创建和管理基于日期和基于时间的事件 |
java.time包由四个子包组成:
java.time.temporal—访问日期/时间字段和单位java.time.format—格式化日期/时间对象的输入和输出java.time.zone—处理时区java.time.chrono—支持日本和泰国日历等日历系统
本章仅关注考试目标所涵盖的日期/时间主题。让我们从学习使用LocalDate、LocalTime、LocalDateTime、Instant、Period和Duration类开始。
使用 LocalDate 类
java.time. LocalDate表示没有时间或时区的日期。LocalDate在 ISO-8601 日历系统中以年-月-日格式(YYYY-MM-DD)表示:例如,2015-10-26。
Java 8 日期和时间 API 使用 ISO 8601 作为默认的日历格式。在这种国际公认的格式中,日期和时间值从最大到最小的时间单位排序:年、月/周、日、小时、分钟、秒和毫秒/纳秒。
这里有一个使用LocalDate的例子:
LocalDate today = LocalDate.now();
System.out.println("Today's date is: " + today);
当我们运行该代码时,它打印出以下内容:
Today's date is: 2015-10-26
LocalDate.now()方法基于默认时区,使用系统时钟获取当前日期。您可以通过显式指定日、月和年组件来获得一个LocalDate对象:
LocalDate newYear2016 = LocalDate.of(2016, 1, 1);
System.out.println("New year 2016: " + newYear2016);
该代码打印以下内容:
New year 2016: 2016-01-01
这个代码怎么样?
LocalDate valentinesDay = LocalDate.of(2016, 14, 2);
System.out.println("Valentine's day is on: " + valentinesDay);
它抛出一个异常:
Exception in thread "main" java.time.DateTimeException: Invalid value for MonthOfYear
(valid values 1 - 12): 14
在这种情况下,month和dayOfMonth参数值互换。LocalDate的of()方法声明如下:
LocalDate of(int year, int month, int dayOfMonth)
为了避免犯这个错误,可以使用重载版本LocalDate.of(int year, Month month, int day)。第二个参数java.time.Month是一个枚举,表示一年中的 12 个月。如果互换日期和月份参数,就会出现编译器错误。下面是使用此枚举的改进版本:
LocalDate valentinesDay = LocalDate.of(2016, Month.FEBRUARY, 14);
System.out.println("Valentine's day is on: " + valentinesDay);
这段代码打印出来
Valentine's day is on: 2016-02-14
LocalDate类有一些方法,你可以用它们在当前的LocalDate对象上增加或减少日、周、月或年。例如,假设你的签证在 180 天后到期。以下是显示到期日期的代码段(假设今天的日期是 2015-10-26 ):
long visaValidityDays = 180L;
LocalDate currDate = LocalDate.now();
System.out.println("My Visa expires on: " + currDate.plusDays(visaValidityDays));
这段代码显示了以下内容:
My Visa expires on: 2016-04-23
除了plusDays()方法,LocalDate还提供了plusWeeks()、plusMonths()和plusYears()方法,以及用于减法的方法:minusDays()、minusWeeks()、minusMonths()和minusYears()。表 8-1 列出了你需要知道的LocalDate类中的更多方法(这个表提到了像ZoneId这样的类——它们将在本章后面讨论)。
表 8-1。
Important Methods in the LocalDate Class
| 方法 | 简短描述 | 示例代码 |
|---|---|---|
LocalDate now(Clock clock) LocalDate now(ZoneId zone) | 使用传递的clock或zone参数返回带有当前日期的LocalDate对象 | // assume today's date is 26 Oct 2015``LocalDate.now(Clock.systemDefaultZone());``// returns current date as 2015-10-26``LocalDate.now(ZoneId.of("Asia/Kolkata"));``// returns current date as 2015-10-26``LocalDate.now(ZoneId.of("Asia/Tokyo")); |
LocalDate ofYearDay(int year, int dayOfYear) | 从作为参数传递的year和dayOfYear中返回LocalDate | LocalDate.ofYearDay(2016,100); // returns date as 2016-04-09 |
LocalDate parse(CharSequence dateString) | 从作为参数传递的dateString中返回LocalDate | LocalDate.parse("2015-10-26");``// returns a LocalDate corresponding``// to the passed string argument; hence it |
LocalDate ofEpochDay(Long epochDay) | 通过将天数添加到纪元开始日(纪元开始于 1970 年)来返回LocalDate | LocalDate.ofEpochDay(10); // returns 1970-01-11; |
使用 LocalTime 类
除了LocalTime表示没有日期或时区的时间之外,java.time.LocalTime类与LocalDate类似。时间采用 ISO-8601 日历系统格式:HH:MM:SS.nanosecond。LocalTime和LocalDate都使用系统时钟和默认时区。
下面是一个使用LocalTime的例子:
LocalTime currTime = LocalTime.now();
System.out.println("Current time is: " + currTime);
当我们执行它时,它打印了以下内容:
Current time is: 12:23:05.072
如上所述,LocalTime使用系统时钟及其默认时区。要基于特定的时间值创建不同的时间对象,可以使用LocalTime类的重载of()方法:
System.out.println(LocalTime.of(18,30));
// prints: 18:30
LocalTime提供了许多有用的方法,可以用来加减小时、分钟、秒和纳秒。例如,假设从现在起 6.5 小时后您有一个会议,您想要找到确切的会议时间。这里有一段代码:
long hours = 6;
long minutes = 30;
LocalTime currTime = LocalTime.now();
System.out.println("Current time is: " + currTime);
System.out.println("My meeting is at: " + currTime.plusHours(hours).plusMinutes(minutes));
这段代码显示了以下内容:
Current time is: 12:29:13.624
My meeting is at: 18:59:13.624
除了plusHours(),LocalTime还支持plusMinutes(),plusSeconds(),plusNanos()方法;同样,对于减法,它支持minusHours()、minusMinutes()、minusNanos()和minusSeconds()。表 8-2 列出了LocalTime类中的一些重要方法。
表 8-2。
Important Methods in the LocalTime Class
| 方法 | 简短描述 | 示例代码 |
|---|---|---|
LocalTime now(Clock clock) LocalTime now(ZoneId zone) | 使用传递的clock或zone参数返回带有当前时间的LocalTime对象 | LocalTime.now(Clock.systemDefaultZone())``// returns current time as 18:30:35.744``LocalDate.now(ZoneId.of("Asia/Tokyo"); |
LocalTime ofSecondOfDay(long daySeconds) | 从作为参数传递的daySeconds中返回LocalTime(注意,一天 24 小时有 86,400 秒) | LocalTime.ofSecondOfDay(66620);``// returns 18:30:20 because |
LocalTime parse(CharSequence timeString) | 从作为参数传递的dateString中返回LocalTime | LocalTime.parse("18:30:05");``// returns a LocalTime object``// corresponding to the given String |
使用 LocalDateTime 类
类别java.time. LocalDateTime表示没有时区的日期和时间。您可以将LocalDateTime视为LocalTime和LocalDate类的逻辑组合。日期和时间格式使用 ISO-8601 日历系统:YYYY-MM-DD HH:MM:SS.nanosecond。
下面是一个打印今天的日期和当前时间的简单示例:
LocalDateTime currDateTime = LocalDateTime.now();
System.out.println("Today's date and current time is: " + currDateTime);
当我们运行这段代码时,它打印出以下内容:
Today's date and current time is: 2015-10-29T21:04:36.376
在这个输出中,注意字符 T 代表时间,它分隔了日期和时间部分。使用系统时钟及其默认时区获取当前日期和时间。
java.time包中的很多类,包括LocalDate、LocalTime、LocalDateTime,都支持isAfter()和isBefore()方法进行比较:
LocalDateTime christmas = LocalDateTime.of(2015, 12, 25, 0, 0);
LocalDateTime newYear = LocalDateTime.of(2016, 1, 1, 0, 0);
System.out.println("New Year 2016 comes after Christmas 2015? "+newYear.isAfter(christmas));
该代码打印以下内容:
New Year 2016 comes after Christmas 2015? true
您可以分别使用toLocalDate()和toLocalTime()方法从给定的LocalDateTime对象中获取LocalDate和LocalTime对象:
LocalDateTime dateTime = LocalDateTime.now();
System.out.println("Today's date and current time: " + dateTime);
System.out.println("The date component is: " + dateTime.toLocalDate());
System.out.println("The time component is: " + dateTime.toLocalTime());
当我们执行这段代码时,它打印出来
Today's date and current time: 2015-11-04T13:19:10.497
The date component is: 2015-11-04
The time component is: 13:19:10.497
与表 8-1 和 8-2 中列出的方法类似,LocalDateTime有now()、of()、parse()等方法。同样,类似于LocalDate和LocalTime,这个类也提供了加减年、月、日、小时、分钟、秒和纳秒的方法。为避免重复,这里不再列出这些方法。
使用即时类
假设您想要跟踪一个 Java 应用的执行,或者将应用事件存储在一个文件中。出于这些目的,您需要获得时间戳值,您可以使用java.time.Instant类来实现。瞬时值开始于 1970 年 1 月 1 日 00:00:00 小时(称为 Unix 纪元)。
Instant类在内部使用一个long变量,该变量保存从 Unix 纪元开始以来的秒数:1970-01-01T00:00:00Z(出现在这个纪元之前的值被视为负值)。此外,Instant使用一个整数变量来存储每秒经过的纳秒数。清单 8-1 中的程序使用了Instant类。
Listing 8-1. UsingInstant.java
import java.time.Instant;
public class UsingInstant {
public static void main(String args[]){
// prints the current timestamp with UTC as time zone
Instant currTimeStamp = Instant.now();
System.out.println("Instant timestamp is: "+ currTimeStamp);
// prints the number of seconds as Unix timestamp from epoch time
System.out.println("Number of seconds elapsed: " + currTimeStamp.getEpochSecond());
// prints the Unix timestamp in milliseconds
System.out.println("Number of milliseconds elapsed: " + currTimeStamp.toEpochMilli());
}
}
执行时,它会打印以下内容:
Instant timestamp is: 2015-11-02T03:16:04.502Z
Number of seconds elapsed: 1446434164
Number of milliseconds elapsed: 1446434164502
LocalDateTime和Instant有什么区别?这里有一个例子可以说明:
LocalDateTime localDateTime = LocalDateTime.now();
Instant instant = Instant.now();
System.out.println("LocalDateTime is: " + localDateTime + " \nInstant is: " + instant);
当我们执行此操作时,它打印了以下内容:
LocalDateTime is: 2015-11-02T 17:21:11.402
Instant is: 2015-11-02T 11:51:11.404Z
如你所见,LocalDateTime打印的时间值与Instant的结果不同。为什么呢?因为我们生活在亚洲/加尔各答时区,与格林威治时间相差+05:30 小时。LocalDateTime使用默认时区,但Instant没有。
使用 Period 类
java.time.Period类用于根据年、月和日来测量时间量。假设你买了一些很贵的药,想在过期前用完。你可以通过下面的方法知道它什么时候到期:
LocalDate manufacturingDate = LocalDate.of(2016, Month.JANUARY, 1);
LocalDate expiryDate = LocalDate.of(2018, Month.JULY, 18);
Period expiry = Period.between(manufacturingDate, expiryDate);
System.out.printf("Medicine will expire in: %d years, %d months, and %d days (%s)\n",
expiry.getYears(), expiry.getMonths(), expiry.getDays(), expiry);
这段代码显示了以下内容:
Medicine will expire in: 2 years, 6 months, and 17 days (P2Y6M17D)
这个例子使用了Period.between()方法,该方法将两个LocalDate值作为参数,并返回一个Period。这个程序使用方法getYears()、getMonths()和getDays()(这三个方法返回一个int值),它们分别返回给定期间的年数、月数和天数。Period的toString()方法打印值P2Y6M17D。在该字符串中,字符P、Y、M和D分别代表周期、年、月和日。
从Period开始,您可以使用plusYears()、plusMonths()、plusDays()、minusYears()、minusMonths()和minusDays()的方法增加或减少年、月和日。表 8-3 列出了这个类中的其他重要方法。
表 8-3。
Important Methods in the Period Class
| 方法 | 简短描述 | 示例代码 |
|---|---|---|
Period of(int years, int months, int days) | 基于给定的参数返回一个Period对象 | LocalDate christmas = LocalDate.of(2015, 12, 25);``System.out.println(``Period.between(LocalDate.now(), christmas)); |
Period ofWeeks(int unit)``Period ofDays(int unit)``Period ofMonths(int unit) | 基于参数中的单位返回一个Period对象 | Period.ofWeeks(2) // returns P14D Period.ofDays(15) // returns P15D Period.ofMonths(6) // returns P6M Period.ofYears(4) // returns P4Y |
Period parse(CharSequence string) | 从作为参数传递的string中返回一个Period | Period.parse("P4Y6M15D"); // returns P4Y6M15D |
Java 8 日期和时间 API 区分了人类和计算机使用日期和时间相关信息的方式。例如,
Instant类表示一个 Unix 时间戳,并在内部使用long和int变量。Instant人类不太容易读懂或使用这些值,因为该类不支持与日、月、小时等相关的方法(相比之下,Period类支持这些方法)。
使用持续时间类
我们之前讨论过Period类——它用年、月和日来表示时间。Duration相当于Period的时间。Duration类用小时、分钟、秒等表示时间。适用于测量机器时间或使用Instance物体时。类似于Instance类,Duration类将秒部分存储为long值,并将纳秒存储为int值。
说你想在今晚午夜祝你最好的朋友贝基生日快乐。你可以通过下面的方法来确定需要多少小时:
LocalDateTime comingMidnight =
LocalDateTime.of(LocalDate.now().plusDays(1), LocalTime.MIDNIGHT);
LocalDateTime now = LocalDateTime.now();
Duration between = Duration.between(now, comingMidnight);
System.out.println(between);
该代码打印以下内容:
PT7H13M42.003S
这个例子使用了LocalDateTime类中of()方法的重载版本:LocalDateTime of(LocalDate, LocalTime)。LocalDate.now()调用返回当前日期,但是您需要给这个值加上一天,这样您就可以使用LocalTime.MIDNIGHT来表示即将到来的午夜。Duration中的between()方法接受两个时间值——在本例中是两个LocalDateTime对象。当我们执行这个程序时,时间是 16:46:17;从那时到午夜是 7 小时 13 分 42 秒。这由toString()输出、Period : PT7H13M42.003S指示。前缀PT表示周期时间,H表示小时,M表示分钟,S表示秒。
表 8-4 列出了Duration类的一些重要方法。TemporalUnit和ChronoUnit将在本章后面讨论。
图 8-1。
Summary of the Instant, Period, and Duration classes
表 8-4。
Important Methods in the Duration Class
| 方法 | 简短描述 | 示例代码 |
|---|---|---|
Duration of(long number, TemporalUnit unit)``Duration ofDays(long unit)``Duration ofHours(long unit)``Duration ofMinutes(long unit)``Duration ofSeconds(long unit)``Duration ofMillis(long unit) | 以指定格式返回给定数字的Duration对象,根据参数中给定的单位返回Duration | Duration.of(3600, ChronoUnit.MINUTES) // returns "PT60H"``Duration.ofDays(4)``// returns "PT96H"``Duration.ofHours(2)``// returns "PT2H"``Duration.ofMinutes(15)``// returns "PT15M"``Duration.ofSeconds(30)``//returns "PT30S"``Duration.ofMillis(120)``// returns "PT0.12S"``Duration.ofNanos(120)``// returns "PT0.00000012S" |
Duration parse(CharSequence string) | 从作为参数传递的string中返回一个Period | Duration.parse("P2DT10H30M")``// returns a Duration object |
使用 TemporalUnit 接口
TemporalUnit接口是java.time.temporal包的一部分。它表示日期或时间单位,如秒、小时、天、月、年等。枚举java.time.temporal.ChronoUnit实现了这个接口。与其使用常数值,不如使用它们的等价枚举值。为什么?因为在ChronoUnit中使用枚举值会产生更可读的代码;此外,你不太可能犯错误。
清单 8-2 打印枚举值,无论它们是基于日期还是基于时间,以及持续时间。
Listing 8-2. ChronoUnitValues.java
import java.time.temporal.ChronoUnit;
public class ChronoUnitValues {
public static void main(String []args) {
System.out.println("ChronoUnit DateBased TimeBased Duration");
System.out.println("---------------------------------------");
for(ChronoUnit unit : ChronoUnit.values()) {
System.out.printf("%10s \t %b \t\t %b \t\t %s %n",
unit, unit.isDateBased(), unit.isTimeBased(), unit.getDuration());
}
}
}
结果如下:
ChronoUnit DateBased TimeBased Duration
--------------------------------------- --------------------
Nanos false true PT0.000000001S
Micros false true PT0.000001S
Millis false true PT0.001S
Seconds false true PT1S
Minutes false true PT1M
Hours false true PT1H
HalfDays false true PT12H
Days true false PT24H
Weeks true false PT168H
Months true false PT730H29M6S
Years true false PT8765H49M12S
Decades true false PT87658H12M
Centuries true false PT876582H
Millennia true false PT8765820H
Eras true false PT8765820000000H
Forever false false PT2562047788015215H30M7.999999999S
java.time包中的众多方法都以TemporalUnit为自变量。例如,考虑一下Duration类中的of()方法:
Duration of(long amount, TemporalUnit unit)
因为ChronoUnit枚举实现了TemporalUnit接口,所以您可以传递一个ChronoUnit枚举值作为该构造函数的第二个参数:
System.out.println(Duration.of(1, ChronoUnit.MINUTES).getSeconds());
// prints: 60
System.out.println(Duration.of(1, ChronoUnit.HOURS).getSeconds());
// prints:3600
System.out.println(Duration.of(1, ChronoUnit.DAYS).getSeconds());
// prints: 86400
从这个例子中可以看出,ChronoUnit帮助您处理时间单位值,如秒、分、小时,以及日期值,如日、月和年。
处理时区和夏令时
| 认证目标 |
|---|
| 跨时区处理日期和时间,并管理夏令时带来的更改,包括日期和时间值的格式 |
前一节讨论了java.time包中的一些重要类。本节讨论如何跨时区处理日期和时间、处理夏令时以及设置日期和时间值的格式。
使用时区相关的类
为了跨时区处理日期和时间,您需要了解三个与时区相关的重要类:ZoneId、ZoneOffset和ZonedDateTime。现在我们来讨论一下。
使用 ZoneId 类
在java.time包中,java.time.ZoneId类代表时区。时区通常使用格林威治标准时间(GMT,也称为 UTC/格林威治时间)的偏移量来标识。
例如,我们生活在印度,而印度唯一的时区是亚洲/加尔各答(使用这种地区/城市格式给出时区)。此代码打印时区:
System.out.println("My zone id is: " + ZoneId.systemDefault());
对于我们的时区,它打印如下:
My zone id is: Asia/Kolkata
您可以通过调用ZoneId中的静态方法getAvailableZoneIds()获得时区列表,该方法返回一个Set<String>:
Set<String> zones = ZoneId.getAvailableZoneIds();
System.out.println("Number of available time zones is: " + zones.size());
zones.forEach(System.out::println);
结果如下:
Number of available time zones is: 589
Asia/Aden
America/Cuiaba
// rest of the output elided...
您可以将这些时区标识符中的任何一个传递给of()方法来创建相应的ZoneId对象,如
ZoneId AsiaKolkataZoneId = ZoneId.of("Asia/Kolkata");
使用 ZoneOffset 类
ZoneId标识时区,如Asia/Kolkata。另一个类ZoneOffset,代表相对于 UTC/格林威治的时区偏移量。例如,时区 ID“亚洲/加尔各答”相对于 UTC/格林威治的时区偏移量为+05:30(加 5 小时 30 分钟)。ZoneOffset类扩展了ZoneId类。我们将在下一节讨论一个使用ZoneOffset的例子。
使用 ZonedDateTime 类
在 Java 8 中,如果只想处理日期、时间或时区,可以分别使用LocalDate、LocalTime或ZoneId。如果您希望日期、时间和时区这三者都包含在内,该怎么办呢?为此,您可以使用ZonedDateTime类:
LocalDate currentDate = LocalDate.now();
LocalTime currentTime = LocalTime.now();
ZoneId myZone = ZoneId.systemDefault();
ZonedDateTime zonedDateTime = ZonedDateTime.of(currentDate, currentTime, myZone);
System.out.println(zonedDateTime);
结果如下:
2015-11-05T11:38:40.647+05:30[Asia/Kolkata]
这段代码使用了重载的静态方法ZonedDateTime of(LocalDate, LocalTime, ZoneID)。
给定一个LocalDateTime,你可以用一个ZoneId得到一个ZonedDateTime对象:
LocalDateTime dateTime = LocalDateTime.now();
ZoneId myZone = ZoneId.systemDefault();
ZonedDateTime zonedDateTime = dateTime.atZone(myZone);
为了说明这些不同时区相关类之间的转换,下面的代码段创建了一个ZoneId对象,将时区信息添加到一个LocalDateTime对象中以获得一个ZonedDateTime对象,最后从ZonedDateTime中获得时区偏移量:
ZoneId myZone = ZoneId.of("Asia/Kolkata");
LocalDateTime dateTime = LocalDateTime.now();
ZonedDateTime zonedDateTime = dateTime.atZone(myZone);
ZoneOffset zoneOffset = zonedDateTime.getOffset();
System.out.println(zoneOffset);
它打印以下内容:
+05:30
假设你在新加坡,日期是 2016 年 1 月 1 日,时间是早上 6:00,在和住在奥克兰(新西兰)的朋友通话之前,你想了解一下新加坡和奥克兰的时差。清单 8-3 展示了一个使用ZoneId、ZonedDateTime和Duration类的程序,以说明如何一起使用这些类。
Listing 8-3. TimeDifference.java
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.Duration;
public class TimeDifference {
public static void main(String[] args) {
ZoneId singaporeZone = ZoneId.of("Asia/Singapore");
ZonedDateTime dateTimeInSingapore = ZonedDateTime.of(
LocalDateTime.of(2016, Month.JANUARY, 1, 6, 0), singaporeZone);
ZoneId aucklandZone = ZoneId.of("Pacific/Auckland");
ZonedDateTime sameDateTimeInAuckland =
dateTimeInSingapore.withZoneSameInstant(aucklandZone);
Duration timeDifference = Duration.between(
dateTimeInSingapore.toLocalTime(),
sameDateTimeInAuckland.toLocalTime());
System.out.printf("Time difference between %s and %s zones is %d hours",
singaporeZone, aucklandZone, timeDifference.toHours());
}
}
结果如下:
Time difference between Asia/Singapore and Pacific/Auckland zones is 5 hours
这个程序创建了两个ZoneId:一个用于新加坡,另一个用于奥克兰。在用给定的日期和时间为新加坡时区创建了一个ZonedDateTime对象之后,通过调用ZonedDateTime类的withZoneSameInstant()方法,您得到了奥克兰的等效的ZonedDateTime对象。要找到以小时为单位的时差,可以使用Duration的Duration.between()方法和toHours()方法。
应对夏令时
因为季节变化,一年中的日照量不会保持不变。例如,夏天有更多的日光。使用夏令时(DST)时,时钟会提前或推迟一小时,以充分利用日光。俗话说,“春去秋来”——春天开始时,时钟通常会提前一个小时,秋天开始时,时钟会推迟一个小时:
ZoneId kolkataZone = ZoneId.of("Asia/Kolkata");
Duration kolkataDST = kolkataZone.getRules().getDaylightSavings(Instant.now());
System.out.printf("Kolkata zone DST is: %d hours %n", kolkataDST.toHours());
ZoneId aucklandZone = ZoneId.of("Pacific/Auckland");
Duration aucklandDST = aucklandZone.getRules().getDaylightSavings(Instant.now());
System.out.printf("Auckland zone DST is: %d hours", aucklandDST.toHours());
以下是结果(2015 年 11 月 5 日执行时):
Kolkata zone DST is: 0 hours
Auckland zone DST is: 1 hours
调用zoneId.getRules().getDaylightSavings(Instant.now());根据当时 DST 是否有效返回一个Duration对象。如果Duration.isZero()为假,DST 在该区域有效;否则,就不是。在本例中,加尔各答时区不采用夏令时,但奥克兰时区采用+1 小时的夏令时。
格式化日期和时间
当用日期和时间编程时,你经常需要用不同的格式打印它们。此外,您可能需要阅读不同格式的日期/时间信息。要读取或打印各种格式的日期和时间值,可以使用java.time.format包中的DateTimeFormatter类。
DateTimeFormatter类提供了许多用于格式化日期和时间值的预定义常量。以下是一些这样的预定义格式化程序的列表(带有示例输出值):
ISO_DATE(2015-11-05)ISO_TIME(11:25:47.624)RFC_1123_DATE_TIME(从而,2015 年 11 月 5 日上午 11:27:22 +0530)ISO_ZONED_DATE_TIME(2015-11-05t 11:30:33.49+05:30[亚洲/加尔各答])
下面是一个简单的例子,它使用了类型为DateTimeFormatter的预定义的ISO_TIME:
LocalTime wakeupTime = LocalTime.of(6, 0, 0);
System.out.println("Wake up time: " + DateTimeFormatter.ISO_TIME.format(wakeupTime));
这印刷了以下内容:
Wake up time: 06:00:00
如果您想使用自定义格式而不是任何预定义的格式,该怎么办?为此,您可以使用DateTimeFormatter类中的ofPattern()方法:
DateTimeFormatter customFormat = DateTimeFormatter.ofPattern("dd MMM yyyy");
System.out.println(customFormat.format(LocalDate.of(2016, Month.JANUARY, 01)));
结果如下:
01 Jan 2016
使用字母对日期或时间的格式进行编码,以形成日期或时间模式字符串。通常这些字母在模式中重复出现。
在日期和时间的格式字符串中使用时,大写和小写字母可以有相似或不同的含义。在尝试使用这些字母之前,请仔细阅读这些模式的 Javadoc。比如在 dd-MM-yy 中,MM 指的是月;但是,在 dd-mm-yy 中,mm 指的是分钟!
前面的代码段给出了一个创建自定义日期格式的简单示例。类似的字母可用于创建自定义日期和时间模式字符串。以下是创建日期模式的重要字母及其含义的列表(带有示例):
G(时代:BC,AD)y(纪元年份:2015 年 15 月)Y(以周为单位的年份:2015 年 15 月)M(月:11 日、11 月、11 月)w(一年中的第 13 周)W(月中的星期:2)E(一周中的日名:星期日、星期日)D(一年中的第 256 天)d(一月中的第几天:13)
清单 8-4 中的程序使用简单和复杂的模式字符串来创建定制的日期格式。
Listing 8-4. CustomDatePatterns.java
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class CustomDatePatterns {
public static void main(String []args) {
// patterns from simple to complex ones
String [] dateTimeFormats = {
"dd-MM-yyyy", /* d is day (in month), M is month, y is year */
"d '('E')' MMM, YYYY", /*E is name of the day (in week), Y is year*/
"w'th week of' YYYY", /* w is the week of the year */
"EEEE, dd'th' MMMM, YYYY" /*E is day name in the week */
};
LocalDateTime now = LocalDateTime.now();
for(String dateTimeFormat : dateTimeFormats) {
System.out.printf("Pattern \"%s\" is %s %n", dateTimeFormat,
DateTimeFormatter.ofPattern(dateTimeFormat).format(now));
}
}
}
结果如下:
Pattern "dd-MM-yyyy" is 05-11-2015
Pattern "d '('E')' MMM, YYYY" is 5 (Thu) Nov, 2015
Pattern "w'th week of' YYYY" is 45th week of 2015
Pattern "EEEE, dd'th' MMMM, YYYY" is Thursday, 05th November, 2015
如您所见,重复的字母导致条目的形式更长。例如,当您使用E(这是一周中的某一天的名称)时,结果是“Thu”,而使用EEEE则打印完整形式的日期名称,即“星期四”。
另一个需要注意的重要事情是如何在给定的模式字符串中打印文本。为此,您使用单引号分隔的文本,由DateTimeFormatter按原样打印。比如'('E')'打印“(Wed)”。如果您给出了一个不正确的模式,或者忘记使用单引号将文本与模式字符串中的模式字母分开,那么您会因为传递了一个“非法模式”而得到一个DateTimeParseException
现在,让我们看一个创建定制时间模式字符串的类似例子。以下是定义自定义时间模式的重要字母列表:
a(文本标记上午/下午标记)H(小时:取值范围 0-23)k(小时:取值范围 1–24)K(上午/下午的小时:取值范围 0-11)h(上午/下午的小时:取值范围 1-12)m(分钟s(第二次)S(几分之一秒)z(时区:通用时区格式)
有关更多字母及其描述,请参见DateTimeFormatter类的 Javadoc。清单 8-5 展示了一个使用简单和复杂模式字符串创建定制时间格式的程序。
Listing 8-5. CustomTimePatterns.java
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
// Using examples, illustrates how to use "pattern strings" for creating custom time formats
class CustomTimePatterns {
public static void main(String []args) {
// patterns from simple to complex ones
String [] timeFormats = {
"h:mm", /* h is hour in am/pm (1-12), m is minute */
"hh 'o''clock'", /* '' is the escape sequence to print a single quote */
"H:mm a", /* H is hour in day (0-23), a is am/pm*/
"hh:mm:ss:SS", /* s is seconds, S is milliseconds */
"K:mm:ss a" /* K is hour in am/pm(0-11) */
};
LocalTime now = LocalTime.now();
for(String timeFormat : timeFormats) {
System.out.printf("Time in pattern \"%s\" is %s %n", timeFormat,
DateTimeFormatter.ofPattern(timeFormat).format(now));
}
}
}
结果如下:
Time in pattern "h:mm" is 12:27
Time in pattern "hh 'o''clock'" is 12 o'clock
Time in pattern "H:mm a" is 12:27 PM
Time in pattern "hh:mm:ss:SS" is 12:27:10:41
Time in pattern "K:mm:ss a" is 0:27:10 PM
注意基于所使用的模式字符串,输出是如何不同的。
航班旅行示例
让我们看一个例子,它使用了到目前为止所涉及的许多类。假设你需要赶 2016 年 1 月 1 日早上 6:00 从新加坡出发的航班,航班需要 10 个小时到达新西兰奥克兰。你能知道到达奥克兰的时间吗?清单 8-6 中的程序解决了这个问题。
Listing 8-6. FlightTravel.java
import java.time.Month;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class FlightTravel {
public static void main(String[] args) {
DateTimeFormatter dateTimeFormatter =
DateTimeFormatter.ofPattern("dd MMM yyyy hh.mm a");
// Leaving on 1st Jan 2016, 6:00am from "Singapore"
ZonedDateTime departure = ZonedDateTime.of(
LocalDateTime.of(2016, Month.JANUARY, 1, 6, 0),
ZoneId.of("Asia/Singapore"));
System.out.println("Departure: " + dateTimeFormatter.format(departure));
// Arrival on the same day in 10 hours in "Auckland"
ZonedDateTime arrival =
departure.withZoneSameInstant(ZoneId.of("Pacific/Auckland"))
.plusHours(10);
System.out.println("Arrival: " + dateTimeFormatter.format(arrival));
}
}
结果如下:
Departure: 01 Jan 2016 06.00 AM
Arrival: 01 Jan 2016 09.00 PM
摘要
让我们简要回顾一下本章中每个认证目标的要点。请在参加考试之前阅读它。
使用 LocalDate、LocalTime、LocalDateTime、Instant、Period 和 Duration 创建和管理基于日期和基于时间的事件,包括将日期和时间组合到单个对象中
- Java 8 日期和时间 API 使用 ISO 8601 作为默认的日历格式。
java.time.LocalDate类表示没有时间或时区的日期;java.time.LocalTime类表示没有日期和时区的时间;java.time.LocalDateTime类表示没有时区的日期和时间。java.time.Instant类表示一个 Unix 时间戳。java.time.Period用于以年、月和日来度量时间。java.time.Duration类用小时、分钟、秒和秒的分数来表示时间。
跨时区处理日期和时间,并管理夏令时带来的更改,包括日期和时间值的格式
ZoneId标识一个时区;ZoneOffset表示相对于 UTC/格林威治的时区偏移量。ZonedDateTime提供对所有三个方面的支持:日期、时间和时区。- 在不同时区工作时,您必须考虑夏令时(DST)。
java.time.format.DateTimeFormatter类支持读取或打印不同格式的日期和时间值。DateTimeFormatter类提供了用于格式化日期和时间值的预定义常量(如ISO_DATE和ISO_TIME)。- 使用区分大小写的字母对日期或时间的格式进行编码,用
DateTimeFormatter类形成日期或时间模式字符串。
使用 Instant、Period、Duration 和 TemporalUnit 定义、创建和管理基于日期和基于时间的事件
- 枚举
java.time.temporal.ChronoUnit实现了java.time.temporal.TemporalUnit接口。 TemporalUnit和ChronoUnit都处理时间单位值,如秒、分和小时,以及日期值,如日、月和年。
Question TimeChoose the correct option based on this code segment: LocalDate babyDOB = LocalDate.of(2015, Month.FEBRUARY, 20); LocalDate now = LocalDate.of(2016, Month.APRIL, 10); System.out.println(Period.between(now, babyDOB).getYears()); // PERIOD_CALC The code segment results in a compiler error in the line marked with the comment PERIOD_CALC The code segment throws a DateTimeException The code segment prints: 1 The code segment prints: -1 Which one of the following classes is best suited for storing timestamp values of application events in a file? java.time.ZoneId class java.time.ZoneOffset class java.time.Instant class java.time.Duration class java.time.Period class Given this code segment ZoneId zoneId = ZoneId.of("Asia/Singapore"); ZonedDateTime zonedDateTime = ZonedDateTime.of(LocalDateTime.now(), zoneId); System.out.println(zonedDateTime.getOffset()); assume that the time-offset value for the Asia/Singapore time zone from UTC/Greenwich is +08:00. Choose the correct option. This code segment results in throwing DateTimeException This code segment results in throwing UnsupportedTemporalTypeException The code segment prints: Asia/Singapore The code segment prints: +08:00 This code segment prints: +08:00 [Asia/Singapore] Choose the correct option based on this code segment: DateTimeFormatter dateFormat = DateTimeFormatter.ISO_DATE; // DEF LocalDate dateOfBirth = LocalDate.of(2015, Month.FEBRUARY, 31); System.out.println(dateFormat.format(dateOfBirth)); // USE The program gives a compiler error in the line marked with the comment DEF The program gives a compiler error in the line marked with the comment USE The code segment prints: 2015-02-31 The code segment prints: 2015-02-03 This code segment throws java.time.DateTimeException with the message "Invalid date 'FEBRUARY 31'" Consider this code segment: DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEEE", Locale.US); System.out.println(formatter.format(LocalDateTime.now())); Which of the following outputs matches the string pattern "EEEE" given in this code segment? F Friday Sept September
答案:
The code segment prints: -1 Here are the arguments to the between() method in the Period class: Period between(LocalDate startDateInclusive, LocalDate endDateExclusive) The first argument is the start and the second argument is the end, and hence the call Period.between(now, babyDOB) results in -1 (not +1). C. Instant class The Instant class stores the number of seconds elapsed since the start of the Unix epoch (1970-01-01T00:00:00Z). The Instant class is suitable for storing a log of application events in a file as timestamp values. The ZoneId and ZoneOffset classes are related to time zones and hence are unrelated to storing timestamp values. The Duration class is for time-based values in terms of quantity of time (such as seconds, minutes, and hours). The Period class is for date-based values such as years, months, and days. D. The code segment prints: +08:00 Given a ZonedDateTime object, the getOffset() method returns a ZoneOffset object that corresponds to the offset of the time zone from UTC/Greenwich. Given that the time-offset value for the Asia/Singapore zone from UTC/Greenwich is +08:00, the toString() method of ZoneOffset prints the string “+08:00” to the console. E. This code segment throws java.time.DateTimeException with the message "Invalid date 'FEBRUARY 31'". The date value 31 passed in the call LocalDate.of(2015, 2, 31); is invalid for the month February, and hence the of() method in the LocalDate class throws DateTimeException. One of the predefined values in DateTimeFormatter is ISO_DATE. Hence, it does not result in a compiler error for the statement marked with the comment DEF. The statement marked with the comment USE compiles without errors because it is the correct way to use the format() method in the DateTimeFormatter class. B. Friday E is the day name in the week; the pattern "EEEE" prints the name of the day in its full format. “Fri” is a short form that would be printed by the pattern "E", but "EEEE" prints the day of the week in full form: for example, “Friday”. Because the locale is Locale.US, the result is printed in English. The output “Sept” or “September” is impossible because E refers to the name in the week, not in a month.