2024 Java 异常—面试常见问题

目录

一、异常的分类

二、throw和throws都是异常处理的关键字,二者区别。

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

四、try-catch-finally 中哪个部分可以省略?

五、常见的 RuntimeException 有哪些?

六、Error 和 Exception 区别是什么?

运行时异常

编译时异常


一、异常的分类

 Java中异常处理的两种方式:(Ctrl + alt + t)

Throwable 是 Java 语言中所有错误与异常的超类。

Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。

Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。

try – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。

catch – 用于捕获异常。catch用来捕获try语句块中发生的异常。

finally – finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。

异常出现,throw出来,程序应该就中断了,也不会往下执行。

        通常情况下,如果没有捕获到异常,方法会提前结束并返回错误代码而不是整个程序。这意味着方法无法正常完成任务,并且通过返回错误代码来指示调用方发生了错误。当方法遇到错误或异常情况时,它将停止执行代码并立即返回给调用方。程序虽然将继续执行,但方法已经提前结束了。调用方可以根据方法返回状态进行相应的处理和错误处理。程序将正常退出,但是返回的错误代码不是0,而是表示具体出错情况的其他值。这样,调用方可以根据返回的错误代码进行相应的处理和错误处理。

二、throwthrows都是异常处理的关键字,二者区别。

  throw关键字用于手动抛出一个异常对象。当程序执行到throw语句时,会立即停止当前的执行路径,并将异常对象传递给调用栈中的上层异常处理机制。throw通常用于在代码中主动抛出异常,例如:

throw new IllegalArgumentException("Invalid argument");

  throws关键字用于声明方法可能抛出的异常类型。当一个方法可能引发异常但不处理时,可以使用throws关键字在方法签名中列出可能抛出的异常类型。这样做的目的是向方法的调用者明确表明该方法可能会产生异常,需要进行相应的异常处理。例如: 

public void readFile() throws IOException { // 读取文件的代码 }

在上面的例子中,readFile()方法声明了可能抛出IOException异常,调用该方法的代码需要进行相应的异常处理,例如使用try-catch块来捕获异常或者继续使用throws关键字将异常向上层抛出。

总结一下:

  • throw用于手动抛出一个异常对象。
  • throws用于在方法签名中声明可能抛出的异常类型,以便通知,声明调用者进行相应的异常处理。

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


答:会执行,在 return 前执行。

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

@SpringBootTest
class MyclocksourceApplicationTests {
    @Test
    void contextLoads() {
        getInt();
    }

    public static int getInt() {
        int a = 10;
        try {
            System.out.println(a / 0); // 这行代码会抛出一个 ArithmeticException 异常
            a = 20; // 这行代码永远不会被执行,因为上面那行会抛出异常
        } catch (ArithmeticException e) {
            a = 30;
            return a; 
            /*
            当执行到 return a 时,return 的值实际上是 30。
            虽然方法要返回,但 finally 块仍会执行。
            */
//return a 在程序执行到这一步的时候,这里不是return a 而是 return 30;这个返回路径就形成了
//但是呢,它发现后面还有finally,所以继续执行finally的内容,a=40
//再次回到以前的路径,继续走return 30,形成返回路径之后,这里的a就不是a变量了,而是常量30
        } finally {
            a = 40; // 修改变量 a 的值,但不会影响已经准备返回的值
// 但 return 30 的返回路径已经确定
        }
        System.out.println(a); // 这行代码永远不会被执行
        return a; // 这行代码也永远不会被执行
    }
}
  • 尽管 finally 块将 a 修改为 40,但是因为在 catch 块中已经准备返回 30,这个返回值不会被修改。最终返回的值是 30。
  • 但如果按照下面这样的方式去写,则会:
    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
            System.out.println(a);
            return a;
        }
    }

        在 Java 中,finally 块中的 return 语句会覆盖 trycatch 块中的 return 语句。也就是说,如果在 finally 块中有一个 return 语句,那个 return 语句将会成为最终的返回路径,无论之前的 return 语句是什么。 故执行结果:40

四、try-catch-finally 中哪个部分可以省略?

答:catch 可以省略

原因

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

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

五、常见的 RuntimeException 有哪些?


ClassCastException(类转换异常)

IndexOutOfBoundsException(数组越界)

NullPointerException(空指针)

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

还有IO操作的BufferOverflowException异常

六、Error 和 Exception 区别是什么?


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

通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;StackOverflowError:栈溢出错误。此类错误发生时,JVM 将终止线程。

这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误。按照Java惯例,我们是不应该实现任何新的Error子类的!

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

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

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

  • 24
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值