大家好,我是徒手敲代码。
今天分享面试中,常见的几个异常问题。在看面试题之前,首先要问问自己,究竟什么是异常,怎么理解异常呢?
从字面上看,异常就是不正常的意思。如何定义不正常呢?做饭之前,买好了苦瓜,买好了牛肉,本来想做个番茄炒蛋。结果家里停电停水,天意弄人。像这些意外情况,让我们无法继续把做饭这个动作进行下去,只能停下来,解决了停电停水这个问题,才能继续,这就是异常。
回到编码上,当我们运行Java程序的时候,JVM会按照字节码,一行一行地运行下去,注意不一定按照我们写的顺序来的喔(这和万恶的编译器有关)。在某个时候,它会趁我们不注意,就出现一些意想不到的问题,比如:数组越界、空指针、数据库连接失败、内存溢出等。此时此刻的这些现象,就称为异常情况。
但是,计算机和人的最大区别之一,就是无论发生了多大的错误,计算机总会说得明明白白,为我们指明修复的大方向,而不需要各种各样的猜测。
异常总体分为两类,分别是非运行时异常和运行时异常。
非运行时异常,指的是在编译的过程中就已经出现的异常,一般idea会要求处理完之后,才能运行,所以这种很容易就能察觉。
运行时异常,指的是运行的时候出现的异常。一般要通过一些编程手段来处理,比如用try catch、对入参进行空判断等。
初学者在看到异常信息的时候,会十分慌张,天啊,为什么会报错?它说的什么意思?然后马上 Ctrl+CV 去网上疯狂找方案。其实,在遇到异常的时候,我们没有必要过于紧张,首先深呼吸一口气,一行一行地慢慢看,别着急。很可能只是配置没有配好、中英文符号问题、依赖冲突之类的,一系列常见问题。
针对异常的处理,常见的方案是捕获,或者抛出。捕获是在当前的方法进行处理,而抛出是丢给上一层或者上上层进行处理。就像背锅一样,是自己背,还是甩出去呢?看你自己咯。
接下来说说在面试中,如何扛住常见的几个问题。看到这里,请抬一下头,然后一鼓作气,把下面的重点看完。
这一类问题,可以先说说什么是异常,然后如何处理。
异常指的是程序可以处理的exception,应该尽可能地捕获,捕获后尽可能地恢复,而不应该随意终止。与异常相近的概念是error,error指的是程序无法处理的错误,一般是内存溢出、系统崩溃、虚拟机错误、方法调用栈溢出等JVM相关的问题,遇到的话只能让程序终止。
对于异常的处理,通常采用抛出异常或捕获异常的方式来处理,抛出异常指抛出一个Throwable对象或者是Throwable的子类对象,交由运行时系统处理;捕获异常指寻找合适的异常处理器来处理异常,否则终止运行。在捕获异常时,应该注意保留原始的信息,可以打印在日志里面保存起来,以便于定位问题并进行调试。
在程序运行的过程中,我们需要对异常进行统一处理,在Controller层,需要对各种自定义的业务异常进行统一拦截,返回给前端;在Service层,可以统一拦截数据库操作相关的异常,包装成自定义的业务异常向上抛;在全局层面,可以对其它未捕获的Throwable对象跟它的子类对象统一拦截,返回给前端。
尽量说自己熟悉的、百分百对的出来,不熟的就不要硬抛书包了。
异常分为运行时异常RuntimeException和非运行时异常。非运行时异常,在编译阶段就能知道,而运行时异常,可以用程序来避免或者做一些补救措施,例如:先判断对象是否为空,再调用这个对象的方法,来避免空指针异常NullPointerException;常见的异常还有类型强制转换异常ClassCastException,传递非法参数异常IllegalArgumentException、下标越界异常IndexOutOfBoundsException、数字格式异常NumberFormatException
打开idea,自己敲两遍就知道啦。
throw是用在方法体内,主动抛出一个异常对象,而且只能抛一个,一般用在try/catch里面。throws用在方法声明后面,用来指定抛出一个或多个异常类型,让捕获者可以捕获或者处理这些异常。throw表示一定发生了某个异常,而throws表示可能会发生这些异常。有些异常不适合在当前方法中处理,而要抛给上层,让上层的调用者决定如何处理。例如:一个方法要读取一个文件,此时找不到这个文件,就抛出一个IOException,让上层调用者决定如何处理,例如重新选择一个文件或者提示用户错误信息,如果直接捕获了,可能无法通知调用者发生了什么问题,也无法让调用者有机会处理问题。
没有异常的时候,当然是皆大欢喜。一旦有人甩锅出去了,那问题就大咯。
如果try catch没有抛出异常,那么对性能几乎没影响,只是多了一些判断。但是如果try catch抛出了异常,对性能影响就很大,因为抛出异常需要做很多事情,例如:当一个异常被抛出,首先要创建一个异常对象,包含异常的类型、消息、栈信息等属性,需要占用内存空间;其次,要携带当前线程的调用栈信息,需要遍历当前线程的所有栈帧,并且将它们转换成字符串形式。然后JVM会寻找一个合适的异常处理器,来处理这个异常,需要匹配异常的类型和范围,从最近的栈帧开始向上查找,直到找到一个匹配的catch块或者到达栈顶。如果当前方法还有一些未完成的操作或者没有释放的资源,例如打开的文件流、持有的锁等,需要对这些资源进行清理,避免内存泄漏或者死锁
finally就是最后的守护者,无论你做了什么,它都会为你兜底,为你负重前行。
finally的代码,无论前面是否有catch异常,都会执行的。如果catch和finally都有return,那么finally里面的return,会将catch的return覆盖掉。
今天的分享到这里结束了,如果你喜欢这种讲解知识的方式,欢迎下方留言。
你的支持,是我创作的最大动力!
关注公众号“徒手敲代码”,让我们在技术的星辰大海、生活的诗与远方中共勉同行,一起书写属于我们的精彩篇章!