2023 最全面试宝典《java异常》

大家号欢迎来到小码哥课堂,今天来回顾下在面试中被问到有关Java异常的问题该怎么回答,Java异常到底是什么?需要完整版关注下方公众号:回复 码出八股文_斩出offer线
具体内容路径
https://blog.csdn.net/a2986467829/article/details/125417976?spm=1001.2014.3001.5502

Java异常面试题

1、Java中异常分为哪两种?

Java的异常分为两种,

1、一种是运行时异常(RuntimeException),

​ 运行时异常不需要程序员去处理,当异常出现时,JVM会帮助处理。

常见的运行时异常有:

  1. ​ ClassCastException(类转换异常)
  2. ​ ClassNotFoundException
  3. ​ IndexOutOfBoundsException(数组越界异常)
  4. ​ NullPointerException(空指针异常)
  5. ​ ArrayStoreException(数组存储异常,即数组存储类型不一致)
  6. ​ 还有IO操作的BufferOverflowException异常

2、一种是非运行异常也叫编译时异常(CheckedException)。

非运行异常需要程序员手动去捕获或者抛出异常进行显示的处理,因为Java认为Checked异常都是可以被修复的异常。

常见的异常有:

  1. IOException
  2. SqlException

2、异常的处理机制有哪些

异常捕捉:try…catch… fifinally,

异常抛出:throws。

3、 Java异常简介

Java异常是Java提供的一种识别及响应错误的一致性机制。 Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。在有效使用异常的情况下,异常能清晰的回答what, where, why这3个问题:异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪”抛出,异常信息回答了“为什么”会抛出。

4、Throwable

  • Throwable 是 Java 语言中所有错误与异常的超类。
  • Throwable 包含两个子类:Error(错误)和 Exception(异常),它们通常用于指示发生了异常情况。
  • Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。

5、Error(错误)

  • 定义:Error 类及其子类。程序中无法处理的错误,表示运行应用程序中出现了严重的错误。
  • 特点:此类错误一般表示代码运行时 JVM 出现问题。通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如 OutOfMemoryError:内存不足错误;StackOverflflowError:栈溢出错误。此类错误发生时,JVM 将终止线程。这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误。按照Java惯例,我们是不应该实现任何新的Error子类的!

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

7、 受检异常与非受检异常

  • Java 的所有异常可以分为受检异常(checked exception)和非受检异常(uncheckedexception)。

受检异常

  • 编译器要求必须处理的异常。正确的程序在运行过程中,经常容易出现的、符合预期的异常情况。一旦发生此类异常,就必须采用某种方式进行处理。除 RuntimeException 及其子类外,其他的Exception 异常都属于受检异常。编译器会检查此类异常,也就是说当编译器检查到应用中的某 处可能会此类异常时,将会提示你处理本异常——要么使用try-catch捕获,要么使用方法签名中用 throws 关键字抛出,否则编译不通过。

非受检异常

  • 编译器不会进行检查并且不要求必须处理的异常,也就说当程序中出现此类异常时,即使我们没有try-catch捕获它,也没有使用throws抛出该异常,编译也会正常通过。该类异常包括运行时异常(RuntimeException极其子类)和错误(Error)。

8、Java异常关键字

  • try – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
  • catch – 用于捕获异常。catch用来捕获try语句块中发生的异常。
  • fifinally – fifinally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有fifinally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果fifinally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
  • throw – 用于抛出异常。
  • throws – 用在方法签名中,用于声明该方法可能抛出的异常。

9、 Java异常处理

1、声明异常

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

注意

  • 非检查异常(Error、RuntimeException 或它们的子类)不可使用 throws 关键字来声明要抛出的异常。
  • 一个方法出现编译时异常,就需要 try-catch/ throws 处理,否则会导致编译错误。

2、抛出异常

  • 如果你觉得解决不了某些异常问题,且不需要调用者处理,那么你可以抛出异常。
  • throw关键字作用是在方法内部抛出一个 Throwable 类型的异常。任何Java代码都可以通过throw语句抛出异常。

3、捕获异常

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

4、如何选择异常类型

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-amRnX3oi-1682217785798)(C:\Users\ponyMa\AppData\Roaming\Typora\typora-user-images\image-20220531151310643.png)]

5. 常见异常处理方式

直接抛出异常

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

