原文:docs.oracle.com/javase/tutorial/reallybigindex.html

问题和练习:泛型

原文:docs.oracle.com/javase/tutorial/java/generics/QandE/generics-questions.html

  1. 编写一个通用方法来计算集合中具有特定属性的元素数量(例如,奇数、质数、回文数)。
  2. 以下类会编译吗?如果不会,为什么?
public final class Algorithm {
    public static <T> T max(T x, T y) {
        return x > y ? x : y;
    }
}
  1. 编写一个通用方法来交换数组中两个不同元素的位置。
  2. 如果编译器在编译时擦除所有类型参数,为什么应该使用泛型?
  3. 在类型擦除后,以下类被转换为什么?
public class Pair<K, V> {

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() { return key; }
    public V getValue() { return value; }

    public void setKey(K key)     { this.key = key; }
    public void setValue(V value) { this.value = value; }

    private K key;
    private V value;
}
  1. 以下方法在类型擦除后会转换为什么?
public static <T extends Comparable<T>>
    int findFirstGreaterThan(T[] at, T elem) {
    // ...
}
  1. 以下方法会编译吗?如果不会,为什么?
public static void print(List<? extends Number> list) {
    for (Number n : list)
        System.out.print(n + " ");
    System.out.println();
}
  1. 编写一个通用方法来查找列表范围begin, end)中的最大元素。
  2. 以下类会编译吗?如果不会,为什么?
public class Singleton<T> {

    public static T getInstance() {
        if (instance == null)
            instance = new Singleton<T>();

        return instance;
    }

    private static T instance = null;
}
  1. 给定以下类:
class Shape { /* ... */ }
class Circle extends Shape { /* ... */ }
class Rectangle extends Shape { /* ... */ }

class Node<T> { /* ... */ }

以下代码会编译吗?如果不会,为什么?

Node<Circle> nc = new Node<>();
Node<Shape>  ns = nc;
  1. 考虑这个类:
class Node<T> implements Comparable<T> {
    public int compareTo(T obj) { /* ... */ }
    // ...
}

以下代码会编译吗?如果不会,为什么?

Node<String> node = new Node<>();
Comparable<String> comp = node;
  1. 如何调用以下方法来找到列表中与指定整数列表互质的第一个整数?
public static <T>
    int findFirst(List<T> list, int begin, int end, UnaryPredicate<T> p)

注意,两个整数ab互质,如果 gcd(a, b) = 1,其中 gcd 是最大公约数的缩写。

