Java 异常

1. 什么是异常

“Exception”是“exceptional event的简写。异常是在程序执行过程中发生的一个事件,它破坏了程序指令的正常流程。

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

在方法抛出异常后,运行时系统会尝试找到一些方式来处理它。处理异常的一组可能的”事物“是为了达到发生错误的方法而被调用的方法的有序列表。方法列表称为调用堆栈(保存方法的堆栈信息)。

在这里插入图片描述

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

选择的异常处理程序被称为捕获异常。如果运行时系统在没有找到合适的异常处理程序时会彻底搜索调用堆栈上的所有方法,如下图所示,运行时系统将终止。

在这里插入图片描述

2. 异常类层次结构

在这里插入图片描述

在 Java 异常体系内有一个共同的基类 java.lang.Throwable 类。Throwable 类有两种重要的子类 java.lang.Error 和 java.lang.Exception。

Java 体系异常类型大致划分为三种:

  • 受检查异常
  • Error
  • 运行时异常(RuntimeException)

2.2 受检查异常

除了 java.lang.Error 和 java.lang.RuntimeException 及其子类之外,其它的 java.lang.Exception 及其子类都属于受检查异常。

受检查异常在编译过程中必须要有合适的 catch 或 throw 进行处理,否则无法通过编译。

常见受检查异常

类名描述
ClassNotFoundExceptionClass反射或类加载器为找到类时抛出的异常
CloneNotSupportedException调用Object中clone方法,但对象没有实现Cloneable接口时抛出的异常
IllegalAccessException应用程序尝试通过反射创建实例(数组除外)、设置或获取字段或调用方法,但当前执行的方法无权访问指定类、字段的定义时,抛出的异常
InstantiationException当应用程序尝试使用Class中的newInstance方法创建类的实例,但无法实例化指定的类对象是抛出的异常
InterruptedException当线程正在 waiting、sleeping 或以其它方式被占用,并且线程在活动执勤啊或期间被中断时抛出的异常
NoSuchFieldException类没有指定名称的字段时抛出的异常
NoSuchMethodException当找不到特定方法时抛出的异常
ReflectiveOperationException核心反射中反射操作抛出的异常的常见超类
IOException发生某种 I/O 异常,由失败或中断的 I/O 操作产生的异常的通用异常类。
FileNotFoundException当具有指定路径名的文件不存在时,FileInputStream、FileOutputStream、RandomAccessFile构造函数将抛出此异常。如果文件确实存在但由于某种原因无法访问,也会抛出此异常
ConnectException尝试将 socket 连接到远程地址和端口时发生错误的信号。通常,连接被远程拒绝时抛出此异常
NoRouteToHostException尝试将 socket 连接到远程地址和端口时发生错误的信号。通常,由于防火墙的接入或中间路由器关闭,无法访问远程主机。
PortUnreachableException表示已在连接的数据报上接收到 ICMP 端口不可达时抛出此异常
ProtocolException抛出表示底层协议存在错误,例如 TCP 错误
SocketException抛出以指示创建或访问 Socket 时出错时抛出的异常
SocketTimeoutException表示 socket 读取或接受时发生超时抛出的异常
UnknownHostException抛出表示无法确定主机的 IP 地址时抛出的异常
URISyntaxException抛出的检查异常指示无法将字符串解析为 URI 连接

2.3 Error

Error 是应用程序外部的特殊情况。并且应用程序通常无法预测或从中恢复。

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

举例:

假设一个应用程序成功打开一个文件进行读取,但由于硬件或系统故障而无法读取该文件。不成功的读取将抛出 Java.io.IOException。应用程序可能会选择捕获此异常,以便将问题通知给用户 – -- 但程序打印堆栈跟踪并退出也可能很有意义。

2.4 运行时异常(RuntimeException)

运行时异常是应用程序内部的异常情况,并且应用程序通常无法预测或从中恢复。这些通常表示编程错误,例如逻辑错误或API使用不当。应用程序可以捕获此异常,但消除导致异常发生的错误可能更有意义。

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

Error 和 RuntimeException 统称为未检查异常。

2.5 未检查异常

Error 和 RuntimeException 统称为未检查异常。

常见未检查异常:

