Java提供了一种强大且面向对象的处理异常情况的方法,称为Java异常处理。之前我写了一篇长篇文章介绍Java中的异常处理,今天我列出了一些重要的Java异常问题及答案,以帮助你应对面试。
- 什么是Java中的异常?
- Java中的异常处理关键字有哪些?
- 解释一下Java异常层次结构?
- Java异常类的重要方法有哪些?
- 解释Java 7的ARM特性和多重捕获块?
- Java中检查异常和非检查异常的区别是什么?
- throw和throws关键字在Java中的区别是什么?
- 如何在Java中编写自定义异常?
- Java中的OutOfMemoryError是什么?
- 导致“Exception in thread main”出现的不同场景有哪些?
- final、finally和finalize在Java中有什么区别?
- 当主方法抛出异常时会发生什么?
- 我们可以有一个空的捕获块吗?
- 提供一些Java异常处理的最佳实践?
- 下面的程序有什么问题,我们该如何解决?
1. Java中的异常是什么?
异常是程序执行过程中可能发生的错误事件,会中断其正常流程。异常可能来自于不同的情况,比如用户输入错误的数据、硬件故障、网络连接失败等。每当执行Java语句时发生错误,就会创建一个异常对象,然后JRE会尝试查找异常处理程序来处理异常。如果找到合适的异常处理程序,那么异常对象就会传递给处理程序的代码来处理异常,这称为捕获异常。如果找不到处理程序,那么应用程序会将异常抛给运行时环境,JRE将终止程序。Java异常处理框架用于处理运行时错误,编译时错误不由异常处理框架处理。
2. Java中的异常处理关键字是什么?
在Java异常处理中有四个关键字。
- throw:有时我们明确地想要创建一个异常对象,然后将其抛出以停止程序的正常处理。使用throw关键字将异常抛给运行时来处理。
- throws:当我们在方法中抛出任何已检查的异常而没有处理它时,我们需要在方法签名中使用throws关键字,让调用者程序知道该方法可能抛出的异常。调用方法可能会处理这些异常或使用
throws
关键字将它们传播给其调用者方法。我们可以在throws子句中提供多个异常,并且它也可以与main()方法一起使用。 - try-catch:我们在代码中使用try-catch块进行异常处理。try是块的开始,catch位于try块的末尾以处理异常。我们可以在try块中有多个catch块,并且try-catch块也可以嵌套。catch块需要一个参数,其类型应为Exception。
- finally: finally块是可选的,只能与try-catch块一起使用。由于异常会中止执行过程,我们可能会有一些资源打开而没有关闭,因此可以使用finally块。finally块始终会执行,无论是否发生异常。
3. 解释Java异常层次结构?
Java异常是分层的,继承用于对不同类型的异常进行分类。 Throwable
是Java异常层次结构的父类,它有两个子对象 – Error
和Exception
。异常进一步分为已检查异常和运行时异常。 错误是应用程序范围之外的异常情况,不可能预见并从中恢复,例如硬件故障、JVM崩溃或内存不足错误。 已检查的异常是我们可以在程序中预见并尝试从中恢复的异常情况,例如FileNotFoundException。我们应该捕获此异常并为用户提供有用的消息,并为调试目的正确记录。 Exception
是所有已检查异常的父类。 运行时异常是由糟糕的编程引起的,例如尝试从数组中检索元素。我们应该先检查数组的长度,然后再尝试检索元素,否则它可能在运行时抛出ArrayIndexOutOfBoundException
。 RuntimeException
是所有运行时异常的父类。
4. Java 异常类的重要方法有哪些?
异常类及其所有子类没有提供任何特定的方法,所有方法都在基类 Throwable 中定义。
- String getMessage() – 此方法返回 Throwable 的消息字符串,消息可以在创建异常时通过其构造函数提供。
- String getLocalizedMessage() – 此方法被提供,以便子类可以重写它以向调用程序提供特定于区域设置的消息。Throwable 类实现此方法只需使用
getMessage()
方法返回异常消息。 - synchronized Throwable getCause() – 此方法返回异常的原因,如果原因未知,则返回 null。
- String toString() – 此方法以字符串格式返回关于 Throwable 的信息,返回的字符串包含 Throwable 类的名称和本地化消息。
- void printStackTrace() – 此方法将堆栈跟踪信息打印到标准错误流,此方法有多个重载形式,我们可以将 PrintStream 或 PrintWriter 作为参数传递以将堆栈跟踪信息写入文件或流中。
解释 Java 7 的 ARM 特性和多异常捕获块?
如果在单个 try 块中捕获了许多异常,您会注意到 catch 块的代码看起来非常丑陋,大部分都是冗余的代码用于记录错误,考虑到这一点,Java 7 的一个特性是多异常捕获块,我们可以在单个 catch 块中捕获多个异常。具有此功能的 catch 块如下所示:
catch(IOException | SQLException | Exception ex){
logger.error(ex);
throw new MyException(ex.getMessage());
}
大多数情况下,我们使用 finally 块仅用于关闭资源,有时我们会忘记关闭它们,并在资源耗尽时获得运行时异常。这些异常很难调试,我们可能需要查看我们使用该类型资源的每个地方,以确保我们关闭了它。因此,Java 7 的一个改进是 try-with-resources,我们可以在 try 语句中创建资源并在 try-catch 块中使用它。当执行离开 try-catch 块时,运行时环境会自动关闭这些资源。具有此改进的 try-catch 块示例如下:
try (MyResource mr = new MyResource()) {
System.out.println("MyResource created in try-with-resources");
} catch (Exception e) {
e.printStackTrace();
}
在 Java 7 ARM 中阅读更多相关信息。
6. Java中Checked和Unchecked异常有什么区别?
- Checked异常应该在代码中使用try-catch块处理,否则方法应该使用throws关键字来让调用者知道方法可能抛出的checked异常。Unchecked异常在程序中不需要处理,也不需要在方法的throws子句中提及它们。
Exception
是所有checked异常的超类,而RuntimeException
是所有unchecked异常的超类。注意,RuntimeException是Exception的子类。- Checked异常是需要在代码中处理的错误场景,否则会得到编译时错误。例如,如果使用FileReader来读取文件,它会抛出
FileNotFoundException
,我们必须在try-catch块中捕获它,或者再次将其抛出给调用方法。Unchecked异常主要由糟糕的编程引起,例如,在调用对象引用上的方法时出现NullPointerException,而没有确保它不是null。例如,我可以编写一个方法来从字符串中删除所有元音字母。调用者的责任是确保不传递null字符串。我可能会更改方法以处理这些情况,但理想情况下,调用者应该负责处理这些情况。
7. Java中throw和throws关键字有什么区别?
throws关键字用于方法签名中声明方法可能抛出的异常,而throw关键字用于中断程序流程并将异常对象交给运行时处理。
8. 如何在Java中编写自定义异常?
我们可以扩展Exception
类或其子类来创建自定义异常类。自定义异常类可以拥有自己的变量和方法,我们可以使用它们将错误代码或其他与异常相关的信息传递给异常处理程序。下面是一个简单的自定义异常示例。
package com.journaldev.exceptions;
import java.io.IOException;
public class MyException extends IOException {
private static final long serialVersionUID = 4664456874499611218L;
private String errorCode="Unknown_Exception";
public MyException(String message, String errorCode){
super(message);
this.errorCode=errorCode;
}
public String getErrorCode(){
return this.errorCode;
}
}
9. Java中的OutOfMemoryError是什么?
OutOfMemoryError在Java中是java.lang.VirtualMachineError的子类,当JVM耗尽堆内存时抛出。我们可以通过Java选项为Java应用程序提供更多内存来解决此错误。 $>java MyProgram -Xms1024m -Xmx1024m -XX:PermSize=64M -XX:MaxPermSize=256m
10. 导致“Exception in thread main”不同情景是什么?
一些常见的主线程异常情景包括:
- Exception in thread main java.lang.UnsupportedClassVersionError: 当你的Java类是从另一个JDK版本编译而来,而你尝试从另一个Java版本运行它时,会出现此异常。
- Exception in thread main java.lang.NoClassDefFoundError: 这个异常有两种变体。第一种是当你提供了带有.class扩展名的类的完整名称时。第二种情况是当类找不到时。
- Exception in thread main java.lang.NoSuchMethodError: main: 当你尝试运行一个没有main方法的类时,会出现此异常。
- 主线程中的异常java.lang.ArithmeticException:无论何时从主方法抛出异常,都会在控制台中打印异常。第一部分解释了异常是从主方法中抛出的,第二部分打印了异常类名,然后在冒号后打印异常消息。
在Java主线程异常中了解更多信息。
11. Java中final、finally和finalize有什么区别?
final和finally是Java中的关键字,而finalize是一个方法。final关键字可用于类变量,以防止重新分配,用于类以避免被类扩展,并用于方法以避免被子类覆盖,finally关键字与try-catch块一起使用,以提供始终执行的语句,即使出现异常也是如此,通常finally用于关闭资源。finalize()方法在对象被销毁之前由垃圾收集器执行,这是确保所有全局资源关闭的好方法。在这三者中,只有finally与Java异常处理相关。
12. 当主方法抛出异常时会发生什么?
当主方法抛出异常时,Java运行时会终止程序并在系统控制台中打印异常消息和堆栈跟踪。
13. 我们可以有一个空的catch块吗?
我们可以有一个空的catch块,但这是一个糟糕的编程示例。我们永远不应该有一个空的catch块,因为如果异常被该块捕获,我们将无法获得有关异常的信息,这将是一个噩梦般的调试过程。至少应该有一个日志记录语句将异常详细信息记录在控制台或日志文件中。
14. 提供一些Java异常处理最佳实践?
与Java异常处理相关的一些最佳实践包括:
- 使用特定的异常以便于调试。
- 尽早抛出异常(快速失败)在程序中。
- 在程序中晚些时候捕获异常,让调用者处理异常。
- 使用Java 7的ARM特性确保资源关闭,或者使用finally块正确关闭它们。
- 始终记录异常消息以进行调试。
- 使用多重捕获块以获得更清晰的关闭。
- 使用自定义异常从应用程序API中抛出单一类型的异常。
- 遵循命名约定,异常名称总是以Exception结尾。
- 使用JavaDoc中的@throws记录方法抛出的异常。
- 异常很昂贵,所以只有在有意义的时候才抛出它。否则,您可以捕获它们并提供空或空响应。
详细了解有关它们的信息在Java异常处理最佳实践。
15. 以下程序的问题是什么,我们该如何解决?
在本节中,我们将研究一些与Java异常相关的编程问题。
-
下面的程序有什么问题?
package com.journaldev.exceptions; import java.io.FileNotFoundException; import java.io.IOException; public class TestException { public static void main(String[] args) { try { testExceptions(); } catch (FileNotFoundException | IOException e) { e.printStackTrace(); } } public static void testExceptions() throws IOException, FileNotFoundException{ } }
上面的程序无法编译,您将收到错误消息:“FileNotFoundException异常已被IOException替代”。这是因为FileNotFoundException是IOException的子类,解决此问题有两种方法。第一种方法是为这两个异常使用单个catch块。
try { testExceptions(); }catch(FileNotFoundException e){ e.printStackTrace(); }catch (IOException e) { e.printStackTrace(); }
另一种方法是从多重捕获块中移除FileNotFoundException。
try { testExceptions(); }catch (IOException e) { e.printStackTrace(); }
您可以根据您的catch块代码选择任何一种方法。
-
以下程序有什么问题?
package com.journaldev.exceptions; import java.io.FileNotFoundException; import java.io.IOException; import javax.xml.bind.JAXBException; public class TestException1 { public static void main(String[] args) { try { go(); } catch (IOException e) { e.printStackTrace(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (JAXBException e) { e.printStackTrace(); } } public static void go() throws IOException, JAXBException, FileNotFoundException{ } }
该程序无法编译,因为FileNotFoundException是IOException的子类,所以FileNotFoundException的catch块是不可达的,您将收到一个错误消息:“无法访问catch块FileNotFoundException。它已由IOException的catch块处理”。您需要修正catch块顺序以解决此问题。
try { go(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (JAXBException e) { e.printStackTrace(); }
请注意,JAXBException与IOException或FileNotFoundException无关,可以放在上述catch块层次结构的任何位置。
-
下面的程序有什么问题?
package com.journaldev.exceptions; import java.io.IOException; import javax.xml.bind.JAXBException; public class TestException2 { public static void main(String[] args) { try { foo(); } catch (IOException e) { e.printStackTrace(); }catch(JAXBException e){ e.printStackTrace(); }catch(NullPointerException e){ e.printStackTrace(); }catch(Exception e){ e.printStackTrace(); } } public static void foo() throws IOException{ } }
程序无法编译,因为JAXBException是一个已检查的异常,foo()方法应该抛出此异常以在调用方法中捕获。你将会收到一个错误消息:“无法到达JAXBException的catch块。此异常从try语句体中永远不会抛出”。要解决这个问题,你需要移除JAXBException的catch块。注意,捕获NullPointerException是有效的,因为它是一个未检查的异常。
-
以下程序有什么问题?
package com.journaldev.exceptions; public class TestException3 { public static void main(String[] args) { try{ bar(); }catch(NullPointerException e){ e.printStackTrace(); }catch(Exception e){ e.printStackTrace(); } foo(); } public static void bar(){ } public static void foo() throws NullPointerException{ } }
这是一个棘手的问题,代码没有问题,它将成功编译。我们始终可以捕获异常或任何未检查的异常,即使它不在方法的throws子句中。同样,如果一个方法(foo)在throws子句中声明了一个未检查的异常,不必在程序中处理它。
-
下面的程序有什么问题?
package com.journaldev.exceptions; import java.io.IOException; public class TestException4 { public void start() throws IOException{ } public void foo() throws NullPointerException{ } } class TestException5 extends TestException4{ public void start() throws Exception{ } public void foo() throws RuntimeException{ } }
上面的程序无法编译,因为子类中的 start() 方法签名与父类不同。要解决这个问题,我们可以将子类中的方法签名修改为与父类完全相同,或者可以像下面这样从子类方法中移除 throws 子句。
@Override public void start(){ }
-
以下程序有什么问题?
package com.journaldev.exceptions; import java.io.IOException; import javax.xml.bind.JAXBException; public class TestException6 { public static void main(String[] args) { try { foo(); } catch (IOException | JAXBException e) { e = new Exception(""); e.printStackTrace(); }catch(Exception e){ e = new Exception(""); e.printStackTrace(); } } public static void foo() throws IOException, JAXBException{ } }
上述程序无法编译,因为多重捕获块中的异常对象是final的,我们无法更改其值。您将获得编译时错误,显示为“无法分配多重捕获块的参数e”。我们必须删除对新异常对象“e”的赋值才能解决此错误。更多信息请查看Java 7 多重捕获块。
这就是关于Java异常的面试问题,希望您喜欢。将来我会添加更多内容到列表中,请确保将其收藏以便日后使用。
Source:
https://www.digitalocean.com/community/tutorials/java-exception-interview-questions-and-answers