[检查你的答案。

课程:包(Packages)

原文:docs.oracle.com/javase/tutorial/java/package/index.html

这节课讲解了如何将类和接口打包成包,如何使用在包中的类,以及如何安排文件系统以便编译器能够找到你的源文件。

创建和使用包

原文:docs.oracle.com/javase/tutorial/java/package/packages.html

为了使类型更容易找到和使用,避免命名冲突,并控制访问权限,程序员将相关类型的组合打包成包。


定义: 是提供访问保护和命名空间管理的相关类型的分组。请注意,类型 指的是类、接口、枚举和注解类型。枚举和注解类型是类和接口的特殊种类,因此在本课程中,类型 经常简称为类和接口


Java 平台中的类型是通过功能将类打包在一起的各种包的成员:基本类在java.lang中,用于读写(输入和输出)的类在java.io中,依此类推。你也可以将你的类型放在包中。

假设你编写了一组表示图形对象的类,比如圆、矩形、线条和点。你还编写了一个接口,Draggable,如果类可以被鼠标拖动,则实现该接口。

//*in the Draggable.java file*
public interface Draggable {
    ...
}

//*in the Graphic.java file*
public abstract class Graphic {
    ...
}

//*in the Circle.java file*
public class Circle extends Graphic
    implements Draggable {
    . . .
}

//*in the Rectangle.java file*
public class Rectangle extends Graphic
    implements Draggable {
    . . .
}

//*in the Point.java file*
public class Point extends Graphic
    implements Draggable {
    . . .
}

//*in the Line.java file*
public class Line extends Graphic
    implements Draggable {
    . . .
}

你应该将这些类和接口打包到一个包中,原因包括以下几点:

  • 你和其他程序员可以轻松确定这些类型是相关的。
  • 你和其他程序员知道在哪里找到可以提供与图形相关功能的类型。
  • 你的类型名称不会与其他包中的类型名称冲突,因为包会创建一个新的命名空间。
  • 你可以允许包内的类型彼此之间具有无限制的访问,但仍然限制包外类型的访问。

创建一个包

原文:docs.oracle.com/javase/tutorial/java/package/createpkgs.html

要创建一个包,你需要为包选择一个名称(命名约定将在下一节讨论),并在包含你想要放入包中的类型(类、接口、枚举和注解类型)的每个源文件的顶部放置一个带有该名称的package语句。

包语句(例如,package graphics;)必须是源文件中的第一行。每个源文件中只能有一个包语句,并且它适用于文件中的所有类型。


**注意:**如果你在单个源文件中放入多个类型,只能有一个是public的,并且它必须与源文件同名。例如,你可以在文件Circle.java中定义public class Circle,在文件Draggable.java中定义public interface Draggable,在文件Day.java中定义public enum Day,等等。

你可以在同一个文件中包含非公共类型和一个公共类型(这是强烈不推荐的,除非非公共类型很小并且与公共类型密切相关),但只有公共类型可以从包外访问。所有顶级的非公共类型将是包私有的。


如果你将前面部分列出的图形接口和类放在一个名为graphics的包中,你将需要六个源文件,如下所示:

//*in the Draggable.java file*
package graphics;
public interface Draggable {
    . . .
}

//*in the Graphic.java file*
package graphics;
public abstract class Graphic {
    . . .
}

//*in the Circle.java file*
package graphics;
public class Circle extends Graphic
    implements Draggable {
    . . .
}

//*in the Rectangle.java file*
package graphics;
public class Rectangle extends Graphic
    implements Draggable {
    . . .
}

//*in the Point.java file*
package graphics;
public class Point extends Graphic
    implements Draggable {
    . . .
}

//*in the Line.java file*
package graphics;
public class Line extends Graphic
    implements Draggable {
    . . .
}

如果你不使用package语句,你的类型将会进入一个无名包。一般来说,无名包只适用于小型或临时应用程序,或者当你刚开始开发过程时。否则,类和接口应该放在命名包中。

包命名

原文:docs.oracle.com/javase/tutorial/java/package/namingpkgs.html

全球范围内的程序员使用 Java 编程语言编写类和接口,很可能许多程序员会为不同类型使用相同的名称。事实上,前面的例子就是这样做的:它定义了一个 Rectangle 类,而 java.awt 包中已经有一个 Rectangle 类。尽管如此,如果它们位于不同的包中,编译器仍允许两个类具有相同的名称。每个 Rectangle 类的完全限定名称包括包名。也就是说,graphics 包中的 Rectangle 类的完全限定名称是 graphics.Rectangle,而 java.awt 包中的 Rectangle 类的完全限定名称是 java.awt.Rectangle

这种方法很有效,除非两个独立的程序员使用相同的包名。如何避免这个问题?约定。

命名约定

包名全部小写以避免与类或接口的名称冲突。

公司使用其反转的互联网域名作为其包名的起始部分—例如,com.example.mypackage 表示由 example.com 的程序员创建的名为 mypackage 的包。

公司内部发生的名称冲突需要在公司内部通过约定处理,也许可以在公司名称后面加上地区或项目名称(例如,com.example.region.mypackage)。

Java 语言中的包以 java.javax. 开头。

在某些情况下,互联网域名可能不是有效的包名。如果域名包含连字符或其他特殊字符,如果包名以数字或其他 Java 名称不允许用作 Java 名称开头的字符开头,或者包名包含保留的 Java 关键字,例如 “int”。在这种情况下,建议的约定是添加下划线。例如:

合法化包名

域名

包名前缀

hyphenated-name.example.org

org.example.hyphenated_name

example.int

int_.example

123name.example.com

com.example._123name

使用包成员

原文:docs.oracle.com/javase/tutorial/java/package/usepkgs.html

组成包的类型被称为包成员

要从其包外部使用public包成员,你必须执行以下操作之一:

  • 通过其完全限定名称引用成员
  • 导入包成员
  • 导入成员的整个包

每种情况都适用于不同的情况,如下面的部分所解释的。

通过其限定名称引用包成员

到目前为止,在本教程中的大多数示例都通过其简单名称引用类型,如RectangleStackOfInts。如果你编写的代码与该成员在同一个包中,或者已经导入了该成员,你可以使用包成员的简单名称。

但是,如果你尝试使用来自不同包的成员,并且该包尚未被导入,你必须使用成员的完全限定名称,其中包括包名称。以下是在前面示例中声明的graphics包中的Rectangle类的完全限定名称。

graphics.Rectangle

你可以使用这个限定名称来创建graphics.Rectangle的实例:

graphics.Rectangle myRect = new graphics.Rectangle();

对于不经常使用的限定名称是可以的。然而,当一个名称被重复使用时,反复输入名称变得乏味,代码变得难以阅读。作为替代方案,你可以导入成员或其包,然后使用其简单名称。

导入包成员

要将特定成员导入当前文件,请在文件开头放置一个import语句,在任何类型定义之前,但在package语句之后(如果有的话)。以下是如何从前一节中创建的graphics包中导入Rectangle类。

import graphics.Rectangle;

现在你可以通过其简单名称引用Rectangle类。

Rectangle myRectangle = new Rectangle();

如果你只从graphics包中使用少量成员,这种方法很有效。但如果你从一个包中使用许多类型,你应该导入整个包。

导入整个包

要导入特定包中包含的所有类型,请使用带有星号(*)通配符的import语句。

import graphics.*;

现在你可以通过其简单名称引用graphics包中的任何类或接口。

Circle myCircle = new Circle();
Rectangle myRectangle = new Rectangle();

import语句中的星号只能用于指定包中的所有类,如下所示。它不能用于匹配包中的一部分类。例如,以下内容不匹配以A开头的graphics包中的所有类。

// *does not work*
import graphics.A*;

相反,它会生成编译器错误。通常情况下,使用import语句只导入单个包成员或整个包。


注意: 另一种不太常见的 import 形式允许你导入封闭类的公共嵌套类。例如,如果 graphics.Rectangle 类包含有用的嵌套类,比如 Rectangle.DoubleWideRectangle.Square,你可以通过以下两个语句导入 Rectangle 及其嵌套类。

import graphics.Rectangle;
import graphics.Rectangle.*;

请注意,第二个导入语句不会导入 Rectangle

另一种不太常见的 import 形式,即静态导入语句,将在本节末尾讨论。


为了方便起见,Java 编译器会自动为每个源文件导入两个完整的包:(1)java.lang 包和(2)当前包(当前文件的包)。

包的表面层次结构

起初,包看起来是分层的,但实际上并非如此。例如,Java API 包括一个 java.awt 包,一个 java.awt.color 包,一个 java.awt.font 包,以及许多以 java.awt 开头的其他包。然而,java.awt.color 包、java.awt.font 包和其他 java.awt.xxxx不包含java.awt 包中。前缀 java.awt(Java 抽象窗口工具包)用于一些相关的包,以明确显示它们之间的关系,而不是表示包含关系。

导入 java.awt.* 导入了 java.awt 包中的所有类型,但不会导入 java.awt.colorjava.awt.font 或任何其他 java.awt.xxxx 包。如果你计划使用 java.awt.color 中的类以及 java.awt 中的类,你必须导入这两个包及其所有文件:

import java.awt.*;
import java.awt.color.*;

名称歧义

如果一个包中的成员与另一个包中的成员同名,并且两个包都被导入,你必须通过其限定名称引用每个成员。例如,graphics 包定义了一个名为 Rectangle 的类。java.awt 包也包含一个 Rectangle 类。如果 graphicsjava.awt 都被导入,以下内容是模棱两可的。

Rectangle rect;

在这种情况下,你必须使用成员的完全限定名称来指示你想要的确切 Rectangle 类。例如,

graphics.Rectangle rect;

静态导入语句

有些情况下,你需要频繁访问一个或两个类的静态 final 字段(常量)和静态方法。反复添加这些类的名称可能会导致代码混乱。静态导入语句为你提供了一种导入你想要使用的常量和静态方法的方式,这样你就不需要为它们的类名添加前缀。

java.lang.Math 类定义了 PI 常量和许多静态方法,包括用于计算正弦、余弦、正切、平方根、最大值、最小值、指数等的方法。例如,

public static final double PI 
    = 3.141592653589793;
public static double cos(double a)
{
    ...
}

通常,要从另一个类中使用这些对象,你需要添加类名前缀,如下所示。

double r = Math.cos(Math.PI * theta);

使用静态导入语句可以导入 java.lang.Math 的静态成员,这样就不需要在类名Math前加前缀了。Math的静态成员可以单独导入:

import static java.lang.Math.PI;

或者作为一个组:

import static java.lang.Math.*;

一旦它们被导入,静态成员可以无需限定地使用。例如,前面的代码片段将变为:

double r = cos(PI * theta);

显然,你可以编写自己的类,其中包含你经常使用的常量和静态方法,然后使用静态导入语句。例如,

import static mypackage.MyConstants.*;

注意: 静态导入要非常谨慎使用。过度使用静态导入会导致代码难以阅读和维护,因为代码读者无法知道哪个类定义了特定的静态对象。正确使用静态导入可以通过消除类名重复使代码更易读。


管理源文件和类文件

原文:docs.oracle.com/javase/tutorial/java/package/managingfiles.html

Java 平台的许多实现依赖于分层文件系统来管理源文件和类文件,尽管Java 语言规范并不要求这样做。策略如下。

将一个类、接口、枚举或注解类型的源代码放在一个文本文件中,文件名为类型的简单名称,扩展名为.java。例如:

//in the Rectangle.java file 
package graphics;
public class Rectangle {
   ... 
}

然后,将源文件放在一个反映类型所属包名的目录中:

.....\graphics\Rectangle.java

包成员的限定名称和文件的路径名称是平行的,假设使用 Microsoft Windows 文件名分隔符反斜杠(对于 UNIX,请使用正斜杠)。

  • 类名graphics.Rectangle
  • 文件路径graphics\Rectangle.java

正如你应该记得的那样,按照惯例,公司使用其反转的互联网域名作为其包名。例如,其互联网域名为example.com的 Example 公司将在其所有包名之前加上com.example。包名的每个组件对应一个子目录。因此,如果 Example 公司有一个包含Rectangle.java源文件的com.example.graphics包,它将包含在一系列子目录中,如下所示:

....\com\example\graphics\Rectangle.java

当你编译一个源文件时,编译器为其中定义的每个类型创建一个不同的输出文件。输出文件的基本名称是类型的名称,其扩展名是.class。例如,如果源文件如下所示

//in the Rectangle.java file
package com.example.graphics;
public class Rectangle {
      . . . 
}

class Helper{
      . . . 
}

然后编译后的文件将位于:

<path to the parent directory of the output files>\com\example\graphics\Rectangle.class
<path to the parent directory of the output files>\com\example\graphics\Helper.class

.java源文件一样,编译后的.class文件应该在反映包名的一系列目录中。然而,.class文件的路径不一定要与.java源文件的路径相同。你可以将源文件和类文件目录分开管理,如:

<path_one>\sources\com\example\graphics\Rectangle.java

<path_two>\classes\com\example\graphics\Rectangle.class

通过这样做,你可以将classes目录提供给其他程序员,而不会泄露你的源代码。你还需要以这种方式管理源文件和类文件,以便编译器和 Java 虚拟机(JVM)可以找到程序中使用的所有类型。

classes目录的完整路径,<path_two>\classes,被称为类路径,并通过CLASSPATH系统变量设置。编译器和 JVM 都会通过将包名添加到类路径来构建到你的.class文件的路径。例如,如果

<path_two>\classes

是你的类路径,包名是

com.example.graphics,

然后编译器和 JVM 会在以下位置查找.class文件

<path_two>\classes\com\example\graphics.

类路径可以包括多个路径,用分号(Windows)或冒号(UNIX)分隔。默认情况下,编译器和 JVM 会搜索当前目录和包含 Java 平台类的 JAR 文件,因此这些目录会自动包含在你的类路径中。

设置 CLASSPATH 系统变量

要在 Windows 和 UNIX(Bourne shell)中显示当前的CLASSPATH变量,请使用以下命令:

In Windows:   C:\> set CLASSPATH
In UNIX:      % echo $CLASSPATH

要删除当前CLASSPATH变量的内容,请使用以下命令:

In Windows:   C:\> set CLASSPATH=
In UNIX:      % unset CLASSPATH; export CLASSPATH

要设置CLASSPATH变量,请使用以下命令(例如):

In Windows:   C:\> set CLASSPATH=C:\users\george\java\classes
In UNIX:      % CLASSPATH=/home/george/java/classes; export CLASSPATH

创建和使用包的总结

原文:docs.oracle.com/javase/tutorial/java/package/summary-package.html

要为一个类型创建一个包,将一个package语句放在包含该类型(类、接口、枚举或注解类型)的源文件中的第一个语句位置。

要使用不同包中的公共类型,你有三种选择:(1)使用类型的完全限定名称,(2)导入类型,或者(3)导入包含该类型的整个包。

包的源文件和类文件的路径名称与包的名称相对应。

你可能需要设置你的CLASSPATH,以便编译器和 JVM 可以找到你的类型的.class文件。

问题和练习:创建和使用包

原文:docs.oracle.com/javase/tutorial/java/package/QandE/packages-questions.html

问题

假设你已经编写了一些类。突然间,你决定将它们分成三个包,如下表所列。此外,假设这些类当前位于默认包中(没有package语句)。

目标包

包名称

类名称

mygame.server

Server

mygame.shared

Utilities

mygame.client

Client

  1. 你需要在每个源文件中添加哪行代码才能将每个类放在正确的包中?
  2. 为了遵循目录结构,你需要在开发目录中创建一些子目录,并将源文件放在正确的子目录中。你需要创建哪些子目录?每个源文件应放在哪个子目录中?
  3. 你认为你需要对源文件进行其他任何更改才能使它们正确编译吗?如果需要,是什么?

练习

下载列在这里的源文件。

  • Client
  • Server
  • Utilities
  1. 使用刚刚下载的源文件实现你在问题 1 到 3 中提出的更改。
  2. 编译修改后的源文件。(提示:如果你是从命令行调用编译器(而不是使用构建工具),请从包含你刚刚创建的mygame目录的目录中调用编译器。

检查你的答案。

Trail: Java 基础类

原文:docs.oracle.com/javase/tutorial/essential/index.html

本教程讨论了对大多数程序员至关重要的 Java 平台类。

解释了异常机制以及如何用它来处理错误和其他异常情况。本课程描述了异常是什么,如何抛出和捕获异常,一旦捕获异常后该如何处理,以及如何使用异常类层次结构。

包括用于基本输入和输出的 Java 平台类。它主要关注I/O 流,这是一个极大简化 I/O 操作的强大概念。本课程还介绍了序列化,它允许程序将整个对象写入流并再次读取它们。然后,课程介绍了一些文件系统操作,包括随机访问文件。最后,简要介绍了新 I/O API 的高级功能。

解释了如何编写能够同时执行多个任务的应用程序。Java 平台从头开始设计,以支持并发编程,在 Java 编程语言和 Java 类库中提供基本的并发支持。自 5.0 版本以来,Java 平台还包括高级并发 API。本课程介绍了平台的基本并发支持,并总结了java.util.concurrent包中一些高级 API。

是由底层操作系统、Java 虚拟机、类库和应用程序启动时提供的各种配置数据定义的。本课程描述了应用程序用于检查和配置其平台环境的一些 API。

是一种根据集合中每个字符串共享的共同特征描述字符串集合的方法。它们可用于搜索、编辑或操作文本和数据。正则表达式的复杂程度各不相同,但一旦理解了它们的构造基础,您就能解读(或创建)任何正则表达式。本课程教授了java.util.regex API 支持的正则表达式语法,并提供了几个工作示例以说明各种对象之间的交互方式。

课程:异常

原文:docs.oracle.com/javase/tutorial/essential/exceptions/index.html

Java 编程语言使用异常来处理错误和其他异常事件。本课程描述了何时以及如何使用异常。

什么是异常?

异常是程序执行过程中发生的事件,打断了指令的正常流程。

捕获或声明要求

这一部分涵盖了如何捕获和处理异常。讨论包括trycatchfinally块,以及链式异常和日志记录。

如何抛出异常

这一部分涵盖了throw语句和Throwable类及其子类。

try-with-resources 语句

这一部分描述了try-with-resources语句,它是一个声明一个或多个资源的try语句。资源是程序完成后必须关闭的对象。try-with-resources语句确保在语句结束时关闭每个资源。

未经检查的异常 - 争议

这一部分解释了由RuntimeException子类指示的未经检查异常的正确和不正确使用。

异常的优势

使用异常来管理错误相对于传统的错误管理技术有一些优势。您将在本节中了解更多信息。

总结

问题和练习

什么是异常?

原文:docs.oracle.com/javase/tutorial/essential/exceptions/definition.html

术语异常是“异常事件”的简称。


定义: 异常是程序执行过程中发生的事件,打断了程序正常指令流程。


当方法内部发生错误时,方法会创建一个对象并将其交给运行时系统。这个对象称为异常对象,包含有关错误的信息,包括错误发生时的类型和程序状态。创建异常对象并将其交给运行时系统称为抛出异常

方法抛出异常后,运行时系统会尝试寻找处理异常的方法。可以处理异常的一系列可能的“方法”是调用到发生错误的方法的有序方法列表。这些方法的列表称为调用堆栈(见下图)。

Java 中文地名翻译成英文_包名

调用堆栈。

运行时系统在调用堆栈中搜索包含可以处理异常的代码块的方法。这个代码块称为异常处理程序。搜索从发生错误的方法开始,并按照调用方法的相反顺序在调用堆栈中进行。当找到适当的处理程序时,运行时系统将异常传递给处理程序。如果抛出的异常对象的类型与处理程序可以处理的类型匹配,则认为异常处理程序是适当的。

选择的异常处理程序被称为捕获异常。如果运行时系统在调用堆栈上详尽搜索而找不到适当的异常处理程序,如下图所示,运行时系统(以及因此程序)将终止。

Java 中文地名翻译成英文_Java 中文地名翻译成英文_02

在调用堆栈中搜索异常处理程序。

使用异常来管理错误相比传统的错误管理技术有一些优势。您可以在异常的优势部分了解更多信息。

捕获或指定要求

原文:docs.oracle.com/javase/tutorial/essential/exceptions/catchOrDeclare.html

有效的 Java 编程语言代码必须遵守捕获或指定要求。这意味着可能引发某些异常的代码必须被以下之一包围:

  • 一个捕获异常的try语句。try必须提供异常处理程序,如 Catching and Handling Exceptions 中所述。
  • 指定可能引发异常的方法。该方法必须提供列出异常的throws子句,如 Specifying the Exceptions Thrown by a Method 中所述。

未遵守捕获或指定要求的代码将无法编译。

并非所有异常都受捕获或指定要求的约束。要理解原因,我们需要看一下三种基本类别的异常,其中只有一种受到该要求的约束。

三种异常类型

第一种异常是受检异常。这些是一个良好编写的应用程序应该预料并从中恢复的异常情况。例如,假设一个应用程序提示用户输入文件名,然后通过将名称传递给java.io.FileReader的构造函数来打开文件。通常,用户提供现有的可读文件的名称,因此FileReader对象的构造成功,应用程序的执行正常进行。但有时用户提供不存在文件的名称,构造函数会抛出java.io.FileNotFoundException。一个良好编写的程序将捕获此异常并通知用户错误,可能提示更正的文件名。

受检异常受到捕获或指定要求的约束。所有异常都是受检异常,除了由ErrorRuntimeException及其子类指示的异常。

第二种异常是错误。这些是应用程序外部的异常情况,应用程序通常无法预料或从中恢复。例如,假设一个应用程序成功打开一个输入文件,但由于硬件或系统故障无法读取文件。读取失败将抛出java.io.IOError。应用程序可能选择捕获此异常,以通知用户问题,但程序打印堆栈跟踪并退出也是有道理的。

错误不受捕获或指定要求的约束。错误是由Error及其子类指示的异常。

第三种异常是运行时异常。这些是应用程序内部的异常情况,应用程序通常无法预料或恢复。这通常表示编程错误,如逻辑错误或不正确使用 API。例如,考虑先前描述的应用程序将文件名传递给FileReader构造函数。如果逻辑错误导致将null传递给构造函数,则构造函数将抛出NullPointerException。应用程序可以捕获此异常,但更有意义的是消除导致异常发生的错误。

运行时异常不受捕获或指定要求的约束。运行时异常是由RuntimeException及其子类指示的异常。

错误和运行时异常统称为未经检查的异常

绕过捕获或指定

一些程序员认为捕获或指定要求是异常机制中的一个严重缺陷,并通过在需要检查的异常位置使用未经检查的异常来绕过它。一般来说,这是不推荐的。章节未经检查的异常 - 争议讨论了何时适合使用未经检查的异常。

捕获和处理异常

原文:docs.oracle.com/javase/tutorial/essential/exceptions/handling.html

本节描述了如何使用三个异常处理组件——trycatchfinally块——编写异常处理程序。然后,解释了在 Java SE 7 中引入的try-with-resources 语句。try-with-resources 语句特别适用于使用Closeable资源的情况,比如流。

本节的最后部分通过一个示例演示并分析了各种情况下的发生情况。

以下示例定义并实现了一个名为ListOfNumbers的类。在构造时,ListOfNumbers创建一个包含 10 个顺序值为 0 到 9 的Integer元素的ArrayListListOfNumbers类还定义了一个名为writeList的方法,该方法将数字列表写入名为OutFile.txt的文本文件。此示例使用了java.io中定义的输出类,这些类在基本 I/O 中有介绍。

// Note: This class will not compile yet.
import java.io.*;
import java.util.List;
import java.util.ArrayList;

public class ListOfNumbers {

    private List<Integer> list;
    private static final int SIZE = 10;

    public ListOfNumbers () {
        list = new ArrayList<Integer>(SIZE);
        for (int i = 0; i < SIZE; i++) {
            list.add(new Integer(i));
        }
    }

    public void writeList() {
	// The FileWriter constructor throws IOException, which must be caught.
        PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt"));

        for (int i = 0; i < SIZE; i++) {
            // The get(int) method throws IndexOutOfBoundsException, which must be caught.
            out.println("Value at: " + i + " = " + list.get(i));
        }
        out.close();
    }
}

粗体的第一行是对构造函数的调用。构造函数在文件上初始化一个输出流。如果文件无法打开,构造函数会抛出一个IOException异常。第二行粗体是对ArrayList类的get方法的调用,如果其参数的值太小(小于 0)或太大(大于ArrayList当前包含的元素数量),则会抛出IndexOutOfBoundsException异常。

如果尝试编译ListOfNumbers类,编译器会打印有关FileWriter构造函数抛出的异常的错误消息。但是,它不会显示有关get方法抛出的异常的错误消息。原因是构造函数抛出的异常IOException是一个已检查异常,而get方法抛出的异常IndexOutOfBoundsException是一个未检查异常。

现在您熟悉了ListOfNumbers类以及其中可能抛出异常的位置,您可以编写异常处理程序来捕获和处理这些异常了。

try 块

原文:docs.oracle.com/javase/tutorial/essential/exceptions/try.html

构建异常处理程序的第一步是将可能会抛出异常的代码放在一个try块中。一般来说,一个try块看起来像下面这样:

try {
    *code*
}
*catch and finally blocks . . .*

在示例中标记为*code*的部分包含一个或多个可能会抛出异常的合法代码行。(catchfinally块将在接下来的两个小节中解释。)

要为ListOfNumbers类中的writeList方法构建一个异常处理程序,将writeList方法中可能会抛出异常的语句放在一个try块中。有多种方法可以做到这一点。你可以将每一行可能会抛出异常的代码放在自己的try块中,并为每个提供单独的异常处理程序。或者,你可以将所有的writeList代码放在一个单独的try块中,并为其关联多个处理程序。以下清单使用一个try块来处理整个方法,因为相关代码非常简短。

private List<Integer> list;
private static final int SIZE = 10;

public void writeList() {
    PrintWriter out = null;
    try {
        System.out.println("Entered try statement");
        FileWriter f = new FileWriter("OutFile.txt");
        out = new PrintWriter(f);
        for (int i = 0; i < SIZE; i++) {
            out.println("Value at: " + i + " = " + list.get(i));
        }
    }
    catch and finally blocks  . . .
}

如果在try块中发生异常,该异常将由与之关联的异常处理程序处理。要为try块关联一个异常处理程序,必须在其后放置一个catch块;下一节,catch 块,将向你展示如何做到这一点。

异常处理块

原文:docs.oracle.com/javase/tutorial/essential/exceptions/catch.html

通过在try块后直接提供一个或多个catch块,将异常处理程序与try块关联起来。在try块结束和第一个catch块开始之间不能有任何代码。

try {

} catch (*ExceptionType name*) {

} catch (*ExceptionType name*) {

}

每个catch块都是一个异常处理程序,处理其参数指示的异常类型。参数类型*ExceptionType*声明了处理程序可以处理的异常类型,必须是从Throwable类继承的类名。处理程序可以使用*name*引用异常。

catch块包含在异常处理程序被调用时执行的代码。当处理程序是调用堆栈中第一个*ExceptionType*与抛出异常类型匹配的处理程序时,运行时系统会调用异常处理程序。如果抛出的对象可以合法地分配给异常处理程序的参数,则系统认为它匹配。

以下是writeList方法的两个异常处理程序:

try {

} catch (IndexOutOfBoundsException e) {
    System.err.println("IndexOutOfBoundsException: " + e.getMessage());
} catch (IOException e) {
    System.err.println("Caught IOException: " + e.getMessage());
}

异常处理程序不仅可以打印错误消息或停止程序。它们可以进行错误恢复,提示用户做出决定,或者使用链式异常将错误传播到更高级别的处理程序,如链式异常部分所述。

使用一个异常处理程序捕获多种类型的异常

在 Java SE 7 及更高版本中,单个catch块可以处理多种类型的异常。这个特性可以减少代码重复,并减少捕获过于宽泛异常的诱惑。

catch子句中,指定阻止处理的异常类型,并用竖线(|)分隔每种异常类型:

catch (IOException|SQLException ex) {
    logger.log(ex);
    throw ex;
}

注意:如果一个catch块处理多种异常类型,则catch参数隐式为final。在这个例子中,catch参数exfinal,因此您不能在catch块内为其分配任何值。

finally 块

原文:docs.oracle.com/javase/tutorial/essential/exceptions/finally.html

finally总是try块退出时执行。这确保了即使发生意外异常,finally块也会被执行。但finally不仅仅用于异常处理,它允许程序员避免清理代码被returncontinuebreak意外绕过。将清理代码放在finally块中始终是一个良好的实践,即使不预期发生异常。


**注意:**如果 JVM 在执行trycatch代码时退出,则finally块可能不会执行。


你一直在这里工作的writeList方法的try块打开了一个PrintWriter。程序应该在退出writeList方法之前关闭该流。这带来了一个有点复杂的问题,因为writeListtry块可以以三种方式之一退出。

  1. new FileWriter语句失败并抛出IOException
  2. list.get(i)语句失败并抛出IndexOutOfBoundsException
  3. 一切顺利,try块正常退出。

无论try块内发生了什么,运行时系统始终执行finally块中的语句。因此,这是执行清理操作的完美位置。

以下writeList方法的finally块清理并关闭PrintWriterFileWriter

finally {
    if (out != null) { 
        System.out.println("Closing PrintWriter");
        out.close(); 
    } else { 
        System.out.println("PrintWriter not open");
    } 
    if (f != null) {
	    System.out.println("Closing FileWriter");
	    f.close();
	}	
}

**重要:**在关闭文件或恢复资源时,请使用try-with-resources 语句而不是finally块。以下示例使用try-with-resources 语句清理和关闭writeList方法的PrintWriterFileWriter

public void writeList() throws IOException {
    try (FileWriter f = new FileWriter("OutFile.txt");
         PrintWriter out = new PrintWriter(f)) {
        for (int i = 0; i < SIZE; i++) {
            out.println("Value at: " + i + " = " + list.get(i));
        }
    }
}

try-with-resources 语句在不再需要时自动释放系统资源。请参见 try-with-resources 语句。


try-with-resources 语句

原文:docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

try-with-resources 语句是一个声明一个或多个资源的try语句。资源是程序在使用完后必须关闭的对象。try-with-resources 语句确保每个资源在语句结束时关闭。任何实现java.lang.AutoCloseable接口的对象,包括所有实现java.io.Closeable接口的对象,都可以用作资源。

以下示例从文件中读取第一行。它使用FileReaderBufferedReader的实例从文件中读取数据。FileReaderBufferedReader是在程序使用完后必须关闭的资源:

static String readFirstLineFromFile(String path) throws IOException {
	    try (FileReader fr = new FileReader(path);
	         BufferedReader br = new BufferedReader(fr)) {
	        return br.readLine();
	    }
	}

在这个示例中,try-with-resources 语句中声明的资源是FileReaderBufferedReader。这些资源的声明语句出现在try关键字之后的括号内。在 Java SE 7 及更高版本中,FileReaderBufferedReader类实现了java.lang.AutoCloseable接口。因为FileReaderBufferedReader实例是在try-with-resources 语句中声明的,无论try语句是否正常完成或突然中断(因为BufferedReader.readLine方法抛出IOException),它们都将被关闭。

在 Java SE 7 之前,可以使用finally块来确保资源在try语句正常完成或突然中断时关闭。以下示例使用finally块而不是try-with-resources 语句:

static String readFirstLineFromFileWithFinallyBlock(String path) throws IOException {

    FileReader fr = new FileReader(path);
    BufferedReader br = new BufferedReader(fr);
    try {
        return br.readLine();
    } finally {
        br.close();
        fr.close();
    }
}

然而,这个示例可能会有资源泄漏。程序不能仅仅依赖垃圾回收器(GC)在完成后回收资源的内存。程序还必须将资源释放回操作系统,通常通过调用资源的close方法。但是,如果程序在 GC 回收资源之前未能执行此操作,则释放资源所需的信息将丢失。这个资源,仍然被操作系统视为正在使用,已经泄漏。

在这个示例中,如果readLine方法抛出异常,并且finally块中的br.close()语句也抛出异常,那么FileReader就会泄漏。因此,使用try-with-resources 语句而不是finally块来关闭程序的资源。

如果readLineclose方法都抛出异常,则readFirstLineFromFileWithFinallyBlock方法会抛出从finally块中抛出的异常;从try块中抛出的异常会被抑制。相比之下,在示例readFirstLineFromFile中,如果try块和try-with-resources 语句都抛出异常,则readFirstLineFromFile方法会抛出从try块中抛出的异常;从try-with-resources 块中抛出的异常会被抑制。在 Java SE 7 及更高版本中,您可以检索被抑制的异常;有关更多信息,请参阅被抑制的异常部分。

以下示例检索打包在 zip 文件zipFileName中的文件的名称,并创建一个包含这些文件名称的文本文件:

public static void writeToFileZipFileContents(String zipFileName,
                                           String outputFileName)
                                           throws java.io.IOException {

    java.nio.charset.Charset charset =
         java.nio.charset.StandardCharsets.US_ASCII;
    java.nio.file.Path outputFilePath =
         java.nio.file.Paths.get(outputFileName);

    // Open zip file and create output file with 
    // try-with-resources statement

    try (
        java.util.zip.ZipFile zf =
             new java.util.zip.ZipFile(zipFileName);
        java.io.BufferedWriter writer = 
            java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
    ) {
        // Enumerate each entry
        for (java.util.Enumeration entries =
                                zf.entries(); entries.hasMoreElements();) {
            // Get the entry name and write it to the output file
            String newLine = System.getProperty("line.separator");
            String zipEntryName =
                 ((java.util.zip.ZipEntry)entries.nextElement()).getName() +
                 newLine;
            writer.write(zipEntryName, 0, zipEntryName.length());
        }
    }
}

在此示例中,try-with-resources 语句包含两个声明,它们之间用分号分隔:ZipFileBufferedWriter。当直接跟在其后的代码块正常终止或因异常终止时,BufferedWriterZipFile对象的close方法会按照这个顺序自动调用。请注意,资源的close方法按照它们创建的相反顺序调用。

以下示例使用try-with-resources 语句自动关闭java.sql.Statement对象:

public static void viewTable(Connection con) throws SQLException {

    String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES";

    try (Statement stmt = con.createStatement()) {
        ResultSet rs = stmt.executeQuery(query);

        while (rs.next()) {
            String coffeeName = rs.getString("COF_NAME");
            int supplierID = rs.getInt("SUP_ID");
            float price = rs.getFloat("PRICE");
            int sales = rs.getInt("SALES");
            int total = rs.getInt("TOTAL");

            System.out.println(coffeeName + ", " + supplierID + ", " + 
                               price + ", " + sales + ", " + total);
        }
    } catch (SQLException e) {
        JDBCTutorialUtilities.printSQLException(e);
    }
}

此示例中使用的资源java.sql.Statement是 JDBC 4.1 及更高版本 API 的一部分。

注意try-with-resources 语句可以像普通的try语句一样具有catchfinally块。在try-with-resources 语句中,任何catchfinally块都会在声明的资源关闭后运行。

被抑制的异常

try-with-resources 语句相关联的代码块中可能会抛出异常。在示例writeToFileZipFileContents中,异常可能会从try块中抛出,当尝试关闭ZipFileBufferedWriter对象时,try-with-resources 语句最多可能会抛出两个异常。如果从try块中抛出异常,并且从try-with-resources 语句中抛出一个或多个异常,则这些从try-with-resources 语句中抛出的异常会被抑制,而由该块抛出的异常就是writeToFileZipFileContents方法抛出的异常。您可以通过调用由try块抛出的异常的Throwable.getSuppressed方法来检索这些被抑制的异常。

实现 AutoCloseable 或 Closeable 接口的类

查看AutoCloseableCloseable接口的 Javadoc,以获取实现这两个接口之一的类列表。Closeable接口扩展了AutoCloseable接口。Closeable接口的close方法会抛出IOException类型的异常,而AutoCloseable接口的close方法会抛出Exception类型的异常。因此,AutoCloseable接口的子类可以重写close方法的行为,以抛出特定的异常,比如IOException,或者根本不抛出异常。

将所有内容整合在一起

原文:docs.oracle.com/javase/tutorial/essential/exceptions/putItTogether.html

前面的部分描述了如何为ListOfNumbers类中的writeList方法构造trycatchfinally代码块。现在,让我们走一遍代码,看看可能发生什么。

当所有组件放在一起时,writeList方法看起来像下面这样。

public void writeList() {
    PrintWriter out = null;

    try {
        System.out.println("Entering" + " try statement");

        out = new PrintWriter(new FileWriter("OutFile.txt"));
        for (int i = 0; i < SIZE; i++) {
            out.println("Value at: " + i + " = " + list.get(i));
        }
    } catch (IndexOutOfBoundsException e) {
        System.err.println("Caught IndexOutOfBoundsException: "
                           +  e.getMessage());

    } catch (IOException e) {
        System.err.println("Caught IOException: " +  e.getMessage());

    } finally {
        if (out != null) {
            System.out.println("Closing PrintWriter");
            out.close();
        } 
        else {
            System.out.println("PrintWriter not open");
        }
    }
}

如前所述,此方法的try块有三种不同的退出可能性;以下是其中两种。

  1. try语句中的代码失败并抛出异常。这可能是由new FileWriter语句引起的IOException,也可能是由for循环中错误的索引值引起的IndexOutOfBoundsException
  2. 一切顺利,try语句正常退出。

让我们看看在这两种退出可能性中writeList方法中会发生什么。

情况 1:发生异常

创建FileWriter的语句可能因多种原因而失败。例如,如果程序无法创建或写入指定文件,则FileWriter的构造函数会抛出IOException

FileWriter抛出IOException时,运行时系统立即停止执行try块;正在执行的方法调用不会完成。然后,运行时系统从方法调用堆栈的顶部开始搜索适当的异常处理程序。在本例中,当发生IOException时,FileWriter构造函数位于调用堆栈的顶部。然而,FileWriter构造函数没有适当的异常处理程序,因此运行时系统检查下一个方法——writeList方法——在方法调用堆栈中。writeList方法有两个异常处理程序:一个用于IOException,一个用于IndexOutOfBoundsException

运行时系统按照try语句后出现的顺序检查writeList的处理程序。第一个异常处理程序的参数是IndexOutOfBoundsException。这与抛出的异常类型不匹配,因此运行时系统检查下一个异常处理程序——IOException。这与抛出的异常类型匹配,因此运行时系统结束了对适当异常处理程序的搜索。现在,运行时已找到适当的处理程序,将执行该catch块中的代码。

异常处理程序执行后,运行时系统将控制权传递给finally块。finally块中的代码无论上面捕获的异常如何,都会执行。在这种情况下,FileWriter从未被打开,也不需要关闭。finally块执行完毕后,程序将继续执行finally块后的第一条语句。

下面是当抛出IOException时出现的ListOfNumbers程序的完整输出。

Entering try statement
Caught IOException: OutFile.txt
PrintWriter not open

以下清单中的粗体代码显示了在这种情况下执行的语句:

public void writeList() {
   PrintWriter out = null;

    try {
        System.out.println("Entering try statement");
        out = new PrintWriter(new FileWriter("OutFile.txt"));
        for (int i = 0; i < SIZE; i++)
            out.println("Value at: " + i + " = " + list.get(i));

    } catch (IndexOutOfBoundsException e) {
        System.err.println("Caught IndexOutOfBoundsException: "
                           + e.getMessage());

    } catch (IOException e) {
        System.err.println("Caught IOException: " + e.getMessage());
    } finally {
        if (out != null) {
            System.out.println("Closing PrintWriter");
            out.close();
        } 
        else {
            System.out.println("PrintWriter not open");
        }
    }
}

情景 2:try 块正常退出

在这种情况下,try块范围内的所有语句都成功执行且没有抛出异常。执行流程跳出try块,运行时系统将控制权传递给finally块。因为一切顺利,当控制权到达finally块时,PrintWriter是打开状态,finally块关闭了PrintWriter。同样,在finally块执行完毕后,程序将继续执行finally块后的第一条语句。

这是ListOfNumbers程序在没有抛出异常时的输出。

Entering try statement
Closing PrintWriter

以下示例中的粗体代码显示了在这种情况下执行的语句。

public void writeList() {
    PrintWriter out = null;
    try {
        System.out.println("Entering try statement");
        out = new PrintWriter(new FileWriter("OutFile.txt"));
        for (int i = 0; i < SIZE; i++)
            out.println("Value at: " + i + " = " + list.get(i));

    } catch (IndexOutOfBoundsException e) {
        System.err.println("Caught IndexOutOfBoundsException: "
                           + e.getMessage());

    } catch (IOException e) {
        System.err.println("Caught IOException: " + e.getMessage());

    } finally {
        if (out != null) {
            System.out.println("Closing PrintWriter");
            out.close();
        } 
        else {
            System.out.println("PrintWriter not open");
        }
    }
}

指定方法抛出的异常

原文:docs.oracle.com/javase/tutorial/essential/exceptions/declaring.html

前一节展示了如何为ListOfNumbers类中的writeList方法编写异常处理程序。有时,代码捕获可能发生的异常是合适的。然而,在其他情况下,最好让调用堆栈中更高层的方法处理异常。例如,如果你将ListOfNumbers类作为一个类包的一部分提供,你可能无法预料到包的所有用户的需求。在这种情况下,最好不要捕获异常,而是让调用堆栈中更高层的方法来处理它。

如果writeList方法不捕获其中可能发生的已检查异常,那么writeList方法必须指定它可以抛出这些异常。让我们修改原始的writeList方法,以指定它可以抛出的异常,而不是捕获它们。为了提醒你,这里是原始版本的writeList方法,它不会编译通过。

public void writeList() {
    PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt"));
    for (int i = 0; i < SIZE; i++) {
        out.println("Value at: " + i + " = " + list.get(i));
    }
    out.close();
}

要指定writeList可能会抛出两个异常,需要在writeList方法的方法声明中添加一个throws子句。throws子句包括throws关键字,后面跟着一个逗号分隔的由该方法抛出的所有异常的列表。该子句放在方法名和参数列表之后,方法范围定义的大括号之前;以下是一个示例。

public void writeList() throws IOException, IndexOutOfBoundsException {

请记住IndexOutOfBoundsException是一个未经检查的异常;在throws子句中包含它并不是强制性的。你可以只写如下内容。

public void writeList() throws IOException {

如何抛出异常

原文:docs.oracle.com/javase/tutorial/essential/exceptions/throwing.html

在捕获异常之前,某个地方的代码必须抛出异常。任何代码都可以抛出异常:您的代码、其他人编写的包中的代码(例如 Java 平台提供的包)或 Java 运行时环境。无论是什么引发了异常,它总是使用 throw 语句抛出。

正如您可能已经注意到的,Java 平台提供了许多异常类。所有这些类都是 Throwable 类的后代,所有这些类都允许程序在程序执行期间区分各种可能发生的异常类型。

您还可以创建自己的异常类来表示您编写的类中可能发生的问题。实际上,如果您是一个包开发者,您可能需要创建自己的一组异常类,以允许用户区分在您的包中可能发生的错误与在 Java 平台或其他包中发生的错误。

您还可以创建链接异常。有关更多信息,请参阅链接异常部分。

抛出语句

所有方法都使用 throw 语句来抛出异常。throw 语句需要一个参数:一个可抛出对象。可抛出对象是 Throwable 类的任何子类的实例。这里是一个 throw 语句的示例。

throw *someThrowableObject*;

让我们看看 throw 语句的上下文。以下 pop 方法取自实现常见堆栈对象的类。该方法从堆栈中移除顶部元素并返回该对象。

public Object pop() {
    Object obj;

    if (size == 0) {
        throw new EmptyStackException();
    }

    obj = objectAt(size - 1);
    setObjectAt(size - 1, null);
    size--;
    return obj;
}

pop 方法检查堆栈上是否有任何元素。如果堆栈为空(其大小等于 0),pop 实例化一个新的 EmptyStackException 对象(java.util 的成员)并将其抛出。本章的创建异常类部分解释了如何创建自己的异常类。现在,您只需要记住您只能抛出继承自 java.lang.Throwable 类的对象。

注意,pop 方法的声明中不包含 throws 子句。EmptyStackException 不是一个受检异常,因此 pop 不需要声明它可能发生。

Throwable 类及其子类

继承自Throwable类的对象包括直接后代(直接从Throwable类继承的对象)和间接后代(从Throwable类的子类或孙子类继承的对象)。下图说明了Throwable类及其最重要的子类的类层次结构。正如你所看到的,Throwable有两个直接后代:ErrorException

Java 中文地名翻译成英文_Java 中文地名翻译成英文_03

Throwable 类。

错误类

当 Java 虚拟机发生动态链接失败或其他严重故障时,虚拟机会抛出一个Error。简单的程序通常不会捕获或抛出Error

异常类

大多数程序会抛出和捕获从Exception类派生的对象。Exception表示发生了问题,但不是严重的系统问题。你编写的大多数程序会抛出和捕获Exception,而不是Error

Java 平台定义了Exception类的许多后代。这些后代表示可能发生的各种异常类型。例如,IllegalAccessException表示找不到特定方法,而NegativeArraySizeException表示程序尝试创建一个负大小的数组。

一个Exception子类,RuntimeException,用于指示 API 的不正确使用的异常。一个运行时异常的例子是NullPointerException,当一个方法尝试通过null引用访问对象的成员时会发生。本节未经检查的异常 — 争议讨论了为什么大多数应用程序不应该抛出运行时异常或子类化RuntimeException

链式异常

原文:docs.oracle.com/javase/tutorial/essential/exceptions/chained.html

应用程序通常通过抛出另一个异常来响应异常。实际上,第一个异常导致第二个异常。知道一个异常导致另一个异常时会非常有帮助。链式异常帮助程序员做到这一点。

以下是支持链式异常的Throwable中的方法和构造函数。

Throwable getCause()
Throwable initCause(Throwable)
Throwable(String, Throwable)
Throwable(Throwable)

initCauseThrowable构造函数的Throwable参数是导致当前异常的异常。getCause返回导致当前异常的异常,initCause设置当前异常的原因。

以下示例展示了如何使用链式异常。

try {

} catch (IOException e) {
    throw new SampleException("Other IOException", e);
}

在这个例子中,当捕获到IOException时,会创建一个带有原始原因附加的新的SampleException异常,并将异常链抛到下一个更高级别的异常处理程序。

访问堆栈跟踪信息

现在假设更高级别的异常处理程序想要以自己的格式转储堆栈跟踪。


定义: 堆栈跟踪提供了关于当前线程执行历史的信息,并列出了在异常发生时调用的类和方法的名称。堆栈跟踪是一个有用的调试工具,当抛出异常时,通常会利用它。


以下代码展示了如何在异常对象上调用getStackTrace方法。

catch (Exception cause) {
    StackTraceElement elements[] = cause.getStackTrace();
    for (int i = 0, n = elements.length; i < n; i++) {       
        System.err.println(elements[i].getFileName()
            + ":" + elements[i].getLineNumber() 
            + ">> "
            + elements[i].getMethodName() + "()");
    }
}

日志记录 API

下一个代码片段记录了异常发生的位置,位于catch块内部。然而,它不是手动解析堆栈跟踪并将输出发送到System.err(),而是使用java.util.logging包中的日志记录功能将输出发送到文件。

try {
    Handler handler = new FileHandler("OutFile.log");
    Logger.getLogger("").addHandler(handler);

} catch (IOException e) {
    Logger logger = Logger.getLogger("*package.name*"); 
    StackTraceElement elements[] = e.getStackTrace();
    for (int i = 0, n = elements.length; i < n; i++) {
        logger.log(Level.WARNING, elements[i].getMethodName());
    }
}

创建异常类

原文:docs.oracle.com/javase/tutorial/essential/exceptions/creating.html

在选择要抛出的异常类型时,你可以使用其他人编写的异常 — Java 平台提供了许多可以使用的异常类 — 或者你可以编写自己的异常。如果对以下任何问题回答是肯定的,那么你应该编写自己的异常类;否则,你可能可以使用别人的。

  • 是否需要一个 Java 平台中没有的异常类型?
  • 如果用户能够区分你的异常和其他供应商编写的异常,这是否有助于用户?
  • 你的代码是否抛出了多个相关的异常?
  • 如果使用别人的异常,用户是否能够访问这些异常?一个类似的问题是,你的包是否独立且自包含?

一个示例

假设你正在编写一个链表类。该类支持以下方法,以及其他方法:

  • objectAt(int n) — 返回列表中第n个位置的对象。如果参数小于 0 或大于当前列表中对象的数量,则抛出异常。
  • firstObject()
  • indexOf(Object o) — 搜索列表中指定的Object并返回其在列表中的位置。如果传入方法的对象不在列表中,则抛出异常。

链表类可以抛出多个异常,能够使用一个异常处理程序捕获链表抛出的所有异常将会很方便。此外,如果计划在一个包中分发你的链表,所有相关代码应该打包在一起。因此,链表应该提供自己的一组异常类。

下图展示了链表抛出的异常可能的类层次结构。

Java 中文地名翻译成英文_java_04

示例异常类层次结构。

选择一个超类

任何Exception子类都可以用作LinkedListException的父类。然而,快速浏览这些子类显示它们不合适,因为它们要么过于专业化,要么与LinkedListException完全无关。因此,LinkedListException的父类应该是Exception

你编写的大多数小程序和应用程序将抛出Exception对象。Error通常用于系统中的严重、严重错误,例如阻止 JVM 运行的错误。


注意: 为了编写可读性强的代码,将Exception字符串附加到所有直接或间接继承自Exception类的类名后是一个好习惯。


未检查异常 — 争议

原文:docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html

因为 Java 编程语言不要求方法捕获或指定未检查异常(RuntimeExceptionError及其子类),程序员可能会倾向于编写仅抛出未检查异常的代码,或者使所有异常子类继承自RuntimeException。这两种捷径使程序员能够编写代码,而不必理会编译器错误,也不必指定或捕获任何异常。尽管这对程序员来说可能很方便,但它绕过了catchspecify要求的意图,可能会给使用您的类的其他人造成问题。

设计者为什么决定强制一种方法来指定在其范围内可能抛出的所有未捕获的已检查异常?方法可能抛出的任何Exception都是方法的公共编程接口的一部分。调用方法的人必须了解方法可能抛出的异常,以便他们可以决定如何处理这些异常。这些异常与方法的编程接口一样重要,就像它的参数和return值一样。

下一个问题可能是:“如果记录方法的 API,包括它可能抛出的异常是如此重要,为什么不也指定运行时异常呢?”运行时异常代表的是由编程问题导致的问题,因此,API 客户端代码不能合理地预期从中恢复或以任何方式处理它们。这些问题包括算术异常,例如除以零;指针异常,例如尝试通过空引用访问对象;以及索引异常,例如尝试通过太大或太小的索引访问数组元素。

运行时异常可能在程序的任何地方发生,在典型情况下可能非常多。在每个方法声明中添加运行时异常会降低程序的清晰度。因此,编译器不要求您捕获或指定运行时异常(尽管您可以)。

一个常见的情况是抛出RuntimeException的情况是当用户错误调用方法时。例如,一个方法可以检查其参数是否不正确为null。如果参数为null,方法可能会抛出NullPointerException,这是一个未检查异常。

一般来说,不要仅仅因为不想麻烦指定方法可能抛出的异常而抛出RuntimeException或创建RuntimeException的子类。

这里是底线指导原则:如果客户端可以合理地预期从异常中恢复,那么将其作为已检查异常。如果客户端无法从异常中恢复,那么将其作为未检查异常。

异常的优点

原文:docs.oracle.com/javase/tutorial/essential/exceptions/advantages.html

现在您知道什么是异常以及如何使用它们,是时候学习在程序中使用异常的优点了。

优点 1:将错误处理代码与“常规”代码分开

异常提供了一种将发生异常情况时的处理细节与程序的主要逻辑分开的手段。在传统编程中,错误检测、报告和处理经常导致令人困惑的意大利面代码。例如,考虑这里的伪代码方法,它将整个文件读入内存。

readFile {
    *open the file;
    determine its size;
    allocate that much memory;
    read the file into memory;
    close the file;*
}

乍一看,这个函数似乎足够简单,但它忽略了所有以下潜在的错误。

  • 如果文件无法打开会发生什么?
  • 如果无法确定文件的长度会发生什么?
  • 如果无法分配足够的内存会发生什么?
  • 如果读取失败会发生什么?
  • 如果文件无法关闭会发生什么?

要处理这种情况,readFile 函数必须有更多的代码来进行错误检测、报告和处理。以下是该函数可能的示例。

errorCodeType readFile {
    initialize errorCode = 0;

    *open the file;*
    if (*theFileIsOpen*) {
        *determine the length of the file;*
        if (*gotTheFileLength*) {
            *allocate that much memory;*
            if (*gotEnoughMemory*) {
                *read the file into memory;*
                if (*readFailed*) {
                    errorCode = -1;
                }
            } else {
                errorCode = -2;
            }
        } else {
            errorCode = -3;
        }
        *close the file;*
        if (*theFileDidntClose* && *errorCode* == 0) {
            errorCode = -4;
        } else {
            errorCode = errorCode and -4;
        }
    } else {
        errorCode = -5;
    }
    return errorCode;
}

这里有太多的错误检测、报告和返回,原始的七行代码在混乱中丢失了。更糟糕的是,代码的逻辑流也丢失了,因此很难判断代码是否在做正确的事情:如果函数无法分配足够的内存,文件是否真的被关闭了?当您在编写三个月后修改方法时,确保代码继续执行正确的事情更加困难。许多程序员通过简单地忽略它来解决这个问题——当他们的程序崩溃时会报告错误。

异常使您能够编写代码的主要流程,并在其他地方处理异常情况。如果readFile函数使用异常而不是传统的错误管理技术,它会看起来更像以下内容。

*readFile* {
    try {
        *open the file;
        determine its size;
        allocate that much memory;
        read the file into memory;
        close the file;*
    } catch (*fileOpenFailed*) {
       *doSomething;*
    } catch (*sizeDeterminationFailed*) {
        *doSomething;*
    } catch (*memoryAllocationFailed*) {
        *doSomething;*
    } catch (*readFailed*) {
        *doSomething;*
    } catch (*fileCloseFailed*) {
        *doSomething;*
    }
}

请注意,异常并不免除您进行错误检测、报告和处理的工作,但它们确实帮助您更有效地组织工作。

优点 2:将错误传播到调用堆栈上

异常的第二个优点是能够将错误报告传播到方法的调用堆栈上。假设readFile方法是主程序进行的一系列嵌套方法调用中的第四个方法:method1调用method2method2调用method3,最终调用readFile

method1 {
    *call method2;*
}

method2 {
    *call method3;*
}

method3 {
    *call readFile;*
}

假设method1是唯一对readFile中可能发生的错误感兴趣的方法。传统的错误通知技术会强制method2method3readFile返回的错误代码传播到调用堆栈上,直到错误代码最终到达method1——唯一对它们感兴趣的方法。

method1 {
    errorCodeType error;
    error = *call method2;*
    if (error)
        *doErrorProcessing;*
    else
        *proceed;*
}

errorCodeType method2 {
    errorCodeType error;
    error = call method3;
    if (error)
        return error;
    else
        *proceed;*
}

errorCodeType method3 {
    errorCodeType error;
    error = call readFile;
    if (error)
        return error;
    else
        *proceed;*
}

请记住,Java 运行时环境会逆向搜索调用堆栈,以找到任何对处理特定异常感兴趣的方法。一个方法可以规避其内部抛出的任何异常,从而允许调用堆栈中更高层的方法捕获它。因此,只有关心错误的方法才需要担心检测错误。

method1 {
    try {
        *call method2;*
    } catch (*exception* e) {
        *doErrorProcessing;*
    }
}

method2 throws *exception* {
    *call method3;*
}

method3 throws exception {
    *call readFile;*
}

然而,正如伪代码所示,规避异常需要中间方法付出一些努力。在方法内部可能抛出的任何已检查异常必须在其throws子句中指定。

优势 3:分组和区分错误类型

因为程序中抛出的所有异常都是对象,异常的分组或分类是类层次结构的自然结果。Java 平台中一组相关的异常类的示例是java.io中定义的那些 — IOException及其后代。IOException是最一般的,表示在执行 I/O 时可能发生的任何类型的错误。其后代表示更具体的错误。例如,FileNotFoundException表示无法在磁盘上找到文件。

一个方法可以编写特定的处理程序,可以处理非常具体的异常。FileNotFoundException类没有后代,因此以下处理程序只能处理一种类型的异常。

catch (FileNotFoundException e) {
    ...
}

一个方法可以通过在catch语句中指定任何异常的超类来基于其组或一般类型捕获异常。例如,为了捕获所有 I/O 异常,无论其具体类型如何,异常处理程序指定一个IOException参数。

catch (IOException e) {
    ...
}

这个处理程序将能够捕获所有 I/O 异常,包括FileNotFoundExceptionEOFException等。您可以通过查询传递给异常处理程序的参数来找到发生的详细信息。例如,使用以下内容打印堆栈跟踪。

catch (IOException e) {
    // Output goes to System.err.
    e.printStackTrace();
    // Send trace to stdout.
    e.printStackTrace(System.out);
}

你甚至可以设置一个异常处理程序,用于处理这里的任何Exception

// *A (too) general exception handler*
catch (Exception e) {
    ...
}

Exception类接近Throwable类层次结构的顶部。因此,此处理程序将捕获许多其他异常,除了处理程序打算捕获的异常之外。如果您只希望程序执行的操作是打印出用户的错误消息,然后退出,您可能希望以这种方式处理异常。

然而,在大多数情况下,您希望异常处理程序尽可能具体。原因是处理程序必须首先确定发生了什么类型的异常,然后才能决定最佳的恢复策略。实际上,通过不捕获特定错误,处理程序必须适应任何可能性。过于一般化的异常处理程序可能会使代码更容易出错,因为它会捕获和处理程序员未预料到的异常,而处理程序并不打算处理这些异常。

正如所指出的,您可以创建异常组并以一般方式处理异常,或者您可以使用特定的异常类型来区分异常并以精确方式处理异常。

摘要

原文:docs.oracle.com/javase/tutorial/essential/exceptions/summary.html

程序可以使用异常来指示发生了错误。要抛出异常,请使用throw语句,并提供一个异常对象 — 一个Throwable的后代 — 以提供有关发生的具体错误的信息。抛出未捕获的已检查异常的方法必须在其声明中包含一个throws子句。

程序可以通过使用trycatchfinally块的组合来捕获异常。

  • try块标识出可能发生异常的代码块。
  • catch块标识出一个代码块,称为异常处理程序,可以处理特定类型的异常。
  • finally块标识出保证执行的代码块,并且是关闭文件、恢复资源以及在try块中封闭的代码之后进行清理的正确位置。

try语句应至少包含一个catch块或一个finally块,并且可以有多个catch块。

异常对象的类表示抛出的异常类型。异常对象可以包含有关错误的进一步信息,包括错误消息。通过异常链接,一个异常可以指向导致它的异常,后者又可以指向导致的异常,依此类推。

问题和练习

原文:docs.oracle.com/javase/tutorial/essential/exceptions/QandE/questions.html

问题

  1. 以下代码是否合法?
try {

} finally {

}
  1. 以下处理程序可以捕获哪些异常类型?
catch (Exception e) {

}

使用这种类型的异常处理程序有什么问题?

  1. 以下异常处理程序的写法有什么问题?这段代码能编译吗?
try {

} catch (Exception e) {

} catch (ArithmeticException a) {

}
  1. 将第一个列表中的每种情况与第二个列表中的一项进行匹配。
  1. `int[] A;
    A[0] = 0;`
  2. JVM 开始运行您的程序,但 JVM 找不到 Java 平台类。(Java 平台类位于classes.ziprt.jar中。)
  3. 一个程序正在读取流并达到流结束标记。
  4. 在关闭流之前和达到流结束标记之后,一个程序尝试再次读取流。
  5. __ 错误
  6. __ 已检查异常
  7. __ 编译错误
  8. __ 无例外

练习

  1. ListOfNumbers.java中添加一个readList方法。该方法应从文件中读取int值,打印每个值,并将它们附加到向量的末尾。您应该捕获所有适当的错误。您还需要一个包含要读取的数字的文本文件。
  2. 修改以下cat方法以便能够编译。
public static void cat(File file) {
    RandomAccessFile input = null;
    String line = null;

    try {
        input = new RandomAccessFile(file, "r");
        while ((line = input.readLine()) != null) {
            System.out.println(line);
        }
        return;
    } finally {
        if (input != null) {
            input.close();
        }
    }
}

检查您的答案。

课程:基本 I/O

原文:docs.oracle.com/javase/tutorial/essential/io/index.html

本课程涵盖了用于基本 I/O 的 Java 平台类。它首先关注* I/O 流*,这是一个极大简化 I/O 操作的强大概念。该课程还涉及序列化,它允许程序将整个对象写入流并再次读取它们。然后课程将查看文件 I/O 和文件系统操作,包括随机访问文件。

大多数在“ I/O 流”部分涵盖的类位于java.io包中。大多数在“文件 I/O”部分涵盖的类位于java.nio.file包中。

I/O 流

  • 字节流处理原始二进制数据的 I/O。
  • 字符流处理字符数据的 I/O,自动处理与本地字符集之间的转换。
  • 缓冲流通过减少对本机 API 的调用次数来优化输入和输出。
  • 扫描和格式化允许程序读取和写入格式化文本。
  • 从命令行进行 I/O 描述了标准流和控制台对象。
  • 数据流处理基本数据类型和String值的二进制 I/O。
  • 对象流处理对象的二进制 I/O。

文件 I/O(使用 NIO.2)

  • 什么是路径?探讨了文件系统上路径的概念。
  • Path 类介绍了java.nio.file包的基石类。
  • 路径操作查看了Path类中处理语法操作的方法。
  • 文件操作介绍了许多文件 I/O 方法共有的概念。
  • 检查文件或目录展示了如何检查文件的存在性和可访问性级别。
  • 删除文件或目录。
  • 复制文件或目录。
  • 移动文件或目录。
  • 管理元数据解释了如何读取和设置文件属性。
  • 读取、写入和创建文件展示了读取和写入文件的流和通道方法。
  • 随机访问文件展示了如何以非顺序方式读取或写入文件。
  • 创建和读取目录涵盖了特定于目录的 API,例如如何列出目录的内容。
  • 链接,符号或其他涵盖了与符号链接和硬链接相关的特定问题。
  • 遍历文件树演示了如何递归访问文件树中的每个文件和目录。
  • 查找文件展示了如何使用模式匹配搜索文件。
  • 监视目录变化展示了如何使用监视服务检测一个或多个目录中添加、删除或更新的文件。
  • 其他有用的方法涵盖了在本课程中其他地方无法涵盖的重要 API。
  • 旧版文件 I/O 代码展示了如何利用Path功能,如果你的旧代码使用了java.io.File类。提供了一个将java.io.File API 映射到java.nio.file API 的表格。

总结

本教程涵盖的关键要点总结。

问题和练习

通过尝试这些问题和练习来测试你在本教程中学到的知识。

I/O 类的实际运用

下一个教程中的许多示例,自定义网络,使用了本课程中描述的 I/O 流来从网络连接读取和写入。


安全注意事项: 一些 I/O 操作需要当前安全管理器的批准。这些教程中包含的示例程序是独立应用程序,默认情况下没有安全管理器。要在小程序中运行,大多数这些示例都需要进行修改。查看小程序的能力和限制以获取有关小程序所受的安全限制的信息。


I/O 流

原文:docs.oracle.com/javase/tutorial/essential/io/streams.html

I/O 流表示输入源或输出目的地。流可以表示许多不同类型的源和目的地,包括磁盘文件、设备、其他程序和内存数组。

流支持许多不同类型的数据,包括简单的字节、基本数据类型、本地化字符和对象。一些流只是传递数据;另一些以有用的方式操作和转换数据。

无论它们内部如何工作,所有流对使用它们的程序呈现相同简单的模型:流是一系列数据。程序使用输入流从源读取数据,一次读取一个项目:

Java 中文地名翻译成英文_源文件_05

将信息读入程序。

程序使用输出流向目的地写入数据,一次写入一个项目:

Java 中文地名翻译成英文_源文件_06

将信息从程序写入。

在本课程中,我们将看到可以处理从基本值到高级对象的各种数据的流。

上图中的数据源和数据目的地可以是任何保存、生成或消耗数据的东西。显然,这包括磁盘文件,但源或目的地也可以是另一个程序、外围设备、网络套接字或数组。

在下一节中,我们将使用最基本的流类型,字节流,来演示流 I/O 的常见操作。作为示例输入,我们将使用示例文件xanadu.txt,其中包含以下诗句:

In Xanadu did Kubla Khan
A stately pleasure-dome decree:
Where Alph, the sacred river, ran
Through caverns measureless to man
Down to a sunless sea.

字节流

原文:docs.oracle.com/javase/tutorial/essential/io/bytestreams.html

程序使用字节流来执行 8 位字节的输入和输出。所有字节流类都是从InputStreamOutputStream继承而来。

有许多字节流类。为了演示字节流的工作原理,我们将重点放在文件 I/O 字节流FileInputStreamFileOutputStream上。其他类型的字节流使用方式基本相同;它们主要在构造方式上有所不同。

使用字节流

我们将通过检查一个名为CopyBytes的示例程序来探讨FileInputStreamFileOutputStream,该程序使用字节流逐字节复制xanadu.txt

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class CopyBytes {
    public static void main(String[] args) throws IOException {

        FileInputStream in = null;
        FileOutputStream out = null;

        try {
            in = new FileInputStream("xanadu.txt");
            out = new FileOutputStream("outagain.txt");
            int c;

            while ((c = in.read()) != -1) {
                out.write(c);
            }
        } finally {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
        }
    }
}

CopyBytes 在一个简单的循环中花费大部分时间,逐字节读取输入流并写入输出流,如下图所示。

Java 中文地名翻译成英文_源文件_07

简单的字节流输入和输出。

总是关闭流

当不再需要流时关闭流非常重要—非常重要,以至于CopyBytes 使用finally块来确保即使发生错误,两个流也将被关闭。这种做法有助于避免严重的资源泄漏。

一个可能的错误是CopyBytes 无法打开一个或两个文件。当发生这种情况时,对应于文件的流变量从未从其初始的null值更改。这就是为什么CopyBytes 确保每个流变量在调用close之前包含一个对象引用。

何时不使用字节流

CopyBytes 看起来像一个普通程序,但实际上代表了一种应该避免的低级 I/O。由于xanadu.txt包含字符数据,最好的方法是使用字符流,如下一节所讨论的。还有用于更复杂数据类型的流。字节流应该仅用于最基本的 I/O。

那么为什么要谈论字节流呢?因为所有其他流类型都是建立在字节流之上的。

字符流

原文:docs.oracle.com/javase/tutorial/essential/io/charstreams.html

Java 平台使用 Unicode 约定存储字符值。字符流 I/O 会自动将内部格式与本地字符集进行转换。在西方区域,本地字符集通常是 ASCII 的 8 位超集。

对于大多数应用程序,使用字符流进行 I/O 与使用字节流进行 I/O 并无太大区别。使用流类进行的输入和输出会自动转换为本地字符集。使用字符流而不是字节流的程序会自动适应本地字符集,并且为国际化做好准备,而无需程序员额外努力。

如果国际化不是首要任务,您可以简单地使用字符流类,而不必过多关注字符集问题。稍后,如果国际化成为首要任务,您的程序可以在不进行大量重编码的情况下进行调整。查看国际化教程以获取更多信息。

使用字符流

所有字符流类都是从ReaderWriter继承而来。与字节流一样,有专门用于文件 I/O 的字符流类:FileReaderFileWriterCopyCharacters示例演示了这些类。

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CopyCharacters {
    public static void main(String[] args) throws IOException {

        FileReader inputStream = null;
        FileWriter outputStream = null;

        try {
            inputStream = new FileReader("xanadu.txt");
            outputStream = new FileWriter("characteroutput.txt");

            int c;
            while ((c = inputStream.read()) != -1) {
                outputStream.write(c);
            }
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
        }
    }
}

CopyCharactersCopyBytes非常相似。最重要的区别在于,CopyCharacters使用FileReaderFileWriter进行输入和输出,而不是FileInputStreamFileOutputStream。请注意,CopyBytesCopyCharacters都使用一个int变量进行读取和写入。但是,在CopyCharacters中,int变量在其最后 16 位中保存字符值;而在CopyBytes中,int变量在其最后 8 位中保存byte值。

使用字节流的字符流

字符流通常是字节流的“包装器”。字符流使用字节流执行物理 I/O,而字符流处理字符和字节之间的转换。例如,FileReader使用FileInputStream,而FileWriter使用FileOutputStream

有两个通用的字节到字符的“桥梁”流:InputStreamReaderOutputStreamWriter。当没有符合您需求的预打包字符流类时,请使用它们来创建字符流。网络教程中的套接字课程展示了如何从套接字类提供的字节流创建字符流。

面向行的 I/O

字符 I/O 通常以比单个字符更大的单位进行。一个常见的单位是行:一串带有行终止符的字符。行终止符可以是回车/换行序列("\r\n"),单个回车("\r")或单个换行("\n")。支持所有可能的行终止符允许程序读取在任何广泛使用的操作系统上创建的文本文件。

让我们修改CopyCharacters示例以使用面向行的 I/O。为此,我们必须使用两个以前未见过的类,BufferedReaderPrintWriter。我们将在缓冲 I/O 和格式化中更深入地探讨这些类。现在,我们只关注它们对面向行的 I/O 的支持。

CopyLines示例调用BufferedReader.readLinePrintWriter.println来逐行进行输入和输出。

import java.io.FileReader;
import java.io.FileWriter;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.IOException;

public class CopyLines {
    public static void main(String[] args) throws IOException {

        BufferedReader inputStream = null;
        PrintWriter outputStream = null;

        try {
            inputStream = new BufferedReader(new FileReader("xanadu.txt"));
            outputStream = new PrintWriter(new FileWriter("characteroutput.txt"));

            String l;
            while ((l = inputStream.readLine()) != null) {
                outputStream.println(l);
            }
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
        }
    }
}

调用readLine返回带有行的文本。CopyLines使用println输出每一行,该方法会附加当前操作系统的行终止符。这可能与输入文件中使用的行终止符不同。

除了字符和行之外,还有许多结构化文本输入和输出的方式。更多信息,请参见扫描和格式化。