OutOfMemoryError当 Java 虚拟机由于内存不足而无法分配对象时抛出,垃圾收集器无法提供更多内存
StackOverflowError由于应用程序递归太深发生堆栈溢出时抛出的异常
UnknownError当 Java 虚拟机中发生未知但严重的异常时抛出的异常
IndexOutOfBoundsException抛出以指示某种索引(例如数组、字符串或 vector)超出范围。应用程序可以子类化此类以指示类似的异常。
ArrayIndexOutOfBoundsException抛出以指示已使用非法索引访问数组。索引为负数或大于或等于数组的大小。
ClassCastException抛出以指示代码已尝试将对象强制转换为它不是其实例的子类
NullPointerException空指针异常(NPE)也是最常见的异常
ConcurrentModificationException当不允许此类修改时,检测到对象的并发修改的方法可能会抛出此异常

3. try-catch-finally 捕获处理异常

3.1 try

构建异常处理程序的第一步是将可能引发异常的代码包含在 try 块中。通常如下所示:

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

try 块用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。

3.2 catch

用于处理 try 捕获到的异常。通过在 try 块之后直接提供一个或多个 catch 块,可以将异常处理程序与 try 块相关联。catch 块如下所示:

try {
	// ...
} catch (ExceptionType name) {
	// ...
} catch (ExceptionTypew name) {
	// ...
}

每个 catch 块都是一个异常处理程序,用于处理由 try 块捕获的异常类型。ExceptionType 声明可以处理的异常类型,并且必须是从 Throwable 类的子类。

catch 块中的异常处理程序可以做的不仅仅是打印错误消息或停止退出程序。它们还可以进行错误恢复,提示用户做出自己的决定,或使用链式异常将错误传播到更高级别的处理程序。

单个 catch 可以处理多种类型的异常,多个异常之间使用竖线 " | " 进行分隔,此功能可以减少代码重复并减少捕获过于广泛的异常。如下示例所示:

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

注意:如果一个 catch 块处理一种以上的异常类型,那么 catch 参数是隐式 final 的。在此示例中,catch 参数 ex 是 final 的,因此你不能在 catch 块中为其分配任何值。

3.3 finally

finally 块总是在 try 块退出时执行。这确保即使发生意外异常也能执行 finally 块。但 finally 不仅仅用于异常处理 – -- 它允许程序员避免通过 return、continue、break 意外绕过清理资源代码。将清理资源代码放在 finally 块中始终是一个好习惯,即使没有预料到异常也是如此。

无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。

**注意:**以下 3 中情况下,finally 块不会被执行:

  1. 在 try 或 finally 第一行中使用 System.exit(int) 退出程序。但是,如果 System.exit(int) 在异常语句之后,finally 还是会别执行;
  2. 程序相关线程突然中止;
  3. 断电或其它情况导致的该 Java 进程或线程突然中止;

注意: 当 try 语句和 finally 语句中都有 return 语句时,在方法返回之前,finally 语句的内容将被执行,并且 finally 语句的返回值将覆盖try块中的返回值返回。如下所示:

public Object test() {
	try {
    return "Try 块"
  } finally {
    return "覆盖 try 块中的返回值,并返回 finally 的返回值"
  }
}

3.4 try-catch-finally 一起使用

Try-catch-finally 一起使用,如下所示:

try {
	// code 
} catch (ExceptionTypeA e) {
	// ...
} catch (ExceptionTypeB e) {
	// ..
} finally {
	// close resources ...
}

注意:

try 块用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。

4. try-with-resources

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

示例 1:

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

示例 2:

public static void writeToFileZipFileContents(String zipFileName, String outputFileName) throws IOException {
	Charset charset = StandardCharsets.US_ASCII;
	Path outputFilePath = Paths.get(outputFileName);s
	
	try (
		ZipFile zf = new ZipFile(zipFileName);
		BufferedWriter writer = Files.newBufferedWriter(outputFilePath, charset);
	) {
		for (Enumeration entries = zf.entries(); entries.hasMoreElements();) {
			String newLine = System.getProperty("line.separator");
			String zipEntryName = ((ZipEntry) entries.nextElement()).getName() + newLine;
			writer.write(zipEntryName, 0, zipEntryName.length());
		} catch (Exception e) {
			// ...
		} finally {
			// ...
		}
	}
}

在此示例中,try-with-resources 语句中包含两个分号分隔的资源声明:ZipFile 和 BufferedWriter。当代码块执行完成时,无论是正常情况还是由于异常,BufferedWriter 和 ZipFile 对象的 close() 方法将按资源声明顺序的相反顺序调用。