当方法中发生异常,异常处之后的代码不会再执行,如果之前获取了一些本地资源需要释放,则需要在方法正常结束时和 catch 语句中都调用释放本地资源的代码,显得代码比较繁琐,fifinally 语句可以解决这个问题。

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 代码块,之后进入 fifinally 代码块;若读取文件时未发生异常,则会跳过 catch 代码块直接进入 fifinally 代码块。所以无论代码中是否发生异常,fifianlly 中的代码都会执行。
  • 若 catch 代码块中包含 return 语句,fifinally 中的代码还会执行吗?将以上代码中的 catch 子句修改如下:
catch (IOException e) {
	System.out.println("readFile method catch block.");
return;
}
  • 调用 readFile 方法,观察当 catch 子句中调用 return 语句时,fifinally 子句是否执行
readFile method catch block.
readFile method finally block.
  • 可见,即使 catch 中包含了 return 语句,fifinally 子句依然会执行。若 fifinally 中也包含 return 语句,fifinally 中的 return 会覆盖前面的 return.
try-with-resource

上面例子中,fifinally 中的 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 方法放在 fifinally 代码块中不同的是,若 scanner.close 抛出异常,则会被抑制,抛出的仍然为原始异常。被抑制的异常会由 addSusppressed 方法添加到原来的异常,如果想要获取被抑制的异常列表,可以调用getSuppressed 方法来获取。

10、Error 和 Exception 区别是什么?

  • Error 类型的错误通常为虚拟机相关错误,如系统崩溃,内存不足,堆栈溢出等,编译器不会对这类错误进行检测,JAVA 应用程序也不应对这类错误进行捕获,一旦这类错误发生,通常应用程序会被终止,仅靠应用程序本身无法恢复;
  • Exception 类的错误是可以在应用程序中进行捕获并处理的,通常遇到这种错误,应对其进行处理,使应用程序可以继续正常运行。

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

  • 运行时异常包括 RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。 Java 编译器不会检查运行时异常。
  • 受检异常是Exception 中除 RuntimeException 及其子类之外的异常。 Java 编译器会检查受检异常。
  • RuntimeException异常和受检异常之间的区别:是否强制要求调用者必须处理此异常,如果强制要求调用者必须进行处理,那么就使用受检异常,否则就选择非受检异常(RuntimeException)。一般来讲,如果没有特殊的要求,我们建议使用RuntimeException异常。

12、JVM 是如何处理异常的?

  • 在一个方法中如果发生异常,这个方法会创建一个异常对象,并转交给 JVM,该异常对象包含异 常名称,异常描述以及异常发生时应用程序的状态。创建异常对象并转交给 JVM 的过程称为抛出异常。可能有一系列的方法调用,最终才进入抛出异常的方法,这一系列方法调用的有序列表叫做调用栈。
  • JVM 会顺着调用栈去查找看是否有可以处理异常的代码,如果有,则调用异常处理代码。当 JVM发现可以处理异常的代时,会把发生的异常传递给它。如果 JVM 没有找到可以处理该异常的代码块,JVM 就会将该异常转交给默认的异常处理器(默认处理器为 JVM 的一部分),默认异常处理器打印出异常信息并终止应用程序。

12、throw 和 throws 的区别是什么?

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

throws 关键字和 throw 关键字在使用上的几点区别如下:

  • throw 关键字用在方法内部,只能用于抛出一种异常,用来抛出方法或代码块中的异常,受查异常和非受查异常都可以被抛出。
  • throws 关键字用在方法声明上,可以抛出多个异常,用来标识该方法可能抛出的异常列表。一个方法用 throws 标识了可能抛出的异常列表,调用该方法的方法中必须包含可处理异常的代码,否则也要在方法签名中用 throws 关键字声明相应的异常。

13、final、finally、finalize 有什么区别?

  • fifinal可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。
  • fifinally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法fifinally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
  • fifinalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,Java 中允许使用fifinalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。

14、NoClassDefFoundError 和 ClassNotFoundException 区别?

  • NoClassDefFoundError 是一个 Error 类型的异常,是由 JVM 引起的,不应该尝试捕获这个异常。
  • 引起该异常的原因是 JVM 或 ClassLoader 尝试加载某类时在内存中找不到该类的定义,该动作发生在运行期间,即编译时该类存在,但是在运行时却找不到了,可能是变异后被删除了等原因导致;
  • ClassNotFoundException 是一个受查异常,需要显式地使用 try-catch 对其进行捕获和处理,或在方法签名中用 throws 关键字进行声明。当使用 Class.forName, ClassLoader.loadClass 或ClassLoader.fifindSystemClass 动态加载类到内存的时候,通过传入的类路径参数没有找到该类,就会抛出该异常;另一种抛出该异常的可能原因是某个类已经由一个类加载器加载至内存中,另一个加载器又尝试去加载它。

