精华内容
下载资源
问答
  • 在有效使用异常的情况下,异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪“抛出,异常信息回答了“为什么抛出,如果你的异常没有回答以上全部问题,那么可能你没有很好地使用它们。三个原则可以帮助...

    异常之所以是一种强大的调试手段,在于其回答了以下三个问题:

    1、什么出了错?

    2、在哪出的错?

    3、为什么出错?

    在有效使用异常的情况下,异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪“抛出,异常信息回答了“为什么“会抛出,如果你的异常没有回答以上全部问题,那么可能你没有很好地使用它们。

    有三个原则可以帮助你在调试过程中最大限度地使用好异常,这三个原则是:

    1、具体明确

    2、提早抛出

    3、延迟捕获

    为了阐述有效异常处理的这三个原则,本文通过杜撰个人财务管理器类JCheckbook进行讨论,JCheckbook用于记录及追踪诸如存取款,票据开具之类的银行账户活动。

    具体明确

    Java定义了一个异常类的层次结构,其以Throwable开始,扩展出Error和Exception,而Exception又扩展出RuntimeException.如图1所示.

    8895450f71fd6cdd820c20cd2b67d855.gif

    这四个类是泛化的,并不提供多少出错信息,虽然实例化这几个类是语法上合法的(如:new Throwable()),但是最好还是把它们当虚基类看,使用它们更加特化的子类。Java已经提供了大量异常子类,如需更加具体,你也可以定义自己的异常类。

    例 如:java.io package包中定义了Exception类的子类IOException,更加特化确的是 FileNotFoundException,EOFException和ObjectStreamException这些IOException的子 类。每一种都描述了一类特定的I/O错误:分别是文件丢失,异常文件结尾和错误的序列化对象流.异常越具体,我们的程序就能更好地回答”什么出了错”这个 问题。

    捕 获异常时尽量明确也很重要。例如:JCheckbook可以通过重新询问用户文件名来处理FileNotFoundException,对于 EOFException,它可以根据异常抛出前读取的信息继续运行。如果抛出的是ObjectStreamException,则程序应该提示用户文件 已损坏,应当使用备份文件或者其他文件。

    Java让明确捕获异常变得容易,因为我们可以对同一try块定义多个catch块,从而对每种异常分别进行恰当的处理。

    File prefsFile = new File(prefsFilename);

    try{

    readPreferences(prefsFile);

    }

    catch (FileNotFoundException e){

    // alert the user that the specified file

    // does not exist

    }

    catch (EOFException e){

    // alert the user that the end of the file

    // was reached

    }

    catch (ObjectStreamException e){

    // alert the user that the file is corrupted

    }

    catch (IOException e){

    // alert the user that some other I/O

    // error occurred

    }

    JCheckbook 通过使用多个catch块来给用户提供捕获到异常的明确信息。举例来说:如果捕获了FileNotFoundException,它可以提示用户指定另一 个文件,某些情况下多个catch块带来的额外编码工作量可能是非必要的负担,但在这个例子中,额外的代码的确帮助程序提供了对用户更友好的响应。

    除前三个catch块处理的异常之外,最后一个catch块在IOException抛出时给用户提供了更泛化的错误信息.这样一来,程序就可以尽可能提供具体的信息,但也有能力处理未预料到的其他异常。

    有 时开发人员会捕获范化异常,并显示异常类名称或者打印堆栈信息以求"具体"。千万别这么干!用户看到java.io.EOFException或者堆栈信息 只会头疼而不是获得帮助。应当捕获具体的异常并且用"人话"给用户提示确切的信息。不过,异常堆栈倒是可以在你的日志文件里打印。记住,异常和堆栈信息是用来帮助开发人 员而不是用户的。

    最后,应该注意到JCheckbook并没有在readPreferences()中捕获异常,而是将捕获和处理异常留到用户界面层来做,这样就能用对话框或其他方式来通知用户。这被称为"延迟捕获",下文就会谈到。

    提早抛出

    异常堆栈信息提供了导致异常出现的方法调用链的精确顺序,包括每个方法调用的类名,方法名,代码文件名甚至行数,以此来精确定位异常出现的现场。

    java.lang.NullPointerException

    at java.io.FileInputStream.open(Native Method)

    at java.io.FileInputStream.(FileInputStream.java:103)

    at jcheckbook.JCheckbook.readPreferences(JCheckbook.java:225)

    at jcheckbook.JCheckbook.startup(JCheckbook.java:116)

    at jcheckbook.JCheckbook.(JCheckbook.java:27)

    at jcheckbook.JCheckbook.main(JCheckbook.java:318)

    以 上展示了FileInputStream类的open()方法抛出NullPointerException的情况。不过注意 FileInputStream.close()是标准Java类库的一部分,很可能导致这个异常的问题原因在于我们的代码本身而不是Java API。所以问题很可能出现在前面的其中一个方法,幸好它也在堆栈信息中打印出来了。

    不幸的是,NullPointerException是Java中信息量最少的(却也是最常遭遇且让人崩溃的)异常。它压根不提我们最关心的事情:到底哪里是null。所以我们不得不回退几步去找哪里出了错。

    通过逐步回退跟踪堆栈信息并检查代码,我们可以确定错误原因是向readPreferences()传入了一个空文件名参数。既然readPreferences()知道它不能处理空文件名,所以马上检查该条件:

    public void readPreferences(String filename)

    throws IllegalArgumentException{

    if (filename == null){

    throw new IllegalArgumentException("filename is null");

    } //if

    //...perform other operations...

    InputStream in = new FileInputStream(filename);

    //...read the preferences file...

    }

    通过提早抛出异常(又称"迅速失败"),异常得以清晰又准确。堆栈信息立即反映出什么出了错(提供了非法参数值),为什么出错(文件名不能为空值),以及哪里出的错(readPreferences()的前部分)。这样我们的堆栈信息就能如实提供:

    java.lang.IllegalArgumentException: filename is null

    at jcheckbook.JCheckbook.readPreferences(JCheckbook.java:207)

    at jcheckbook.JCheckbook.startup(JCheckbook.java:116)

    at jcheckbook.JCheckbook.(JCheckbook.java:27)

    at jcheckbook.JCheckbook.main(JCheckbook.java:318)

    另外,其中包含的异常信息("文件名为空")通过明确回答什么为空这一问题使得异常提供的信息更加丰富,而这一答案是我们之前代码中抛出的NullPointerException所无法提供的。

    通过在检测到错误时立刻抛出异常来实现迅速失败,可以有效避免不必要的对象构造或资源占用,比如文件或网络连接。同样,打开这些资源所带来的清理操作也可以省却。

    延迟捕获

    菜鸟和高手都可能犯的一个错是,在程序有能力处理异常之前就捕获它。Java编译器通过要求检查出的异常必须被捕获或抛出而间接助长了这种行为。自然而然的做法就是立即将代码用try块包装起来,并使用catch捕获异常,以免编译器报错。

    问 题在于,捕获之后该拿异常怎么办?最不该做的就是什么都不做。空的catch块等于把整个异常丢进黑洞,能够说明何时何处为何出错的所有信息都会永远丢失。把异常写到日志中还稍微好点,至少还有记录可查。但我们总不能指望用户去阅读或者理解日志文件和异常信息。让readPreferences()显示错误信息对话框也不合适,因为虽然JCheckbook目前是桌面应用程序,但我们还计划将它变成基于HTML的Web应用。那样的话,显示错误对话框显然不是个选择。同时,不管HTML还是C/S版本,配置信息都是在服务器上读取的,而错误信息需要显示给Web浏览器或者客户端程序。 readPreferences()应当在设计时将这些未来需求也考虑在内。适当分离用户界面代码和程序逻辑可以提高我们代码的可重用性。

    在有条件处理异常之前过早捕获它,通常会导致更严重的错误和其他异常。例如,如果上文的readPreferences()方法在调用FileInputStream构造方法时立即捕获和记录可能抛出的FileNotFoundException,代码会变成下面这样:

    public void readPreferences(String filename){

    //...

    InputStream in = null;

    // DO NOT DO THIS!!!

    try{

    in = new FileInputStream(filename);

    }

    catch (FileNotFoundException e){

    logger.log(e);

    }

    in.read(...);

    //...

    }

    上面的代码在完全没有能力从FileNotFoundException中恢复过来的情况下就捕获了它。如果文件无法找到,下面的方法显然无法读取它。如果 readPreferences()被要求读取不存在的文件时会发生什么情况?当然,FileNotFoundException会被记录下来,如果我们 当时去看日志文件的话,就会知道。然而当程序尝试从文件中读取数据时会发生什么?既然文件不存在,变量in就是空的,一个 NullPointerException就会被抛出。

    调试程序时,本能告诉我们要看日志最后面的信息。那将会是NullPointerException,非常让人讨厌的是这个异常非常不具体。错误信息不仅误导我们什么出了错(真正的错误是FileNotFoundException而不是NullPointerException),还误导了错误的出处。真正 的问题出在抛出NullPointerException处的数行之外,这之间有可能存在好几次方法的调用和类的销毁。我们的注意力被这条小鱼从真正的错误处吸引了过来,一直到我们往回看日志才能发现问题的源头。

    既然readPreferences()真正应该做的事情不是捕获这些异常,那应该是什么?看起来有点有悖常理,通常最合适的做法其实是什么都不做,不要马上捕获异常。把责任交给 readPreferences()的调用者,让它来研究处理配置文件缺失的恰当方法,它有可能会提示用户指定其他文件,或者使用默认值,实在不行的话也 许警告用户并退出程序。

    把异常处理的责任往调用链的上游传递的办法,就是在方法的throws子句声明异常。在声明可能抛出的异常时,注意越具体越好。这用于标识出调用你方法的程序需要知晓并且准备处理的异常类型。例如,“延迟捕获”版本的readPreferences()可能是这样的:

    public void readPreferences(String filename)

    throws IllegalArgumentException,

    FileNotFoundException, IOException{

    if (filename == null){

    throw new IllegalArgumentException("filename is null");

    } //if

    //...

    InputStream in = new FileInputStream(filename);

    //...

    }

    技 术上来说,我们唯一需要声明的异常是IOException,但我们明确声明了方法可能抛出FileNotFoundException。 IllegalArgumentException不是必须声明的,因为它是非检查性异常(即RuntimeException的子类)。然而声明它是为 了文档化我们的代码(这些异常也应该在方法的JavaDocs中标注出来)。

    当 然,最终你的程序需要捕获异常,否则会意外终止。但这里的技巧是在合适的层面捕获异常,以便你的程序要么可以从异常中有意义地恢复并继续下去,而不导致更 深入的错误;要么能够为用户提供明确的信息,包括引导他们从错误中恢复过来。如果你的方法无法胜任,那么就不要处理异常,把它留到后面捕获和在恰当的层面处理。

    总结

    经验丰富的开发人员都知道,调试程序的最大难点不在于修复缺陷,而在于从海量的代码中找出缺陷的藏身之处。只要遵循本文的三个原则,就能让你的异常协助你跟踪和消灭缺陷,使你的程序更加健壮,对用户更加友好。以上就是这篇文章的全部内容,希望能对大家的学习或者工作带来一定的帮助。

    展开全文
  • Java异常架构与异常...在有效使用异常的情况下,异常能清晰的回答what, where, why这3个问题:异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪”抛出,异常信息回答了“为什么抛出。Java异常架构1...

    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进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常类。该异常我们必须手动在代码里添加捕获语句来处理该异常。

    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异常处理

    Java 通过面向对象的方法进行异常处理,一旦方法抛出异常,系统自动根据该异常对象寻找合适异常处理器(Exception Handler)来处理该异常,把各种不同的异常进行分类,并提供了良好的接口。在 Java 中,每个异常都是一个对象,它是 Throwable 类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并可以对其进行处理。Java 的异常处理是通过 5 个关键词来实现的:try、catch、throw、throws 和 finally。

    在Java应用中,异常的处理机制分为声明异常,抛出异常和捕获异常。

    声明异常

    通常,应该捕获那些知道如何处理的异常,将不知道如何处理的异常继续传递下去。传递异常可以在方法签名处使用 throws 关键字声明可能会抛出的异常。

    注意非检查异常(Error、RuntimeException 或它们的子类)不可使用 throws 关键字来声明要抛出的异常。

    一个方法出现编译时异常,就需要 try-catch/ throws 处理,否则会导致编译错误。

    抛出异常

    如果你觉得解决不了某些异常问题,且不需要调用者处理,那么你可以抛出异常。

    throw关键字作用是在方法内部抛出一个Throwable类型的异常。任何Java代码都可以通过throw语句抛出异常。

    捕获异常

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

    如何选择异常类型

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

    常见异常处理方式

    直接抛出异常

    通常,应该捕获那些知道如何处理的异常,将不知道如何处理的异常继续传递下去。传递异常可以在方法签名处使用 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();

    }

    封装异常再抛出

    有时我们会从 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;

    }

    }

    捕获异常

    在一个 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

    }

    }

    自定义异常

    习惯上,定义一个异常类应包含两个构造函数,一个无参构造函数和一个带有详细描述信息的构造函数(Throwable 的 toString 方法会打印这些详细信息,调试时很有用)

    public class MyException extends Exception {

    public MyException(){ }

    public MyException(String msg){

    super(msg);

    }

    // ...

    }

    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.

    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异常常见面试题

    1. Error 和 Exception 区别是什么?

    Error 类型的错误通常为虚拟机相关错误,如系统崩溃,内存不足,堆栈溢出等,编译器不会对这类错误进行检测,JAVA 应用程序也不应对这类错误进行捕获,一旦这类错误发生,通常应用程序会被终止,仅靠应用程序本身无法恢复;

    Exception 类的错误是可以在应用程序中进行捕获并处理的,通常遇到这种错误,应对其进行处理,使应用程序可以继续正常运行。

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

    运行时异常包括 RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。 Java 编译器不会检查运行时异常。

    受检异常是Exception 中除 RuntimeException 及其子类之外的异常。 Java 编译器会检查受检异常。

    RuntimeException异常和受检异常之间的区别:是否强制要求调用者必须处理此异常,如果强制要求调用者必须进行处理,那么就使用受检异常,否则就选择非受检异常(RuntimeException)。一般来讲,如果没有特殊的要求,我们建议使用RuntimeException异常。

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

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

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

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

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

    throws 关键字和 throw 关键字在使用上的几点区别如下:throw 关键字用在方法内部,只能用于抛出一种异常,用来抛出方法或代码块中的异常,受查异常和非受查异常都可以被抛出。

    throws 关键字用在方法声明上,可以抛出多个异常,用来标识该方法可能抛出的异常列表。一个方法用 throws 标识了可能抛出的异常列表,调用该方法的方法中必须包含可处理异常的代码,否则也要在方法签名中用 throws 关键字声明相应的异常。

    5. final、finally、finalize 有什么区别?final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。

    finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。

    finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,Java 中允许使用 finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。

    6. NoClassDefFoundError 和 ClassNotFoundException 区别?

    NoClassDefFoundError 是一个 Error 类型的异常,是由 JVM 引起的,不应该尝试捕获这个异常。

    引起该异常的原因是 JVM 或 ClassLoader 尝试加载某类时在内存中找不到该类的定义,该动作发生在运行期间,即编译时该类存在,但是在运行时却找不到了,可能是变异后被删除了等原因导致;

    ClassNotFoundException 是一个受查异常,需要显式地使用 try-catch 对其进行捕获和处理,或在方法签名中用 throws 关键字进行声明。当使用 Class.forName, ClassLoader.loadClass 或 ClassLoader.findSystemClass 动态加载类到内存的时候,通过传入的类路径参数没有找到该类,就会抛出该异常;另一种抛出该异常的可能原因是某个类已经由一个类加载器加载至内存中,另一个加载器又尝试去加载它。

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

    答:catch 可以省略

    原因

    更为严格的说法其实是:try只适合处理运行时异常,try+catch适合处理运行时异常+普通异常。也就是说,如果你只用try去处理普通异常却不加以catch处理,编译是通不过的,因为编译器硬性规定,普通异常如果选择捕获,则必须用catch显示声明以便进一步处理。而运行时异常在编译时没有如此规定,所以catch可以省略,你加上catch编译器也觉得无可厚非。

    理论上,编译器看任何代码都不顺眼,都觉得可能有潜在的问题,所以你即使对所有代码加上try,代码在运行期时也只不过是在正常运行的基础上加一层皮。但是你一旦对一段代码加上try,就等于显示地承诺编译器,对这段代码可能抛出的异常进行捕获而非向上抛出处理。如果是普通异常,编译器要求必须用catch捕获以便进一步处理;如果运行时异常,捕获然后丢弃并且+finally扫尾处理,或者加上catch捕获以便进一步处理。

    至于加上finally,则是在不管有没捕获异常,都要进行的“扫尾”处理。

    8. 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

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

    有如下代码片断:

    try {

    throw new ExampleB("b")

    } catch(ExampleA e){

    System.out.println("ExampleA");

    } catch(Exception e){

    System.out.println("Exception");

    }

    请问执行此段代码的输出是什么?

    答:

    输出:ExampleA。(根据里氏代换原则[能使用父类型的地方一定能使用子类型],抓取 ExampleA 类型异常的 catch 块能够抓住 try 块中抛出的 ExampleB 类型的异常)

    面试题 - 说出下面代码的运行结果。(此题的出处是《Java 编程思想》一书)

    class Annoyance extends Exception {

    }

    class Sneeze extends Annoyance {

    }

    class Human {

    public static void main(String[] args)

    throws Exception {

    try {

    try {

    throw new Sneeze();

    } catch ( Annoyance a ) {

    System.out.println("Caught Annoyance");

    throw a;

    }

    } catch ( Sneeze s ) {

    System.out.println("Caught Sneeze");

    return ;

    } finally {

    System.out.println("Hello World!");

    }

    }

    }

    结果

    Caught Annoyance

    Caught Sneeze

    Hello World!

    10. 常见的 RuntimeException 有哪些?ClassCastException(类转换异常)

    IndexOutOfBoundsException(数组越界)

    NullPointerException(空指针)

    ArrayStoreException(数据存储异常,操作数组时类型不一致)

    还有IO操作的BufferOverflowException异常

    11. 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时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等。

    java.lang.NumberFormatException:数字格式异常。当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常。

    java.lang.StringIndexOutOfBoundsException:字符串索引越界异常。当使用索引值访问某个字符串中的字符,而该索引值小于0或大于等于序列大小时,抛出该异常。

    请继续关注试题(二)

    觉得有用的同学可以点下赞同呀~

    关注我,获得更多技术知识~

    展开全文
  • 你也奈何不了他:) 这在C++中还导致另外一个问题,是重载函数不能只有不同的返回值,而相同的参数表,因为如果调用者不检查返回值,则编译器会不知道应该调用哪个重载函数。当然这个问题与本文无关,我们暂且放下...
  • Java提供了两种错误的异常类,分别Error和Exception,且它们拥有共同的父类Throwable Error表示在运行期间出现了很严重的错误,并且该错误是不可恢复的,由于这属于JVM层次的严重错误,因此这种错误是导致程序...

    Java提供了两种错误的异常类,分别为Error和Exception,且它们拥有共同的父类Throwable

    Error表示在运行期间出现了很严重的错误,并且该错误是不可恢复的,由于这属于JVM层次的严重错误,因此这种错误是会导致程序终止执行的。此外,编译器不会检查Error是否被处理,因此在程序中不推荐去捕获Error类型的异常,主要原因是运行时异常多是由于逻辑错误导致的,属于应该解决的问题,也就是说一个正确的程序是不应该存在Error的。OutOfMemoryError、ThreadDeath等都属于错误。当这些异常发生时,JVM会选择终止线程。

    Exception表示可恢复的异常,是编译器可以捕捉到的。它包含两种类型:检查异常运行时异常

    1. 检查异常

    检查异常是程序中最经常碰到的异常。所有继承自Exception并且不是运行时异常的异常都输入检查异常。比如最常见的IO异常和SQL异常。
    这种异常都发生在编译阶段,Java编译器强制程序去捕获此类型的异常。
    即把可能会出现这些异常的代码放到try块中,把对异常的处理的代码放到catch块中,这种异常一般会在如下几种情况中使用。

    1)异常的发生并不会导致程序出错,进行处理后可以继续执行后续的操作,例如:当数据库连接失败后,可以重新连接后进行后续操作
    2)程序依赖于不可靠的外部条件,例如系统IO

    2. 运行时异常
    运行时异常不同于检查异常,编译器没有强制对其进行捕获处理。如果不对这种异常进行处理,当出现这种异常时,会由JVM来处理,例如NullPointerException异常,它就是运行时异常,在Java语言中最常见的异常包括空指针异常、类型转换异常、数组越界异常等

    出现运行时异常后,系统会把异常一直往上层抛出,直到遇到处理代码为止。若没有处理块则抛到最上层;如果是多线程就用Thread.run()方法抛出,如果是单线程,就用main()方法抛出。抛出之后,如果是线程,那么这个线程也就退出了。如果是主程序抛出异常,那么整个程序也就退出了。

    所以,如果不对运行时的异常进行处理,后果是非常严重的,一旦发生,要么线程终止,要么主程序终止。

    在使用异常处理时还需要注意以下几个问题:

    • Java异常处理用到了多态的概念、如果在异常处理中,先捕获了基类,然后再捕获子类,那么捕获子类的代码块将永远不会被执行。因此在进行异常捕获时正确的写法是:先捕获子类,在捕获基类的异常信息。
    try{
    	//可能会发生异常
    }catch(SQLException e1){
    
    }catch(Exception e2){
    }
    
    • 尽早的抛出异常,同时对捕获的异常进行处理,或者从错误中恢复,让程序继续执行,对捕获的异常不进行任何处理是一个非常不好的一贯,这样将不利于调试。但也不是抛出异常越多越好。
    • 可以根据实际的需求自定义异常类。这些自定义的异常类只需要继承自Exception类即可。
    • 异常能处理就处理,不能处理就抛出。对于一般异常,如果不能进行有效的处理,最好转换为运行时异常抛出。对于最终没有处理的异常,JVM会进行处理。
    展开全文
  • 你也奈何不了他:) 这在C++中还导致另外一个问题,就是重载函数不能只有不同的返回值,而相同的参数表,因为如果调用者不检查返回值,则编译器会不知道应该调用哪个重载函数。当然这个问题与本文无关,我们暂且放下...

    常用的错误处理方式

      返回值:我们常用函数的返回值来标志成功或者失败,甚至是失败的原因。但是这种做法最大的问题是如果调用者不主动检查返回值也是可以被编译器接受的,你也奈何不了他:) 这在C++中还导致另外一个问题,就是重载函数不能只有不同的返回值,而有相同的参数表,因为如果调用者不检查返回值,则编译器会不知道应该调用哪个重载函数。当然这个问题与本文无关,我们暂且放下。只要谨记返回值可能被忽略的情况即可。

      全局状态标志。例如系统调用使用的errno。返回值不同的是,全局状态标志可以让函数的接口(返回值、参数表)被充分利用。函数在退出前应该设置这个全局变量的值为成功或者失败(包括原因),而与返回值一样,它隐含的要求调用者要在调用后检查这个标志,这种约束实在是同样软弱。全局变量还导致了另外一个问题,就是多线程不安全:如果多个线程同时为一个全局变量赋值,则调用者在检查这个标志的时候一定会非常迷惑。如果希望线程安全,可以参照errno的解决办法,它是线程安全的。

          异常:现在我们再来看看异常能解决什么问题。对于返回值和errno遇到的尴尬,对异常来说基本上不存在,如果你不捕获(catch)程序中抛出的异常,默认行为是导致abort()被调用,程序被终止(core dump)。因此你的函数如果抛出了异常,这个函数的调用者或者调用者的调用者,也就是在当前的call stack上,一定要有一个地方捕获这个异常。而对于setjmp()/longjmp()带来的栈上对象不被析构的问题对异常来说也是不存在的。那么它是否破坏了结构化(对于OO paradigms,也许应该说是破坏了流程?)呢?显然不是,有了异常之后你可以放心的只书写正确的逻辑,而将所有的错误处理归结到一个地方,这不是更好么? 

          异常的优势:

           1。能够用较小的投入写出高质量的代码,省去了很多测试的时间。

            通常可以用try/catch/throw返回一个状态(有时也称为错误代码),调用者被假定为通过判定返回值确定函数是否执行成功。

            尽管返回技术有时候似乎是最适当的错误处理技术,但是他也会增加一些令人厌恶的负面影响,例如:

           a.降低性能

            正如大家所知道的,相对其他判断而言,条件判断几乎10倍错误概率,因此,其他的事情相当于,如果你能把条件判定相关的代码移出你的工程,你很有可能会有一个相对健壮很多的代码。

            b.会延缓开发周期,提高成本

            会增加很多白鹤测试的测试用例,不必要的条件判定会增加很多的测试人力方面的开销;如果你没有测试过每一个代码分支,可能会有一些遗留bug,如果遗留给用户,将是非常严重的事情。

            c.增加开发成本

            发现bug,修改bug,测试bug将会增加很多不必要的流程控制复杂度。

            因此与用if判定错误代码相比,try/catch/throw会有少量的bug,相对便宜的开发成本,较短的开发周期,当然,如果你的团队没有使用exception相关的经验,你可以在一些预言性的项目上练习一下,以确保你知道怎么用它。

           2.构造函数调用失败后的处理

            构造函数没有返回值,因此不能返回错误代码,因此,用信号通知调用者调用失败的方法是抛出异常。

           3.析构函数调用失败后的处理

           写错误日至或者调用其他相关接口都可以,但是不要抛出异常 ,否则,如果该类中其他对象抛出了异常,在析构函数中再抛出异常,程序将terminate.

     

     

    展开全文
  • Java异常

    2021-01-23 18:59:56
    Java异常对应的类Exception类,Exception类对象是Java程序处理或抛弃的对象,它各种不同的子类分别对应于不同类型的异常。Java编译器要求程序必须捕获或声明所有的非运行时异常,但对运行时异常可以不做处理。...
  • Java 异常处理

    2020-06-19 18:59:26
    当我们编写了错误的代码时,编译器在编译期间可能抛出异常时候即使编译正常,在运行代码的时候也可能抛出异常。本小节我们将介绍什么异常、Java 中异常类的架构、如何进行异常处理、如何自定义异常什么...
  • 异常:1,异常:简单的说,能够产生使程序终止的行为。2,异常的种类:运行时异常:编译没问题,代码的问题,可以通过...如:明明文件存在为什么会编译不通过呢?编译器检查代码的时候发现这个代码可能会因为资源...
  • 在Java中运行表达式:1.0 / 0.0,会有返回么?会不会抛出异常或者是编译器error? 那1/0呢? ① 浮点数除法,1.0/0.0不会抛出异常,值Infinity。 This is another tricky question from Double class. Though ...
  • 浅析Java异常处理机制

    2018-04-18 23:30:30
    异常处理是Java中唯一正式的错误报告机制,...但是除数0又有什么含义呢?的时候我们可以通过当前的问题环境来得知该如何。但是如果出现了一些我们也不知道如何处理的异常,那么在这个时候我们就要抛出异常,而不是
  • 使用枚举定义常量时,会有伴有大量的switch语句判断,目的是为每个枚举解释其行为。  我们知道,目前的Java的switch语句只能判断byte、short、char、int类型(JDK7已经允许使用string类型),为什么枚举也能跟在...
  • 在程序中为什么有异常处理呢? 首先程序很难做到完美,存在各种bug,为了解决这些异常,我们需要知道知道异常发生的原因; 当程序运行发生错误的时候,就抛出异常,这时我们可以设置一个捕捉异常的“处理器...
  • 例如,一段读取文件的代码知道可能读取的文件不存在,或者内容空,因此,试图处理文件信食的代码就需要通知编译器可能抛出IOException类的异常。  方法应该在其首部声明所有可能抛出的异常。这样可以从首部...
  • 一、为什么要做这件事? 不知道你平时在写Controller层接口的时候,没有注意过抛出异常该怎么处理,是否第一反应是想着用个try-catch来捕获异常? 但是这样地处理只适合那种编译器主动提示的检查时异常,因为你...
  • Java异常分类和统一处理

    千次阅读 2016-11-23 09:53:33
    Java异常分为"检查"和"非检查"两类,"检查"二字的意思是,代码编译时,编译器会去Check一下没有进行异常处理(捕获或向上抛),对于归类需要检查的异常,若没处理,编译就会报错过不去。 初学Java时常常想...
  • 在编译期间出现的错误有编译器帮助我们一起修正,然而运行期间的错误便不是编译器力所能及了,并且运行期间的错误往往是难以预料的。2. 在程序运行过程中,意外发生的情况,背离我们程序本身的意图的表现,都可以...
  • 数组-数组为什么特殊

    2018-05-18 22:49:01
    数组与其他种类容器三个主要的区别:效率、类型和保存基本类型的能力。...如果滥用就得到RuntimeException的异常!数组可以通过编译器检查,防止插入错误类型的数据。数组插入的是基本类型。数组使用[]来访问...
  • 既然这么多地放都提到了 SEH,那我为什么还要说它是未公开的呢?本质上讲,Win32 结构化异常处理是操作系统提供的一种服务。编译器的运行时库对这种服务操作系统实现进行了封装,而所有能找到的介绍 SEH 的文档讲...
  • java编译器把unreachable statement标记运行时错误,一个unreachable statement就是编译器决定永远不会执行它。 不可达语句的造成是因为:在此语句前面一个返回操作,或者其他操作导致不管什么条件都无法执行...
  • 我了解已检查和未检查的异常之间的区别。 Java编译器迫使程序员要么用...为什么会这样呢?这是因为在继承层次结构中,类(其中包含可能产生异常的代码)位于顶部?例如,我正在为Hadopp映射器编写映射函数:public vo...
  • 作为一个面向对象编程的程序员对于 下面的一句一定非常熟悉:try{//代码块}catch(Exception e){//...为什么有异常其实这个问题不用多说,程序员都知道,简单总结为一句话:就是为了增强程序健壮性呗,比如下面的代...
  • 很多猿友会有疑问,我写的程序如果出错,那么编译器会给我提示的,那么我直接改正不就可以了?为什么还要用try catch throw语句来重新捕获呢?这个理解是片面的,JAVA中的异常处理机制,主要是针对用户设置的处理...
  • 不初始化也能通过编译器,但指针指向哪里,你根本不知道,如果指向的是内存中比较重要的地方,可能导致系统异常,如有时电脑提示指向了一个不可用的地址之类的错误。 定义一个指针的时候,可以在声明语句中初始...
  • 为什么需要 java中错误的信息也被包装到对象里面。对象就是包装数据,组织数据的,那错误也是数据。 大部分异常这两个构造器 string s表示详细消息。 Throwable类 Error(错误)不需要程序猿...
  • main函数里不写return 0,怎么样?

    千次阅读 2019-02-11 15:45:41
    对于下面的代码: int main() { } 代码中没有写 return 0,或者写的文绉绉一点 ...为什么C++的编译器会做这些? 因为C++有异常处理,因此他代码的返回点不一定是在return 0 那里进行返回的。比如: class O...
  • 一、分析使用枚举定义常量时,会有伴有大量的switch语句判断,目的是为每个枚举解释其行为。我们知道,目前的Java的switch语句只能判断byte、short、char、int类型(JDK7已经允许使用string类型),为什么枚举也能跟在...

空空如也

空空如也

1 2 3 4 5 ... 10
收藏数 181
精华内容 72
关键字:

为什么会有编译器异常