Java面试题,异常架构篇⭐(精心梳理⭐附带阿里巴巴开发手册+异常习题及答案)

  • Java异常面试题(精心整理),你准备好接招了吗?求怕累!
  • 内容最后附带阿里巴巴开发手册,整合不易,还请大家认真阅读,并一键三连加关注,给博主一些支持,谢谢了!笔芯 ❤

在这里插入图片描述

文章目录

在这里插入图片描述


Java异常架构与异常关键字

在这里插入图片描述

一. Java异常简介

  • Java异常是Java提供的一种识别及响应错误的一致性机制。
  • Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。在有效使用异常的情况下,异常能清晰的回答what, where, why这3个问题:异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪”抛出,异常信息回答了“为什么”会抛出。

二. Java异常架构

在这里插入图片描述

1. Throwable

  • Throwable 是 Java 语言中所有错误与异常的超类(父类)
  • Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。
  • Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。

2. Error(错误)

  • 定义:Error 类及其子类。程序中无法处理的错误,表示运行应用程序中出现了严重的错误。
  • 特点:此类错误一般表示代码运行时 JVM 出现问题。通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;StackOverflowError:栈溢出错误。此类错误发生时,JVM 将终止线程。
  • 这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误。按照Java惯例,我们是不应该实现任何新的Error子类的!

3. Exception(异常)

  • 程序本身可以捕获并且可以处理的异常。 Exception 这种异常又分为两类:运行时异常和编译时异常。

运行时异常

  • 定义: RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。
  • 特点: Java 编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既"没有通过throws声明抛出它",也"没有用try-catch语句捕获它",还是会编译通过。比如NullPointerException空指针异常、ArrayIndexOutBoundException数组下标越界异常、ClassCastException类型转换异常、ArithmeticExecption算术异常。此类异常属于不受检异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。虽然 Java 编译器不会检查运行时异常,但是我们也可以通过 throws 进行声明抛出,也可以通过 try-catch 对它进行捕获处理。如果产生运行时异常,则需要通过修改代码来进行避免。例如,若会发生除数为零的情况,则需要通过代码避免该情况的发生!
  • RuntimeException 异常会由 Java 虚拟机自动抛出并自动捕获 (就算我们没写异常捕获语句运行时也会抛出错误!!) ,此类异常的出现绝大数情况是代码本身有问题应该从逻辑上去解决并改进代码。

编译时异常

  • 定义: Exception 中除 RuntimeException 及其子类之外的异常。
  • 特点: Java 编译器会检查它。如果程序中出现此类异常,比如 ClassNotFoundException(没有找到指定的类异常),IOException(IO流异常),要么通过throws进行声明抛出,要么通过trycatch进行捕获处理,否则不能通过编译。在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。该异常我们必须手动在代码里添加捕获语句来处理该异常。

4. 受检异常与非受检异常

  • Java 的所有异常可以分为受检异常(checked exception)和非受检异常(unchecked exception)。

受检异常

  • 编译器要求必须处理的异常。正确的程序在运行过程中,经常容易出现的、符合预期的异常情况。一旦发生此类异常,就必须采用某种方式进行处理。除 RuntimeException 及其子类外,其他的Exception 异常都属于受检异常。 编译器会检查此类异常,也就是说当编译器检查到应用中的某处可能会此类异常时,将会提示你处理本异常——要么使用try-catch捕获,要么使用方法签名中用 throws 关键字抛出,否则编译不通过。

非受检异常

  • 编译器不会进行检查并且不要求必须处理的异常,也就说当程序中出现此类异常时,即使我们没有try-catch捕获它,也没有使用throws抛出该异常,编译也会正常通过。该类异常包括运行时异常(RuntimeException极其子类)和错误(Error)。

三. Java异常关键字

  • try : 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
  • catch : 用于捕获异常。catch用来捕获try语句块中发生的异常。
  • finally : finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
  • throw : 用于抛出异常。
  • throws : 用在方法签名中,用于声明该方法可能抛出的异常。

Java异常处理

在这里插入图片描述

一. 声明异常

  • 通常,应该捕获那些知道如何处理的异常,将不知道如何处理的异常继续传递下去。传递异常可以在方法签名处使用 throws 关键字声明可能会抛出的异常。
  • 注意:非检查异常(Error、RuntimeException 或它们的子类)不可使用 throws 关键字来声明要抛出的异常。 一个方法出现编译时异常,就需要 try-catch/ throws 处理,否则会导致编译错误。

二. 抛出异常

  • 如果你觉得解决不了某些异常问题,且不需要调用者处理,那么你可以抛出异常。
  • throw关键字作用是在方法内部抛出一个 Throwable 类型的异常。任何Java代码都可以通过 throw 语句抛出异常。