15、常见的五种运行时异常:

ClassCastException(类转换异常)

IndexOutOfBoundsException

(数组越界)

NullPointerException(空指针异常)

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

Bu?erOver?owException

16、Error与Exception的区别

Exception和Error都继承自Throwable,在Java中只有Throwable类型的实例才可以被抛出或捕获。

Error指正常情况下不太可能出现的情况,绝大部分的Error或导致程序崩溃,

处于非正常的不可恢复的状态,如OutOfMemoryError、StackOverflowError。

是程序中不应该试图捕获的严重问题。Exception是程序正常运行中可以预料的意外情况,可以捕获并处理。

总结:

  1. Error和Exception都是java错误处理机制的一部分,都继承了Throwable类。
  2. Exception表示的异常,异常可以通过程序来捕捉,或者优化程序来避免。
  3. Error表示的是系统错误,不能通过程序来进行错误处理。

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

以下三种情况都是可以的:
try-catch
try-finally
try-catch-finally
可以省略catch或者finally。catch和finally不可以同时省略。

18、如果 catch 中 return 了,finally 还会执行吗?

会。

(1)finally的作用就是,无论出现什么状况,finally里的代码一定会被执行。

(2)如果在catch中return了,也会在return之前,先执行finally代码块。

(3)而且如果finally代码块中含有return语句,会覆盖其他地方的return。

(4)对于基本数据类型的数据,在finally块中改变return的值对返回值没有影响,而对引用数据类型的数据会有影响。

注:
finally也不是一定会被执行。

19、什么情形下,finally代码块不会执行?

1、什么情况下不会执行

(1)没有进入try代码块;

(2)System.exit()强制退出程序;

(3)守护线程被终止;

2、finally为什么不会执行

1.finally的含义

finally的真正含义是指从try代码块出来才一定会执行相应的finally代码块。

public class Test {
    public static void main(String[] args) {
        foo(false);
    }
    public static void foo(boolean flag) {
        System.out.println("enter foo()");
        if(flag) {
          try {
              System.out.println("enter try block");
          } finally {
              System.out.println("enter finally block");
          }
        } else {
            System.out.println("leave foo()");
        }

    }
}
/
控制台打印如下
enter foo()
leave foo()
/

上述代码,flag为false,没有进入try代码块,对应的finally自然也不会执行。

2.System.exit()

System.exit()的作用是中止当前虚拟机,虚拟机都被中止了,finally代码块自然不会执行。

public class Test {
    public static void main(String[] args) {
        foo();
    }
    public static void foo() {
        System.out.println("enter foo()");
       try {
            System.out.println("enter try block");
            System.exit();
       } finally {
            System.out.println("enter finally block");
       }
    }
}
/
控制台打印如下
enter foo()
enter try block
/

上述代码,进入foo()方法后再进入try代码块,但是在进入finally代码块之前调用了System.exit()中止虚拟机, finally代码块不会被执行。

3.守护(daemon)线程被中止时

java线程分为两类,守护线程和非守护线程。当所有的非守护线程中止时,不论存不存在守护线程,虚拟机都会kill掉守护线程从而中止程序。 虚拟机中,执行main方法的线程就是一个非守护线程,垃圾回收则是另一个守护线程,main执行完,程序就中止了,而不管垃圾回收线程是否中止。 所以,如果守护线程中存在finally代码块,那么当所有的非守护线程中止时,守护线程被kill掉,其finally代码块是不会执行的。

public class Test {
    public static void main(String[] args) {
        Thread t = new Thread(new Task());
        t.setDaemon(true); //置为守护线程
        t.start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException("the "+Thread.currentThread().getName()+" has been interrupted",e);
        }
    }
}
class Task implements Runnable {
    @Override
    public void run() {
         System.out.println("enter run()");
        try {
            System.out.println("enter try block");
            TimeUnit.SECONDS.sleep(5); //阻塞5s
        } catch(InterruptedException e) {
            System.out.println("enter catch block");
            throw new RuntimeException("the "+Thread.currentThread().getName()+" has been interrupted",e);
        } finally {
            System.out.println("enter finally block");
        }      
    }
}
/
控制台打印如下
enter run()
enter try block

上述代码,main()执行完,非守护线程也就结束了,虽然线程t处于阻塞状态,但由于其是守护线程,所以程序仍会中止。 而且,即使其进入了try代码块,finally代码块也不会被执行。

总结

finally代码块并非一定执行,在不进入try代码块或者程序被中止时就不会执行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值