4.1 异常抑制

当对外部资源进行处理时,如果 try 块发生异常,且在自动关闭外部资源时发生了异常,则在 catch 中捕获的异常是 try 块中发生的异常,自动关闭资源文件的异常将被抑制(并非丢弃),可以通过从 try 块抛出的异常中调用 Throwable.getSuppressed 方法来检索这些被抑制的异常。

4.2 AutoCloseable 和 Closeable 接口

4.2.1 java.lang.AutoCloseable

一个可能持有资源(例如文件或套接字句柄)直到关闭的对象。close() 方法在退出 try-with-resources 块时自动调用,该块已在资源规范标头中声明了该对象。这种构造确保及时释放资源占用,避免资源耗尽异常和其它可能发生的错误。

4.2.2 java.io.Closeable

Closeable 是可以关闭的数据的源或目标。调用 close 方法来释放对象所持有的资源(例如打开的文件).

4.2.3 AutoCloseable 和 Closeable 的 close 方法对比

java.io.Closeable#close() 方法关闭流并释放与值关联的任何系统资源。如果流已经关闭,则调用此方法没有效果,也就是说这个方法是幂等的。

java.lang.AutoCloseable#close() 多次调用这个方法可能会产生一些明显的副作用,也就是说这个方法不是幂等的。

5. 抛出异常

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

示例 1:

public Object pop() {
	Object obj;
	if(size == 0) {
		throw new EmptyStackException();
	}
	obj = objectAt(size - 1);
	setObjectAt(size - 1, null);
	size --;
	return obj;
}

pop 方法检查堆栈上是否有元素。如果堆栈为空,则 pop 实例化一个 EmptyStackException 对象并将其抛出。

请注意:pop 方法的声明不包含 throws 子句。EmptyStackException 不是受检查异常,因此不需要 pop 声明。

示例 2:

private void readObject(java.io.ObjectInputStream s)
    throws java.io.InvalidObjectException {
    throw new java.io.InvalidObjectException("Proxy required");
}

此示例直接使用 throw 关键字抛出一个 InvalidObjectException 异常。

5.1 throws 关键字

当一个方法存在一个受检查异常,但本身不处理此异常时,那么 Java 的编译器会要求你必须在方法签名上使用 throws 关键字声明这些可能抛出的异常,否则编译不通过。

throws 是另一种处理异常的方式,它不同于 try-catch-finally,try-with-resources,throws 关键字仅仅是将方法中受检查异常抛给调用者,自己不对异常进行处理,调用者可以选择捕获处理异常或使用throws继续向上抛出更上层调用者。

采用这种异常处理的原因可能是:方法本身不知道如何处理的异常,或者说异常处理逻辑放在调用者更合适。

5.2 throw 关键字

throw 语句用于显示的手动的抛出一个异常,throw 语句后必须跟一个异常对象(Throwable 子类),例如:

throw new java.io.InvalidObjectException("Proxy required");

6. 异常链

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

7. 异常的优势

  1. 将 Error-Handling 代码与常规代码分开
  2. 在调用堆栈中向上传播错误
  3. 对错误类型进行分组和区分

7.1 优势 1:将 Error-Handling 代码与常规代码分开

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

readFile {
	open the file;
	datermine its size;
	allocate that much memory;
	read the file into memory;
	calose 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;
    }
}

请注意:异常不会让你省去检测、报告和处理错误的工作,但他们确实可以帮助你更有效地组织工作。

7.2 优势 2:在调用堆栈中向上传播错误

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

method1 {
    call method2;
}

method2 {
    call method3;
}

method3 {
    call readFile;
}

还假设 method1 是唯一对 readFile 可能发生的错误感兴趣的方法。传统的错误通知技术强制method2和method3将readFile返回的错误代码向上传播到调用堆栈,直到错误代码最终到达 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 子句中指定。

7.3 对错误类型进行分组和区分

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

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

catch (FileNotFoundException) {	
	// ...
}

通过 catch 语句中指定的一组异常的超类,方法可以根据组或一般类型捕获的异常。例如,要捕获所有 I/O 异常,不管它们的特定类型如何,异常处理程序指定一个 IOException 参数。

catch (IOException e) {
	// ...
}