三. 捕获异常

  • 程序通常在运行之前不报错,但是运行后可能会出现某些未知的错误,但是还不想直接抛出到上一级,那么就需要通过try…catch…的形式进行异常捕获,之后根据不同的异常情况来进行相应的处理。

四. 如何选择异常类型

  • 可以根据下图来选择是捕获异常,声明异常还是抛出异常。

在这里插入图片描述

五. 常见异常处理方式

在这里插入图片描述

1. 直接抛出异常

  • 通常,应该捕获那些知道如何处理的异常,将不知道如何处理的异常继续传递下去。传递异常可以在方法签名处使用 throws 关键字声明可能会抛出的异常。
private static void readFile(String filePath) throws IOException {
	File file = new File(filePath);
	String result;
	BufferedReader reader = new BufferedReader(new FileReader(file));
	while((result = reader.readLine())!=null) {
		System.out.println(result);
	}
	reader.close();
}

2. 封装异常再抛出

  • 有时我们会从 catch 中抛出一个异常,目的是为了改变异常的类型。多用于在多系统集成时,当某个子系统故障,异常类型可能有多种,可以用统一的异常类型向外暴露,不需暴露太多内部异常细节。
private static void readFile(String filePath) throws MyException {
	try {
		// code
	} catch (IOException e) {
		MyException ex = new MyException("read file failed.");
		ex.initCause(e);
		throw ex;
	}
}

3. 捕获异常

  • 在一个 try-catch 语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理
private static void readFile(String filePath) {
	try {
		// code
	} catch (FileNotFoundException e) {
		// handle FileNotFoundException
	} catch (IOException e){
		// handle IOException
	}
}
  • 同一个 catch 也可以捕获多种类型异常,用 | 隔开
private static void readFile(String filePath) {
	try {
		// code
	} catch (FileNotFoundException | UnknownHostException e) {
		// handle FileNotFoundException or UnknownHostException
	} catch (IOException e){
		// handle IOException
	}
}

4. 自定义异常

  • 习惯上,定义一个异常类应包含两个构造函数,一个无参构造函数和一个带有详细描述信息的构造函数(Throwable 的 toString 方法会打印这些详细信息,调试时很有用)
public class MyException extends Exception {
	public MyException(){ 
	}
	public MyException(String msg){
		super(msg);
	}
	// ...
}

5. try-catch-finally

  • 当方法中发生异常,异常处之后的代码不会再执行,如果之前获取了一些本地资源需要释放,则需要在方法正常结束时和 catch 语句中都调用释放本地资源的代码,显得代码比较繁琐,finally 语句可以解决这个问题。
