点击蓝字
关注我们
下面我们来探讨一下Java开发人员技术面试中可能出现的问题,关于异常的问题。
1. Java中的异常是什么?
异常是指在程序执行过程中发生的事件,它破坏了程序指令的正常流程。
2. Java中的异常处理是如何工作的
下面的步骤演示了Java中异常处理的工作原理:
Step 1: 当一个方法内部发生错误时,该方法会创建一个对象并交给运行时系统这个对象称为异常对象。异常对象包含了有关错误的信息,包括错误的类型和错误发生时程序的状态。创建一个异常对象并将其交给运行时系统称为抛出异常。
Step 2: 方法抛出异常后,运行时系统会试图找到一些东西来处理它。处理异常的可能的 "东西 "集是为了到达发生错误的方法而被调用的方法的有序列表。这个方法列表被称为调用栈。下图显示了三个方法调用的调用栈,其中第一个调用的方法有异常处理程序。
Step 3: 运行时系统在调用栈中搜索一个方法,该方法包含一个可以处理异常的代码块。这个代码块被称为异常处理程序。搜索从发生错误的方法开始,按调用方法的相反顺序在调用栈中进行。当找到合适的处理程序后,运行时系统将异常传递给处理程序。
如果抛出的异常对象的类型与处理程序可以处理的类型相匹配,则认为异常处理程序是合适的。
Step 4: 选择的异常处理程序被称为捕获异常。如果运行时系统穷尽了调用栈上的所有方法而没有找到合适的异常处理程序,如下图所示,则运行时系统(进而程序)终止。
3. Java中的异常处理关键词是什么?
Java的异常处理是通过五个关键字来管理的-----
1. try: 在try块中包含可能引发异常的代码。如果在try块中发生异常,则由与之相关联的异常处理程序处理该异常。try块至少包含一个catch块或final块。
2. catch - Java catch块用于处理Exception。它必须仅在 try 块之后使用。一个try可以使用多个catch块。
3. throw: 有时我们明确要创建一个异常对象,然后抛出它来停止程序的正常处理,throw关键字用来抛出一个异常给运行时处理。
4. throws: 当我们在方法中抛出任何一个检查过的异常而不进行处理时,那么我们需要在方法签名中使用throws关键字来让调用者程序知道该方法可能抛出的异常。调用者方法可能会处理这些异常,或者使用throws关键字将其传播给它的调用者方法。我们可以在throws子句中提供多个异常,它也可以和*main()*方法一起使用。
5. finally: 最后块是可选的,只能与try-catch块一起使用。由于异常停止了执行过程,我们可能有一些资源开放,但不会被关闭,所以我们可以使用 finally 块。Finally 块总是被执行,无论异常是否发生。
这张图总结了这些关键字的用法。
4. throw和throws关键字的作用是什么?
throws关键字用于指定一个方法在执行过程中可能引发异常。当调用一个方法时,它强制执行显式异常处理:
public void simpleMethod() throws Exception {
// ...
}
throw关键字允许我们抛出一个异常对象来中断程序的正常流程。当程序不能满足给定条件时,这是最常用的:
if (task.isTooComplicated()) {
throw new TooComplicatedException("The task is too complicated");
}
5. 如何处理异常?
通过使用try-catch-finally语句:
try {
// ...
} catch (ExceptionType1 ex) {
// ...
} catch (ExceptionType2 ex) {
// ...
} finally {
// ...
}
可能发生异常的代码块被封装在try块中。这个块也称为 "保护 "或 "守卫 "代码。
如果发生异常,则执行与所抛出的异常相匹配的捕获块,如果没有,则忽略所有捕获块。
最后块总是在try块退出后执行,不管里面是否有异常被抛出。
6. 解释Java异常层次结构?
继承 Throwable 类的对象包括直接子代(直接继承 Throwable 类的对象)和间接子代(继承 Throwable 类的子代或孙代的对象)。
Throwable 有两个子类:
Error Class
Exception Class
下图展示了Throwable 类及其最重要的子类的类层次结构。
Error Class: 当Java虚拟机发生动态链接故障或其他硬故障时,虚拟机会抛出一个Error。例如--VirtualMachineError、OutOfMemoryError、UnKnownError、StackOverflowError等。
Exception Class: 大多数程序抛出和捕获的对象都是从Exception类派生出来的。Exception表示发生了问题,但不是严重的系统问题。例如FileNotFoundException。我们应该捕获这个异常,并向用户提供有用的信息,并正确地记录下来,以便调试。Exception类是所有Checked Exceptions的父类。
RuntimeException Class: 一个Exception子类,RuntimeException,是为表示不正确使用API的异常保留的。运行时异常的一个例子是NullPointerException,它发生在一个方法试图通过空引用访问一个对象的成员时。
7. 如何捕捉多个异常?
在一个代码块中有三种处理多个异常的方法。
第一种是使用一个能够处理所有异常类型的 catch 块。
try {
// ...
} catch (Exception ex) {
// ...
}
你应该记住,推荐的做法是使用尽可能准确的异常处理程序。
过于宽泛的异常处理程序会使你的代码更容易出错,捕捉到没有预料到的异常,并在你的程序中造成意外行为。
第二种方法是实现多个捕获块:
try {
// ...
} catch (FileNotFoundException ex) {
// ...
} catch (EOFException ex) {
// ...
}
请注意,如果异常有继承关系,子类型必须在前,父类型在后。如果我们没有做到这一点,就会导致编译错误。
第三种是使用多抓取块:
try {
// ...
} catch (FileNotFoundException | EOFException ex) {
// ...
}
这个功能,最早是在Java 7中引入的;减少了代码的重复,使其更容易维护。
8. Java中Checked Exception和Unchecked Exception的区别是什么?
\1. Checked Exceptions应该在代码中使用try-catch块来处理,否则方法应该使用throws关键字来让调用者知道可能从方法中抛出的Checked Exceptions。未检查的异常不需要在程序中处理,也不需要在方法的throws子句中提及。
\2. Exception类是所有检查异常的超类,而RuntimeException是所有未检查异常的超类。注意RuntimeException是Exception的子类。
\3. Checked exceptions是需要在代码中处理的错误情况,否则会得到编译时的错误。例如,如果你使用FileReader读取文件,它会抛出FileNotFoundException,我们必须在try-catch block中捕获它,或者再次抛给调用者方法。未被选中的异常大多是由于编程不当造成的,例如,在没有确保对象引用不是空的情况下调用对象引用上的方法时,就会出现NullPointerException。例如,我可以写一个方法来删除字符串中的所有元音。调用者有责任确保不传递一个空字符串。我可能会改变方法来处理这些情况,但理想情况下,调用者应该处理好这个问题。
\4. 检查异常和未检查异常也分别称为编译时异常和运行时异常。
9. Java中throw和throws关键字的区别是什么?
throws关键字用于与方法签名一起声明该方法可能抛出的异常,而throw关键字则用于破坏程序的流程,并将异常对象交给运行时处理。
10. 异常和错误之间的区别是什么?
异常是一个事件,它代表的是一种有可能恢复的情况,而错误代表的是一种通常不可能恢复的外部情况。
JVM抛出的所有错误都是Error或它的一个子类的实例,比较常见的错误包括但不限于。
OutOfMemoryError – 当JVM因为内存不足而无法分配更多的对象,垃圾回收器无法提供更多的可用对象时抛出的错误
StackOverflowError – 当线程的堆栈空间耗尽时发生,通常是因为应用程序递归太深。
ExceptionInInitializerError – 在评估静态初始化器时发生意外异常的信号。
NoClassDefFoundError – 当classloader试图加载一个类的定义,但找不到它时抛出,通常是因为在classpath中没有找到所需的类文件。
UnsupportedClassVersionError – 当JVM试图读取一个类文件并确定文件中的版本不受支持时发生,通常是因为文件是用较新版本的Java生成的。
虽然可以用try语句来处理错误,但这并不是一个推荐的做法,因为不能保证程序在抛出错误后能够可靠地做任何事情。
11. Java中的OutOfMemoryError是什么?
Java中的OutOfMemoryError是java.lang.VirtualMachineError的一个子类,它是由JVM在堆内存用完时抛出的。
下图说明了Error类的类层次结构。
我们可以通过java选项提供更多的内存来运行java应用程序来解决这个错误。
$>java MyProgram -Xms1024m -Xmx1024m -XX:PermSize=64M -XX:MaxPermSize=256m
12. 什么是Java中的链式异常?
链式异常功能允许您将另一个异常与一个异常关联起来。这第二个异常描述了第一个异常的原因。
例如,想象一下这样一种情况:一个方法因为试图除以零而抛出一个算术异常。然而,问题的实际原因是发生了一个I/O错误,导致除数设置不当。虽然该方法肯定要抛出一个算术异常,但由于发生的就是这个错误,你可能还想让调用代码知道根本原因是一个I/O错误。链式异常可以让你处理这种情况,以及任何其他存在异常层的情况。这个概念是在JDK 1.4中引入的。
13. 如何在Java中编写自定义异常?
在大型应用程序中,大多数情况下,我们需要自定义异常来表示业务异常,这些业务异常的级别高于JDK定义的技术异常。
下面是创建一个自定义异常的步骤:
创建一个新的类,其名称应该以Exception结尾,比如ClassNameException。这是将异常类与普通类区分开来的惯例。
让这个类扩展一个异常,它是java.lang.Exception类的子类型。一般来说,一个自定义的异常类总是直接从Exception类中扩展出来的。
创建一个构造函数,其参数为String,该参数是异常的详细消息。在这个构造函数中,只需调用超级构造函数并传递消息即可。在Java中,有两种类型的异常--检查异常和非检查异常。
下面是一个简单的自定义异常的例子:
public class ResourceNotFoundException extends Exception {
private static final long serialVersionUID = 1L;
public ResourceNotFoundException(Object resourId) {
super(resourId != null ? resourId.toString() : null);
}
}
14. Java中final、final和finalize的区别是什么?
1. final: 用于对类、方法和变量进行限制。final类不能被继承,final方法不能被重写,final变量值不能被改变。 2. finally: 关键字与try-catch块一起使用,提供即使出现某些异常也会被执行的语句,通常finally用于关闭资源。
3. finalize: 用于在对象被垃圾回收之前进行清理处理。
15. 当异常被main方法抛出时会发生什么?
当main()方法抛出异常时,Java Runtime会终止程序,并在系统控制台中打印异常消息和堆栈跟踪。
16. 什么是try-with-resources语句?
In Java, the try-with-resources statement is a try statement that declares one or more resources. The resource is as an object that must be closed after finishing the program. The try-with-resources statement ensures that each resource is closed at the end of the statement execution.
For example:
public class BufferedReaderExample {
public static void main(String[] args) {
try (FileReader fr = new FileReader("C:/workspace/java-io-guide/sample.txt"); BufferedReader br = new BufferedReader(fr);) {
String sCurrentLine;
while ((sCurrentLine = br.readLine()) != null) {
System.out.println(sCurrentLine);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
17. 什么是stacktrace,它与异常有什么关系?
堆栈跟踪提供了从应用程序开始到异常发生时被调用的类和方法的名称。
这是一个非常有用的调试工具,因为它使我们能够准确地确定应用程序中抛出异常的位置以及导致异常的原始原因。
18. Java异常的优势是什么?
以下是在程序中使用异常的优势。
优点1:将错误处理代码与 "常规 "代码分开。
优势2:将错误传播到调用栈上
优势3:对错误类型进行分组和区分。
19. 你能在lambda表达式的主体内抛出任何异常吗?
当使用Java已经提供的标准功能接口时,你只能抛出未选中的异常,因为标准功能接口在方法签名中没有 "throws "子句:
List integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(i -> {if (i == 0) {throw new IllegalArgumentException("Zero not allowed");
}
System.out.println(Math.PI / i);
});
但是,如果你使用的是自定义功能接口,则可以抛出检查异常:
@FunctionalInterface
public static interface CheckedFunction<T> {
void apply(T t) throws Exception;
}
public void processTasks(
List taks, CheckedFunction checkedFunction) {
for (Task task : taks) {
try {
checkedFunction.apply(task);
} catch (Exception e) {
// ...
}
}
}
processTasks(taskList, t -> {
// ...
throw new Exception("Something happened");
});
20. 当覆盖一个抛出异常的方法时,我们需要遵循哪些规则?
有几条规则决定了在继承的情况下必须如何声明异常。
当父类方法不抛出任何异常时,子类方法不能抛出任何检查异常,但可以抛出任何未检查的异常。
这里有一个例子代码来演示这个问题:
class Parent {
void doSomething() {
// ...
}
}
class Child extends Parent {
void doSomething() throws IllegalArgumentException {
// ...
}
}
下一个例子将无法编译,因为覆盖方法会抛出一个没有在覆盖方法中声明的检查异常:
class Parent {
void doSomething() {
// ...
}
}
class Child extends Parent {
void doSomething() throws IOException {
// Compilation error
}
}
当父类方法抛出一个或多个检查异常时,子类方法可以抛出任何未检查的异常;可以抛出所有、没有或声明的检查异常的子集,甚至可以抛出更多的异常,只要它们的范围相同或更窄。
下面是一个成功遵循前面规则的示例代码:
class Parent {
void doSomething() throws IOException, ParseException {
// ...
}
void doSomethingElse() throws IOException {
// ...
}
}
class Child extends Parent {
void doSomething() throws IOException {
// ...
}
void doSomethingElse() throws FileNotFoundException, EOFException {
// ...
}
}
请注意,这两个方法都尊重规则。第一个方法抛出的异常比被覆盖的方法少,第二个方法尽管抛出的异常更多;但它们的范围更窄。
但是,如果我们试图抛出一个父类方法没有声明的检查异常,或者我们抛出一个范围更广的异常;我们会得到一个编译错误:
class Parent {
void doSomething() throws FileNotFoundException {
// ...
}
}
class Child extends Parent {
void doSomething() throws IOException {
// Compilation error
}
}
当父类方法的throws子句中有一个未选中的异常时,子类方法可以不抛出任何异常或任意数量的未选中的异常,即使它们没有关系。
下面是一个尊重该规则的例子:
class Parent {
void doSomething() throws IllegalArgumentException {
// ...
}
}
class Child extends Parent {
void doSomething()throws ArithmeticException, BufferOverflowException {
// ...
}
}
21. Java异常处理最佳实践
在Finally Block中清理资源或使用 [Try-With-Resource Statement]
抛出特定异常
不要捕捉Exception类,而是捕捉特定的子类。
永远catch一个Throwable类
始终正确地将异常包在自定义异常中,这样就不会丢失堆栈跟踪。
先抓住最具体的例外
不要忽略异常情况,而是要记录异常情况。
永远不要从最后的阻塞中抛出任何异常
不要使用printStackTrace()语句或类似方法。
如果你不打算处理异常,请使用 finally 块而不是 catch 块。
验证用户的输入,以便在请求处理过程中尽早发现不良情况。
抛出带有描述性消息的异常。
扫码关注我们
![ab2010f7ad8996f484f4d366eb53fb3f.png](https://img-blog.csdnimg.cn/img_convert/ab2010f7ad8996f484f4d366eb53fb3f.png)
![4d6a20a329388172e87109d6a4be13d5.png](https://img-blog.csdnimg.cn/img_convert/4d6a20a329388172e87109d6a4be13d5.png)
如果你觉得这篇文章帮助到了你,可以帮忙分享给身边正在学习的朋友