你甚至可以设置一个 Exception,通过此处的处理程序来处理任何异常。

catch (Exception e) {
	// ...
}

Exception 类更靠近 Throwable 类层次结构的顶部。因此,除了处理程序要捕获的异常之外,此处理程序还将捕获许多其他异常。例如,如果你希望程序执行的所有操操作为用户打印一条错误消息然后退出,则你可能希望以这种方式处理。

但是,大多数情况下,你希望异常处理程序尽可能具体。原因是处理程序必须做的第一件事是确定发生了什么类型的异常,然后才能决定最佳恢复策略。实际上,通过不捕获特定错误,处理程序必须适应任何的可能性。过于通用的异常处理程序可以通过捕获和处理程序员为预料到且处理程序不打算处理的异常使代码更容易出错。

如前所述,你可以创建异常组并以通用方式处理异常,或者你可以使用特定异常类型来区分异常并以精确方式处理异常。

8. 自定义异常

8.1 为什么要创建自定义异常

当面临选择要抛出的异常类型时,你可以使用其他人编写的异常类,也可以编写自己的异常类。如果你对以下任何问题的回答是肯定的,你应该编写自定义异常类;否则,你可能可以使用别人的异常类。

  • 你是否需要 Java 平台中没有的异常类?
  • 如果用户能够将你的异常与其它供应商(二方、三方库)编写的类抛出的异常区分开来,这对用户有帮助吗?
  • 你的代码是否抛出多个相关异常?
  • 如果你使用其他人提供的异常,用户是否可以访问这些异常?一个类似的问题是,你的包应该是独立和和自包含的吗?

一个示例:

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

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

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

下图说明了链表抛出的异常的一个可能的类层次结构。

在这里插入图片描述

8.2 选择超类

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

  • 所有异常都必须是 Throwable 的子类。
  • 如果希望写一个检查性异常类,则需要继承 Exception 类。
  • 如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。

8.3 举例

8.3.1 自定义受检查异常举例

自定义一个受检查异常类 CustomCheckedException,继承自 Exception,如下代码所示:

public class CustomCheckedException extends Exception {

    public CustomCheckedException() {
    }

    public CustomCheckedException(String message) {
        super(message);
    }
}

使用自定义受检查异常类:

public class ExceptionTest {

    public void testCustomException() throws CustomCheckedException {
        throw new CustomCheckedException("手动抛出自定义异常信息");
    }

    public static void main(String[] args) {
        try {
            new ExceptionTest().testCustomException();
        } catch (CustomCheckedException e) {
            System.out.println("这里针对发生异常时做出的处理");
            e.printStackTrace();
        }
    }
}

因为 CustomCheckedException 是受检查异常,所以手动抛出其异常时,方法签名上需要 throws CustomCheckedException 的声明。

ExceptionTest 输入如下:

这里针对发生异常时做出的处理
com.practice.exception.CustomException: 手动抛出自定义异常信息
	at com.practice.exception.ExceptionTest.testCustomException(ExceptionTest.java:6)
	at com.practice.exception.ExceptionTest.main(ExceptionTest.java:11)

8.3.2 自定义运行时异常举例

自定义一个运行时异常 CustomRuntimeException,继承自 RuntimeException,如下代码示例所示:

public class CustomRuntimeException extends RuntimeException {

    public CustomRuntimeException() {
    }

    public CustomRuntimeException(String message) {
        super(message);
    }
}

使用自定义运行时异常类:

public class ExceptionTest {

    public void testCustomRuntimeException() {
        throw new CustomRuntimeException("手动抛出自定义运行时异常信息");
    }

    public static void main(String[] args) {
        new ExceptionTest().testCustomRuntimeException();
    }
}

因为 CustomRuntimeException 是运行时异常,所以方法签名上不需要使用 throws 进行抛出异常的声明。

ExceptionTest 输出如下:

Exception in thread "main" com.practice.exception.CustomRuntimeException: 手动抛出自定义运行时异常信息
	at com.practice.exception.ExceptionTest.testCustomRuntimeException(ExceptionTest.java:10)
	at com.practice.exception.ExceptionTest.main(ExceptionTest.java:14)

9. 总结

程序可以使用异常来指示发生错误。要抛出异常,请使用 throw 语句并提供一个异常对象(Throwable 的子类)来提供有关发生的特定的错误信息。抛出未捕获的异常的方法必须声明 throws 后再抛出。