private static void readFile(String filePath) throws MyException {
	File file = new File(filePath);
	String result;
	BufferedReader reader = null;
	try {
		reader = new BufferedReader(new FileReader(file));
		while((result = reader.readLine())!=null) {
			System.out.println(result);
		}
	} catch (IOException e) {
		System.out.println("readFile method catch block.");
		MyException ex = new MyException("read file failed.");
		ex.initCause(e);
		throw ex;
	} finally {
		System.out.println("readFile method finally block.");
		if (null != reader) {
			try {
				reader.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
  • 调用该方法时,读取文件时若发生异常,代码会进入 catch 代码块,之后进入 finally 代码块;若读取文件时未发生异常,则会跳过 catch 代码块直接进入 finally 代码块。所以无论代码中是否发生异常,fianlly 中的代码都会执行。
  • 若 catch 代码块中包含 return 语句,finally 中的代码还会执行吗?将以上代码中的 catch 子句修改如下:
catch (IOException e) {
	System.out.println("readFile method catch block.");
	return;
}
  • 调用 readFile 方法,观察当 catch 子句中调用 return 语句时,finally 子句是否执行
readFile method catch block.
readFile method finally block.
  • 可见,即使 catch 中包含了 return 语句,finally 子句依然会执行。若 finally 中也包含 return 语句,finally 中的 return 会覆盖前面的 return。

6. try-with-resource

  • 上面例子中,finally 中的 close 方法也可能抛出 IOException, 从而覆盖了原始异常。JAVA 7 提供了更优雅的方式来实现资源的自动释放,自动释放的资源需要是实现了 AutoCloseable 接口的类。
private static void tryWithResourceTest(){
	try (Scanner scanner = new Scanner(new FileInputStream("c:/abc"),"UTF-8")){
		// code
	} catch (IOException e){
		// handle exception
	}
}
  • try 代码块退出时,会自动调用 scanner.close 方法,和把 scanner.close 方法放在 finally 代码块中不同的是,若 scanner.close 抛出异常,则会被抑制,抛出的仍然为原始异常。被抑制的异常会由addSusppressed 方法添加到原来的异常,如果想要获取被抑制的异常列表,可以调用getSuppressed 方法来获取。

Java异常常见面试题

在这里插入图片描述

一. Error 和 Exception 区别是什么?

  • Error 类型的错误通常为虚拟机相关错误,如系统崩溃,内存不足,堆栈溢出等,编译器不会对这类错误进行检测,JAVA 应用程序也不应对这类错误进行捕获,一旦这类错误发生,通常应用程序会被终止,仅靠应用程序本身无法恢复;
  • Exception 类的错误是可以在应用程序中进行捕获并处理的,通常遇到这种错误,应对其进行处理,使应用程序可以继续正常运行。

二. 运行时异常和一般异常(受检异常)区别是什么?

  • 运行时异常包括 RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。 Java 编译器不会检查运行时异常。
  • 受检异常是Exception 中除 RuntimeException 及其子类之外的异常。 Java 编译器会检查受检异常。
  • RuntimeException异常和受检异常之间的区别:是否强制要求调用者必须处理此异常,如果强制要求调用者必须进行处理,那么就使用受检异常,否则就选择非受检异常(RuntimeException)。一般来讲,如果没有特殊的要求,我们建议使用RuntimeException异常。

三. JVM 是如何处理异常的?

  • 在一个方法中如果发生异常,这个方法会创建一个异常对象,并转交给 JVM,该异常对象包含异常名称,异常描述以及异常发生时应用程序的状态。创建异常对象并转交给 JVM 的过程称为抛出异常。可能有一系列的方法调用,最终才进入抛出异常的方法,这一系列方法调用的有序列表叫做调用栈。

在这里插入图片描述

  • JVM 会顺着调用栈去查找看是否有可以处理异常的代码,如果有,则调用异常处理代码。当 JVM发现可以处理异常的代码时,会把发生的异常传递给它。如果 JVM 没有找到可以处理该异常的代码块,JVM 就会将该异常转交给默认的异常处理器(默认处理器为 JVM 的一部分),默认异常处理器打印出异常信息并终止应用程序。

四. throw 和 throws 的区别是什么?

  • Java 中的异常处理除了包括捕获异常和处理异常之外,还包括声明异常和拋出异常,可以通过 throws 关键字在方法上声明该方法要拋出的异常,或者在方法内部通过 throw 拋出异常对象。

throws 关键字和 throw 关键字在使用上的几点区别如下:

  • throw 关键字用在方法内部,只能用于抛出一种异常,用来抛出方法或代码块中的异常,受查异常和非受查异常都可以被抛出。
  • throws 关键字用在方法声明上,可以抛出多个异常,用来标识该方法可能抛出的异常列表。一个方法用 throws 标识了可能抛出的异常列表,调用该方法的方法中必须包含可处理异常的代码,否则也要在方法签名中用 throws 关键字声明相应的异常。

五. final、finally、finalize 有什么区别?

  • final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。
  • finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
  • finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,Java 中允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。

六. NoClassDefFoundError 和 ClassNotFoundException 区别?

  • NoClassDefFoundError 是一个 Error 类型的异常,是由 JVM 引起的,不应该尝试捕获这个异常。
  • 引起该异常的原因是 JVM 或 ClassLoader 尝试加载某类时在内存中找不到该类的定义,该动作发生在运行期间,即编译时该类存在,但是在运行时却找不到了,可能是变异后被删除了等原因导致;
  • ClassNotFoundException 是一个受查异常,需要显式地使用 try-catch 对其进行捕获和处理,或在方法签名中用 throws 关键字进行声明。当使用 Class.forName, ClassLoader.loadClass 或
    ClassLoader.findSystemClass 动态加载类到内存的时候,通过传入的类路径参数没有找到该类,就会抛出该异常;另一种抛出该异常的可能原因是某个类已经由一个类加载器加载至内存中,另一个加载器又尝试去加载它。

七. try-catch-finally 中哪个部分可以省略?

  • 答:catch 可以省略

原因

  • 更为严格的说法其实是:try只适合处理运行时异常,try+catch适合处理运行时异常+普通异常。也就是说,如果你只用try去处理普通异常却不加以catch处理,编译是通不过的,因为编译器硬性规定,普通异常如果选择捕获,则必须用catch显示声明以便进一步处理。而运行时异常在编译时没有如此规定,所以catch可以省略,你加上catch编译器也觉得无可厚非。
  • 理论上,编译器看任何代码都不顺眼,都觉得可能有潜在的问题,所以你即使对所有代码加上try,代码在运行期时也只不过是在正常运行的基础上加一层皮。但是你一旦对一段代码加上try,就等于显示地承诺编译器,对这段代码可能抛出的异常进行捕获而非向上抛出处理。如果是普通异常,编译器要求必须用catch捕获以便进一步处理;如果运行时异常,捕获然后丢弃并且+finally扫尾处理,或者加上catch捕获以便进一步处理。
  • 至于加上finally,则是在不管有没捕获异常,都要进行的“扫尾”处理。

八. try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?

  • 答: 会执行,在 return 前执行。
  • 注意: 在 finally 中改变返回值的做法是不好的,因为如果存在 finally 代码块,try中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行完毕之后再向调用者返回其值,然后如果在finally 中修改了返回值,就会返回修改后的值。显然,在 finally 中返回或者修改返回值会对程序造成很大的困扰,C#中直接用编译错误的方式来阻止程序员干这种龌龊的事情,Java中也可以通过提升编译器的语法检查级别来产生警告或错误。

代码示例1:

public static int getInt() {
	int a = 10;
	try {
		System.out.println(a / 0);
		a = 20;
	} catch (ArithmeticException e) {
		a = 30;
		return a;
	/*
	* return a 在程序执行到这一步的时候,这里不是return a 而是 return 30;这个返回路径就形成了
	* 但是呢,它发现后面还有finally,所以继续执行finally的内容,a=40
	* 再次回到以前的路径,继续走return 30,形成返回路径之后,这里的a就不是a变量了,而是常量30
	*/
	} finally {
		a = 40;
	}
	return a;
}
  • 执行结果:30

代码示例2:

public static int getInt() {
	int a = 10;
	try {
		System.out.println(a / 0);
		a = 20;
	} catch (ArithmeticException e) {
		a = 30;
		return a;
	} finally {
		a = 40;
		//如果这样,就又重新形成了一条返回路径,由于只能通过1个return返回,所以这里直接返回40
		return a;
	}
}	
  • 执行结果:40

九. 类 ExampleA 继承 Exception,类 ExampleB 继ExampleA。

  • 有如下代码片断:
try {
	throw new ExampleB("b")
} catchExampleA e){
	System.out.println("ExampleA");
} catchException e){
	System.out.println("Exception");
}
  • 请问执行此段代码的输出是什么?
  • 答: 输出:ExampleA。(根据里氏代换原则[能使用父类型的地方一定能使用子类型],抓取ExampleA 类型异常的 catch 块能够抓住 try 块中抛出的 ExampleB 类型的异常)

十. 常见的 RuntimeException 有哪些?

  • ClassCastException(类转换异常)
  • IndexOutOfBoundsException(数组越界)
  • NullPointerException(空指针)
  • ArrayStoreException(数据存储异常,操作数组时类型不一致)
  • 还有IO操作的BufferOverflowException异常

Java常见异常有哪些

  • java.lang.IllegalAccessError:违法访问错误。当一个应用试图访问、修改某个类的域(Field)或者调用其方法,但是又违反域或方法的可见性声明,则抛出该异常。
  • java.lang.InstantiationError:实例化错误。当一个应用试图通过Java的new操作符构造一个抽象类或者接口时抛出该异常.
  • java.lang.OutOfMemoryError:内存不足错误。当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误。
  • java.lang.StackOverflowError:堆栈溢出错误。当一个应用递归调用的层次太深而导致堆栈溢出或者陷入死循环时抛出该错误。
  • java.lang.ClassCastException:类造型异常。假设有类A和B(A不是B的父类或子类),O是A的实例,那么当强制将O构造为类B的实例时抛出该异常。该异常经常被称为强制类型转换异常。
  • java.lang.ClassNotFoundException:找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
  • java.lang.ArithmeticException:算术条件异常。譬如:整数除零等。
  • java.lang.ArrayIndexOutOfBoundsException:数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
  • java.lang.IndexOutOfBoundsException:索引越界异常。当访问某个序列的索引值小于0或大于等于序列大小时,抛出该异常。
  • java.lang.InstantiationException:实例化异常。当试图通过newInstance()方法创建某个类的实例,而该类是一个抽象类或接口时,抛出该异常。
  • java.lang.NoSuchFieldException:属性不存在异常。当访问某个类的不存在的属性时抛出该异常。
  • java.lang.NoSuchMethodException:方法不存在异常。当访问某个类的不存在的方法时抛出该异常。
  • java.lang.NullPointerException:空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。
  • java.lang.NumberFormatException:数字格式异常。当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常。
  • java.lang.StringIndexOutOfBoundsException:字符串索引越界异常。当使用索引值访问某个字符串中的字符,而该索引值小于0或大于等于序列大小时,抛出该异常。

Java异常处理最佳实践

在 Java 中处理异常并不是一个简单的事情。不仅仅初学者很难理解,即使一些有经验的开发者也需要花费很多时间来思考如何处理异常,包括需要处理哪些异常,怎样处理等等。这也是绝大多数开发团队都会制定一些规则来规范进行异常处理的原因。而团队之间的这些规范往往是截然不同的。

  • 本文给出几个被很多团队使用的异常处理最佳实践。

在这里插入图片描述

一. 在 finally 块中清理资源或者使用 try-with-resource 语句

  • 当使用类似InputStream这种需要使用后关闭的资源时,一个常见的错误就是在try块的最后关闭资源。
public void doNotCloseResourceInTry() {
	FileInputStream inputStream = null;
	try {
		File file = new File("./tmp.txt");
		inputStream = new FileInputStream(file);
		// use the inputStream to read a file
		// do NOT do this
		inputStream.close();
	} catch (FileNotFoundException e) {
		log.error(e);
	} catch (IOException e) {
		log.error(e);
	}
}
  • 问题就是,只有没有异常抛出的时候,这段代码才可以正常工作。try 代码块内代码会正常执行,并且资源可以正常关闭。但是,使用 try 代码块是有原因的,一般调用一个或多个可能抛出异常的方法,而且,你自己也可能会抛出一个异常,这意味着代码可能不会执行到 try 代码块的最后部分。结果就是,你并没有关闭资源。

所以,你应该把清理工作的代码放到 finally 里去,或者使用 try-with-resource 特性。

1.使用 finally 代码块

  • 与前面几行 try 代码块不同,finally 代码块总是会被执行。不管 try 代码块成功执行之后还是你在catch 代码块中处理完异常后都会执行。因此,你可以确保你清理了所有打开的资源。
public void closeResourceInFinally() {
	FileInputStream inputStream = null;
	try {
		File file = new File("./tmp.txt");
		inputStream = new FileInputStream(file);
		// use the inputStream to read a file
	} catch (FileNotFoundException e) {
		log.error(e);
	} finally {
		if (inputStream != null) {
			try {
				inputStream.close();
			} catch (IOException e) {
				log.error(e);
			}
		}
	}
}

2. Java 7 的 try-with-resource 语

  • 如果你的资源实现了 AutoCloseable 接口,你可以使用这个语法。大多数的 Java 标准资源都继承了这个接口。当你在 try 子句中打开资源,资源会在 try 代码块执行后或异常处理后自动关闭。
public void automaticallyCloseResource() {
	File file = new File("./tmp.txt");
	try (FileInputStream inputStream = new FileInputStream(file);) {
		// use the inputStream to read a file
	} catch (FileNotFoundException e) {
		log.error(e);
	} catch (IOException e) {
		log.error(e);
	}
}

二. 优先明确的异常

  • 你抛出的异常越明确越好,永远记住,你的同事或者几个月之后的你,将会调用你的方法并且处理异常。
  • 因此需要保证提供给他们尽可能多的信息。这样你的 API 更容易被理解。你的方法的调用者能够更好的处理异常并且避免额外的检查。
  • 因此,总是尝试寻找最适合你的异常事件的类,例如,抛出一个 NumberFormatException 来替换一个 IllegalArgumentException 。避免抛出一个不明确的异常。
public void doNotDoThis() throws Exception {
	...
}
public void doThis() throws NumberFormatException {
	...
}	

三. 对异常进行文档说明

  • 当在方法上声明抛出异常时,也需要进行文档说明。目的是为了给调用者提供尽可能多的信息,从而可以更好地避免或处理异常。在 Javadoc 添加 @throws 声明,并且描述抛出异常的场景。
public void doSomething(String input) throws MyBusinessException {
	...
}

四. 使用描述性消息抛出异常

  • 在抛出异常时,需要尽可能精确地描述问题和相关信息,这样无论是打印到日志中还是在监控工具中,都能够更容易被人阅读,从而可以更好地定位具体错误信息、错误的严重程度等。
  • 但这里并不是说要对错误信息长篇大论,因为本来 Exception 的类名就能够反映错误的原因,因此只需要用一到两句话描述即可。
  • 如果抛出一个特定的异常,它的类名很可能已经描述了这种错误。所以,你不需要提供很多额外的信息。一个很好的例子是 NumberFormatException 。当你以错误的格式提供 String 时,它将被java.lang.Long 类的构造函数抛出。
try {
	new Long("xyz");
} catch (NumberFormatException e) {
	log.error(e);
}

五. 优先捕获最具体的异常

  • 大多数 IDE 都可以帮助你实现这个最佳实践。当你尝试首先捕获较不具体的异常时,它们会报告无法访问的代码块。
  • 但问题在于,只有匹配异常的第一个 catch 块会被执行。 因此,如果首先捕获IllegalArgumentException ,则永远不会到达应该处理更具体的 NumberFormatException 的 catch 块,因为它是 IllegalArgumentException 的子类。
  • 总是优先捕获最具体的异常类,并将不太具体的 catch 块添加到列表的末尾。
  • 你可以在下面的代码片断中看到这样一个 try-catch 语句的例子。 第一个 catch 块处理所有NumberFormatException 异常,第二个处理所有非 NumberFormatException 异常的IllegalArgumentException 异常。
public void catchMostSpecificExceptionFirst() {
	try {
		doSomething("A message");
	} catch (NumberFormatException e) {
		log.error(e);
	} catch (IllegalArgumentException e) {
		log.error(e)
	}
}

六. 不要捕获 Throwable 类

  • Throwable 是所有异常和错误的超类。你可以在 catch 子句中使用它,但是你永远不应该这样做!
  • 如果在 catch 子句中使用 Throwable ,它不仅会捕获所有异常,也将捕获所有的错误。JVM 抛出错误,指出不应该由应用程序处理的严重问题。 典型的例子是 OutOfMemoryError 或者 StackOverflowError 。两者都是由应用程序控制之外的情况引起的,无法处理。
  • 所以,最好不要捕获 Throwable ,除非你确定自己处于一种特殊的情况下能够处理错误。
public void doNotCatchThrowable() {
	try {
		// do something
	} catch (Throwable t) {
		// don't do this!
	}
}

七. 不要忽略异常

  • 很多时候,开发者很有自信不会抛出异常,因此写了一个catch块,但是没有做任何处理或者记录日志。
public void doNotIgnoreExceptions() {
	try {
		// do something
	} catch (NumberFormatException e) {
		// this will never happen
	}
}
  • 但现实是经常会出现无法预料的异常,或者无法确定这里的代码未来是不是会改动(删除了阻止异常抛出的代码),而此时由于异常被捕获,使得无法拿到足够的错误信息来定位问题。
  • 合理的做法是至少要记录异常的信息。
public void logAnException() {
	try {
		// do something
	} catch (NumberFormatException e) {
		log.error("This should never happen: " + e);
	}
}

八. 不要记录并抛出异常

  • 这可能是本文中最常被忽略的最佳实践。可以发现很多代码甚至类库中都会有捕获异常、记录日志并再次抛出的逻辑。如下:
try {
	new Long("mzc");
} catch (NumberFormatException e) {
	log.error(e);
	throw e;
}
  • 这个处理逻辑看着是合理的。但这经常会给同一个异常输出多条日志。如下:
17:44:28,945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: "mzc"
Exception in thread "main" java.lang.NumberFormatException: For input string:
"xyz"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:589)
at java.lang.Long.(Long.java:965)
at
com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHan
dling.java:63)
at
com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)
  • 如上所示,后面的日志也没有附加更有用的信息。如果想要提供更加有用的信息,那么可以将异常包装为自定义异常。
