Java 异常

Java的异常 运行时异常是指在程序运行过程中可能会出现的异常,这些异常通常是由程序逻辑错误引起的,而不是外部环境或用户输入造成的。这些异常通常是由外部环境或用户输入造成的,程序员需要在代码中显式地处理这些异常,以确保程序的稳定性和可靠性。在这个示例中,我们展示了一个可能抛出文件未找到异常和IO异常的情况,并使用try-catch块来捕获并处理这些异常。一般在service层处理受检异常,通过创建一个自定义的异常类,在需要的地方抛出自定义异常即可。可以直接在service层的最外层进行抛出异常或者直接捕获异常并处理异常。 阅读详情

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
【异常】JAVA常见异常 受检异常需要在编译时处理,而非受检异常通常是在运行时抛出,并且不强制要求在编译时处理。这些异常在编译时必须要处理,通常需要使用 try-catch 块或在方法签名中声明。当应用程序试图通过字符串名称加载类时,如果在类路径中找不到对应的类,则抛出此异常。当方法在不适当的时刻被调用时抛出。当试图将字符串转换为数值类型时,如果字符串的格式不正确,则抛出此异常。通常在输入输出操作失败或中断时抛出,比如文件操作、网络通信等。这些异常在编译时不强制要求处理,通常在运行时抛出。当试图将对象强制转换为不兼容的类型时抛出。 阅读详情

相关推荐

Java 异常处理 -- Java 语言的异常、异常链与断言

世界上存在永远不会出错的程序吗?也许这只会出现在程序员的梦中。随着编程语言和软件的诞生,异常情况就如影随形地纠缠着我们,只有正确处理好意外情况,才能保证程序的可靠性。

栗筝i的博客 5211

java try的用法_Java中try、catch的使用方法