9.1 try

  • try 块标识可能发生异常的代码块
  • try 块后至少跟一个 catch 或 finally 块,并且可以具有多个 catch 块

9.2 finally

  • finally 块标识一个确保执行的代码块,并且在 try 块中包含的代码之后关闭文件、恢复资源和以其它方式进行清理的正确位置。
  • finally:无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。

**注意:**以下 3 中情况下,finally 块不会被执行:

  1. 在 try 或 finally 第一行中使用 System.exit(int) 退出程序。但是,如果 System.exit(int) 在异常语句之后,finally 还是会别执行;
  2. 程序相关线程突然中止;
  3. 断电或其它情况导致的该 Java 进程或线程突然中止;

9.3 try-with-resources

  1. 任何实现 java.lang.AutoCloseable 或 java.io.Closeable 的对象都适用于 try-with-resources 语法糖。

  2. 关闭资源和finally块的执行顺序:在 try-with-resources 语句中,任何catch或finally块在声明的资源关闭后运行。

  3. 建议使用 try-with-resources 代替 try-catch-finally,面对必须关闭的资源,我们总是应该优先使用 try-with-resources 而不是 try-catch-finally。随之产生的代码更简短、更清晰,产生的异常对我们也更有用。try-with-resources 语句让我们更容易编写必须要关闭的资源的代码,若采用 try-catch-finally 则几乎做不到这点。

9.4 优势

  1. 将 Error-Handling 代码与常规代码分开
  2. 在调用堆栈中向上传播错误
  3. 对错误类型进行分组和区分

10. 常见面试题

10.1 以下代码是否合法?

try {
	// ...
} finally {
	// ...
}

答:合法,try 块后至少跟一个 catch 或 finally 块

10.2 以下处理程序可以捕获什么异常类型?

catch (Exception e) {
	// ...
}

答:

几乎所有类型(除 Error 外)。但是这可能是一个糟糕的实现,因为你丢失了有关正在抛出的异常的具体类型的价值信息。

10.3 以下代码是否有问题?编译会报错吗?

try {
	// ...
} catch (Exception e) {
	// ...
} catch (ArithmeticException e) {
  // ...
}

答:

编译不会报错,但是存在僵尸代码,第二个捕获的异常 ArithmeticException 永远不会执行,这是一个代码的坏味道。如果只是这样一段空代码的话,class 编译后,这段代码会被编译器忽略。

10.4 看代码,给出正确的控制台输出结果

    public static int test() {
        int i = 1;
        try {
            i++;
            System.out.println("try block, i = " + i);
        } catch (Exception e) {
            i--;
            System.out.println("catch block i = " + i);
        } finally {
            i = 10;
            System.out.println("finally block i = " + i);
        }
        return i;
    }

    public static void main(String[] args) {
        System.out.println(test());
    }

输出结果如下:

try block, i = 2
finally block i = 10
10

10.5 看代码,给出正确的控制台输出结果

    public static int test() {
        int i = 1;
        try {
            i++;
            throw new Exception();
        } catch (Exception e) {
            i--;
            System.out.println("catch block i = " + i);
        } finally {
            i = 10;
            System.out.println("finally block i = " + i);
        }
        return i;
    }

    public static void main(String[] args) {
        System.out.println(test());
    }

输出结果如下:

catch block i = 1
finally block i = 10
10

10.6 看代码,给出正确的控制台输出结果

    public static int test3() {
        int i = 1;
        try {
            i++;
            System.out.println("try block i = " + i);
            return i;
        } catch (Exception e) {
            i++;
            System.out.println("catch block i = " + i);
            return i;
        } finally {
            i = 10;
            System.out.println("finally block i = " + i);
        }
    }

    public static void main(String[] args) {
        System.out.println(test3());
    }

输出结果如下:

try block i = 2
finally block i = 10
2

10.7 看代码,给出正确的控制台输出结果

    public static int test4() {
        int i = 1;
        try {
            i++;
            System.out.println("try block i = " + i);
            return i;
        } catch (Exception e) {
            i++;
            System.out.println("catch block i = " + i);
            return i;
        } finally {
            i ++;
            System.out.println("finally block i = " + i);
            return i;
        }
    }
    public static void main(String[] args) {
        System.out.println(test3());
    }

输入结果如下:

try block i = 2
finally block i = 3
3
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值