public void wrapException(String input) throws MyBusinessException {
	try {
		// do something
	} catch (NumberFormatException e) {
		throw new MyBusinessException("A message that describes the error.", e);
	}
}
  • 因此,仅仅当想要处理异常时才去捕获,否则只需要在方法签名中声明让调用者去处理。

九. 包装异常时不要抛弃原始的异常

  • 捕获标准异常并包装为自定义异常是一个很常见的做法。这样可以添加更为具体的异常信息并能够做针对的异常处理。
  • 在你这样做时,请确保将原始异常设置为原因(注:参考下方代码 NumberFormatException e 中的原始异常 e )。Exception 类提供了特殊的构造函数方法,它接受一个 Throwable 作为参数。否则,你将会丢失堆栈跟踪和原始异常的消息,这将会使分析导致异常的异常事件变得困难。
public void wrapException(String input) throws MyBusinessException {
	try {
		// do something
	} catch (NumberFormatException e) {
		throw new MyBusinessException("A message that describes the error.", e);
	}
}

十. 不要使用异常控制程序的流程

  • 不应该使用异常控制应用的执行流程,例如,本应该使用if语句进行条件判断的情况下,你却使用异常处理,这是非常不好的习惯,会严重影响应用的性能。

十一. 使用标准异常

  • 如果使用内建的异常可以解决问题,就不要定义自己的异常。Java API 提供了上百种针对不同情况的异常类型,在开发中首先尽可能使用 Java API 提供的异常,如果标准的异常不能满足你的要求,这时候创建自己的定制异常。尽可能得使用标准异常有利于新加入的开发者看懂项目代码。