Java中有两种处理异常的方式,分别是用throws抛出异常、用try、catch捕获异常。try-catch在Javatry-catch语句的语法格式:try{//代码块}catch(Exception1 e){//抛出异常后所要进行的操作}当try语句当中捕获到异常时,会将异常抛出到catch语句中匹配异常类型,当异常和catch后面的异常类型相匹配时,则catch语句将会执行。举个最基础的例...

weixin_28989055的博客 1万+

java中try catch的用法

java中try catch的用法 java try catch的用法 java中 try catch的用法

Java try catch语句详解

在实际应用中,对于错误的处理是极其重要的,任何程序都很难做到百分百完美,程序中可能存在大量未知问题,所以程序开发时一定要对各种问题进行相应的处理,而Java提供的异常处理机制可以帮用户更好地解决这方面的问题。Java 的异常处理机制可以让程序具有极好的容错性,让程序更加健壮。

Itmastergo的博客 1万+

驱动兼容_FANUC 驱动器兼容问题探讨

近10年来,FANUC伺服驱动器随着系统的升级而升级,不同的系统,可以兼容很多种类的驱动器,作者做了一些常见经验总结如下表:电源模块伺服放大器主轴放大器bi一体18i/OIB/OIC6110-Hxxx6114-Hxxx6111-Hxxx6134-Hxxx31iA/OID6140-Hxxx6117-Hxxx6141-Hxxx6164-Hxxx31IB/35IB/OIF/OI MATE D(5包)62...

weixin_34420941的博客 1751

动态掩码语言模型(MLM)优化策略:效率提升与最新技术实践

本文深入探讨了动态掩码语言模型(MLM)的优化策略与技术实践,涵盖核心原理、效率优化及2025年最新突破。重点解析了动态掩码在提升模型性能中的关键作用,包括工程实现、掩码策略演进及混合专家动态掩码等创新技术,为自然语言处理领域的研究与应用提供实用指南。

vscode5coder的博客 220

java中的try_java中try是什么意思

try是Java中的关键字,主要用于异常处理机制,那么它有什么作用呢?try – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。它一般与catch..finally组合使用块,用来引导第一部分的关键字,用于声明需要捕获指定语句块的异常。完整的使用方法是:try {语句块1} catch ( extends Throwable>...

weixin_34662154的博客 5557

别再只调电压了!深入理解数控电源的CC/CV模式与双闭环PID控制(以BUCK电路为例)

本文深入解析数控电源的CC/CV模式与双闭环PID控制技术,以BUCK电路为例,探讨如何通过双闭环控制优化负载突变响应和模式切换稳定性。文章详细介绍了电压环与电流环的协同工作原理、PID参数整定技巧及工程实践中的非线性补偿方法,帮助工程师提升电源设计的智能控制能力。

weixin_27017211的博客 263

java中的异常-异常处理(try、catch、finally、throw、throws)+自定义异常

(2)但我们的程序有时候想要在不满足某种业务条件时以抛出异常的形式来做处理,此时就需要我们自定义一个与业务相关的异常类来表示,例如分数不合法异常(ScoreException)1、java程序员在编写程序时提前编写好对异常的处理程序,在程序发生异常时就可以执行预先设定好的处理程序,处理程序执行完之后,可以继续向后执行后面的程序。(4)通常,这类异常的发生不是由程序员编写的代码引起的,例如:FileNotFoundException(文件找不到异常)

Watermelon_Mr的博客 3615

GPT Researcher 的win docker安装攻略

github网址是:https://github.com/assafelovic/gpt-researcher/blob/master/README-zh_CN.md。使用 Docker 运行 AI-Researcher 项目的。因为docker安装方法不够清晰,因此写一个使用方法。打开设置——docker engine ,增加代码。如有其他问题,可提供错误截图或日志进一步排查!

weixin_43818488的博客 1125

ESP32-C3入门教程 环境篇②——Windows开发环境准备

文章目录一、注意事项二、下载ESP-IDF工具安装器三、安装内容四、详细安装步骤报错处理 一、注意事项 请注意 ESP-IDF 和 ESP-IDF 工具的安装路径不能超过 90 个字符,安装路径过长可能会导致构建失败。 Python 或 ESP-IDF 的安装路径中一定不能包含空格或括号。 除非操作系统配置为支持 Unicode UTF-8,否则 Python 或 ESP-IDF 的安装路径中也不能包括特殊字符(非 ASCII 码字符、中文字符) 二、下载ESP-IDF工具安装器 ESP-IDF Win

5244

Java异常

Java异常的讲解

熵减玩家 987

Arduino与Proteus仿真实例-HG7881(L9110)驱动直流电机仿真

HG7881(L9110)驱动直流电机仿真 HG7881 (L9110) 双通道电机驱动器模块。 HG7881 (L9110) 是一款紧凑型电机驱动芯片,支持 2.5-12V 电压范围,800mA 连续电流。 这些芯片具有内置输出钳位二极管以保护敏感的微控制器电子设备。 它们适用于非常小的机器人项目。 每个 HG7881 (L9110) 芯片都能够使用两个数字控制输入驱动单个直流电机。 一个输入用于选择电机方向,而另一个用于控制电机速度。 速度是通过使用 PWM 脉冲宽度调制来控制的。 电机驱动器通常具有

视觉与物联智能 3050

【java】认识异常,理解异常,掌握异常

文章目录 1️⃣异常的背景 1.认识异常 2.避免异常的两种方式 2️⃣异常的基本用法 1.基本语法 2.多个 catch 块 3.使用共同父类 Exception 捕获异常 4.关于异常的错误输出 5.关于异常的调用链 6.关于 finally 代码块 7.使用 try 回收资源 8.一组关键字:throw 和 throws 9.异常处理流程总结 3️⃣异常体系结构 受查异常 4️⃣自定义异常类

Welcome to my blog. 1071

让 ABAP Screen Variant 安稳上车:SHD0 与 SHD0_MANDT 的 Transport 全攻略

摘要: SAP项目常需优化交易码界面,通过SHD0的Transaction Variant和Screen Variant可实现字段精简、默认值预设及权限控制,避免修改标准代码。但跨系统迁移时易出现变式丢失或版本兼容问题。核心概念包括: Screen Variant:针对单个屏幕的字段属性配置(跨Client); Transaction Variant:串联多屏幕变式(分跨Client和Client-specific); 版本差异:4.70/6.x后需区分SHD0(跨Client)与SHD0_MANDT(Cl

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 226

YOLOv11 改进 - SPPF模块 | 发论文神器!LSKA注意力改进SPPF,增强多尺度特征提取能力,高效涨点!!!

本文介绍了Large Separable Kernel Attention (LSKA)模块及其在YOLOv11中的结合应用。LSKA旨在解决Visual Attention Networks (VAN)使用大内核卷积时的计算效率问题,通过将2D深度卷积层的卷积核分解为级联的水平和垂直1-D卷积核,降低了计算复杂度和内存占用,且使VAN更关注物体形状。我们将集成LSKA的SPPF_LSKA模块引入YOLOv11,大量实验表明,改进后的模型在对象识别、检测、分割和稳健性测试上表现出色。

魔改工程师的博客 1279

【Java】认识异常

自定义异常:我们自己定义维护符合我们实际需要的异常结构。//登入if(!System.out.println("用户名错误!");if(!System.out.println("密码错误!");他的密码输入错误,结果输出如上。如果我们想要在我们输错时报出异常,这时,我们可以自己定义一个异常,用户名输入错误异常和密码输入错误异常;我们可以定义一个输入错误异常类和密码输入错误异常类来继承运行时异常super();super(m);super();super(m);

2301_80543957的博客 2258

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Java——异常

在我们日常生活中,我们可能会生病,会头疼,会肚子疼,打球的时候也可能会受伤。再程序中也是一样,运行时难免会出现一些奇奇怪怪的问题。在Java中,将程序执行过程中发生的不正常行为称为异常。算数异常//算数异常数组越界异常//数组越界异常空指针异常//空指针异常在Java中不同类型的异常,都有与其对应的类来进行描述。程序先执行 try 中的代码如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配.

m0_74100417的博客 1550

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

java java 检查型异常_Java异常——异常分类、声明检查型异常

一、异常的分类1、在Java程序设计语言中,异常对象都是派生于Throwable类的一个类的实例。如果Java的内置类不能满足需求,用户还可以创建自己的异常类。2、异常分为两支,Error和Exception。Error类层次描述了Java运行时系统的内部错误和资源耗尽错误。这种情况很少出现,如果出现了这种情况,就只能通知用户并妥善地终止程序。Exception类是应该重点关注的层次结构。3、Ex...

weixin_29688535的博客 3145

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

JAVA 异常

明白异常到底是什么?—>异常就是类知道异常的体系 / 知道异常的分类在编写程序时,如果程序中出现错误,此时就需要将错误的信息告知给调用者,比如:参数检测。在Java中,可以借助throw关键字,抛出一个指定的异常对象,将错误信息告知给调用者。具体语法如下:throw new XXXException(“异常产生的原因”);throw new NullPointerException("传递的数组为null");

2301_80934700的博客 1409

【Java异常】Java异常监控重要手段 --异常链

异常链”无论是在框架源码中还是在日常项目开发中,都是一项非常重要的对异常处理的手段。以下摘抄自《Java编程思想》对异常链的介绍:常常会想要在①捕获一个异常后抛出另一个异常②并且希望把原始异常的信息保存下来,这被称为异常链。略。

qq_43783527的博客 2136

java异常判断_Java异常类

异常指不期而至的各种状况,如:文件找不到、网络连接失败、非法参数等。异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程。Java通 过API中Throwable类的众多子类描述各种不同的异常。因而,Java异常都是对象,是Throwable子类的实例,描述了出现在一段编码中的 错误条件。当条件生成时,错误将引发异常。Java异常类层次结构图:在 Java 中,所有的异常都有一个共同的祖先 ...

weixin_42472941的博客 5344
上一篇: Java 泛型
下一篇: Java集合-ArrayList
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值