十二. 异常会影响性能

  • 异常处理的性能成本非常高,每个 Java 程序员在开发时都应牢记这句话。创建一个异常非常慢,抛出一个异常又会消耗1~5ms,当一个异常在应用的多个层级之间传递时,会拖累整个应用的性能。
  • 仅在异常情况下使用异常;
  • 在可恢复的异常情况下使用异常;
  • 尽管使用异常有利于 Java 开发,但是在应用中最好不要捕获太多的调用栈,因为在很多情况下都不需要打印调用栈就知道哪里出错了。因此,异常消息应该提供恰到好处的信息。

十三. 总结

  • 综上所述,当你抛出或捕获异常的时候,有很多不同的情况需要考虑,而且大部分事情都是为了改善代码的可读性或者 API 的可用性。
  • 异常不仅仅是一个错误控制机制,也是一个通信媒介。因此,为了和同事更好的合作,一个团队必须要制定出一个最佳实践和规则,只有这样,团队成员才能理解这些通用概念,同时在工作中使用它。

在这里插入图片描述

异常处理-阿里巴巴Java开发手册

在这里插入图片描述
在这里插入图片描述

在线版地址:《阿里巴巴开发手册终极版1.3.0》


Java异常题库

在这里插入图片描述

一. 填空题(补全___上的内容)

  1. ____机制是一种非常有用的辅助性程序设计方法。采用这种方法可以使得在程序设计时将程序的正常流程与错误处理分开,有利于代码的编写和维护。

  2. 在Java异常处理中可以使用多个catch子句,此时包含异常类的父类Exception的catch子句的位置应该是在____。

  3. 异常处理中finally块可确保无论是否发生异常,该块中代码总能被执行。finally块不执行的唯一情况是在异常处理代码中执行____语句退出Java虚拟机。

  4. 异常是由Java应用程序抛出和处理的非严重错误,比如所需文件没有找到、零作除数,数组下标越界等,可分为两类:Checked异常和_____。

  5. 在Java中对于程序可能出现的检查时异常,要么用try…catch语句捕获并处理它,要么使用___语句抛出它,由上一级调用者来处理。

  6. Java异常处理中,如果一个方法中出现了多个Checked异常,可以在方法声明中使用关键字___声明抛出,各异常类型之间使用逗号分隔。

答案

  1. 异常处理
  2. 最后
  3. System.exit()
  4. 运行时异常
  5. throws
  6. thorws

二. 选择题

在这里插入图片描述

  1. 以下关于异常的代码的执行结果是( )。(选择一项)
public class Test {
    public static void main(String args[]) {
       try {
           System.out.println("try");        
           return;
       } catch(Exception e){
           System.out.println("catch");
       }finally {
           System.out.println("finally");
       }
    }
}
  • A.try catch finally
  • B.catch finally
  • C.try finally
  • D.Try

  1. 在异常处理中,如释放资源、关闭文件等由( )来完成。(选择一项)
  • A.try子句
  • B.`catch子句
  • C.finally子句
  • D.throw子句

  1. 编译并运行如下Java程序,将输出( )。(选择一项)
public static void main(String[] args) {
    try {
            int num1 = 2;          
            int num2 = 0;
			int result = num1 / num2;
			System.out.println(result);
			throw new NumberFormatException( );
        } catch (ArrayIndexOutOfBoundsException e) {
			System.out.print("1");
		} catch (NumberFormatException e) {
			System.out.print("2");
        } catch (Exception e) {
            System.out.print("3");
        } finally {
            System.out.print("4");
        }
         	System.out.print("5");
		}
}
  • A.134
  • B.2345
  • C.1345
  • D.345

  1. 下面选项中有关Java异常处理模型的说法错误的是( )。(选择二项)
  • A.一个try块只能有一条catch语句
  • B.一个try块中可以不使用catch语句
  • C.catch块不能单独使用,必须始终与try块在一起
  • D.finally块可以单独使用,不是必须与try块在一起

  1. 下面选项中属于运行时异常的是( )。(选择二项)
  • A. Exception和SexException

  • B. NullPointerException和InputMismatchException

  • C. ArithmeticException和ArrayIndexOutOfBoundsException

  • D. ClassNotFoundException和ClassCastException


  1. 阅读如下Java代码,在控制台输入"-1",执行结果是( )。(选择一项)
 	

public class Demo {
      public static void main(String[] args) {
          Scanner input = new Scanner(System.in);
          System.out.print("请输入数字:");
          try {
             int num = input.nextInt();
			 if (num < 1 || num > 4) {
					throw new Exception("必须在1-4之间!");
			 }
		     } catch (InputMismatchException e) {
					System.out.println("InputMismatchException");
             } catch (Exception e) {
                    System.out.println(e.getMessage());
             }
          }
}
  • A. 输出:InputMismatchException
  • B. 输出:必须在1-4之间!
  • C. 什么也没输出
  • D. 编译错误

答案

  1. C
  2. C
  3. D
  4. AD
  5. BC
  6. B

三. 简答题 (答案请在文章中寻找)

  1. Error和Exception的区别

  2. Checked异常和Runtime异常的区别。

  3. Java异常处理中,关键字try、catch、finally、throw、throws分别代表什么含义?

  4. throws和throw的区别

  5. java中的两种异常类型是什么?他们有什么区别?


四. 编码题

在这里插入图片描述

  • 1.编写程序接收用户输入分数信息,如果分数在0—100之间,输出成绩。如果成绩不在该范围内,抛出异常信息,提示分数必须在0—100之间。

  • 要求:使用自定义异常实现

/**
 *
 * 分数范围异常类型
 *
 * @author Administrator
 *
 *
 *
 */
public class ScoreScopeException extends RuntimeException
{
    public ScoreScopeException()
    {
        super();
    }
    public ScoreScopeException(String message)
    {
        super(message);
    }
}




import java.util.Scanner;
public class Test
{
    public static void main(String[] args)
    {
        Scanner scanner = new Scanner(System.in);
        try
        {
            System.out.print("请输入分数:");
            int score = scanner.nextInt();
            if (score < 0 || score > 100)
            {
                throw new ScoreScopeException("分数必须在0-100之间");
            }
            System.out.println("分数为:" + score);
        } catch (ScoreScopeException e)
        {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
    }
}

  • 2.写一个方法void isTriangle(int a,int b,int c),判断三个参数是否能构成一个三角形, 如果不能则抛出异常IllegalArgumentException,显示异常信息 “a,b,c不能构成三角形”,如果可以构成则显示三角形三个边长,在主方法中得到命令行输入的三个整数, 调用此方法,并捕获异常。

public class Test
{
    public static void main(String[] args)
    {
        //输入三条边
        System.out.println("请输入三条边长");
        Scanner reader = new Scanner(System.in);
        int a = reader.nextInt();
        int b = reader.nextInt();
        int c = reader.nextInt();
        //判断是否是三角形
        isTriangle(a, b, c);
    }
    private static void isTriangle(int a, int b, int c)
    {
        //三条边都不能是负数
        if (a < 0 || b < 0 || c < 0)
        {
            throw new IllegalArgumentException("三条边不能是负数");
        }
        //判断是否构成三角形
        if (a + b > c && a + c > b && b + c > a)
        {
            System.out.println("三角形的边长分别为" + "a=" + a + " "
                    + "b=" + b + " " + "c=" + " " + c);
        } else
        {
            throw new IllegalArgumentException(a + "," + b + "," + c
                    + "不能构成三角形!");
        }
    }
}

五. 可选题

  • 1.编写一个计算N个整数平均值的程序。程序应该提示用户输入N的值,如何必须输入所有N个数。如果用户输入的值是一个负数,则应该抛出一个异常并捕获,提示“N必须是正数或者0”。并提示用户再次输入该数
public class Test
{
    public static void main(String[] args)
    {
        Scanner input = new Scanner(System.in);
        System.out.println("要计算几个整数的平均值呢:");
        int n = input.nextInt();
        // 使用sum求出用户输入的数的总和;
        int sum = 0;
        int num = 0;
        // 使用循环提醒用户输入
        for (int i = 0; i < n; i++)
        {
            System.out.println("请输入第" + (i + 1) + "个数");
            try
            {
                num = input.nextInt();
                if (num < 0)
                {
                    // i<0抛出异常
                    throw new Exception("N必须是正数或者0");
                }
                sum += num;
            } catch (Exception e)
            {
                // 使用递归,如果出现异常继续输入
                System.out.println(e.getMessage());
                i--;
            }
        }
        System.out.println("一共" + n + "个数," + "和为:" + sum
                + ",平均值为:" + sum / n);
    }
}

六. 看代码,判问题

问题 1

public class Father{
    public void test() throws IOException{
        throw new IOException("IOException");
    }
}
 
public class Son extends Father{
    public void test throws Exception{
        throw new Exception("Exception");
    }
}
  • 答:上面的代码片段在编译时子类方法会出现编译异常,因为在 java 中重写方法抛出的异常不能是原方法抛出异常的父类,这里test方法在父类中抛出了 IOException,所有在子类中的方法只能抛出IOExcepition 或是其子类。

问题 2

public int test1(){
    int temp = 0;
    try{
        return temp;
    }finally{
        temp = 2;
    }
}
 
public int test2(){
    int temp = 0;
    try{
        int a = 5/0;
        return temp;
    }finally{
        return 2;
    }
}
 
public void test3(){
    try{
        int temp = 5/0;
    }finally{
        throw new RuntimeException("RuntimeException");
    }
}
  • 答:
  1. test1方法中返回0,因为执行到 try 的 return temp; 语句前会先将返回值 temp保存在一个临时变量中,然后才执行 finally 语句,最后 try 再返回那个临时变量,finally 中对 temp 的修改不会被返回。
  2. test2 方法运行返回 2,因为 5/0 会触发 ArithmeticException 异常,但是 finally 中有 return 语句,finally 中 return 不仅会覆盖 try 和 catch 内的返回值且还会掩盖 try 和 catch 内的异常,就像异常没有发生一样(特别注意,当 finally 中没有 return 时该方法运行会抛出 ArithmeticException 异常),所以这个方法就会返回 2,而且不再向上传递异常了。
  3. test3方法会抛出RuntimeException异常,原异常会被覆盖。

  • 25
    点赞
  • 137
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 17
    评论
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虫链Java Library

谢谢您的支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值