面试题大全
说一说你是怎么处理Java的异常的?
一般使用 try 去包裹可能出现异常的代码块,然后使用 catch 去根据不同的异常类型,制定不同的结局方案。最后如果有需要回收的资源,或者必须要执行的特殊逻辑,可以写在 finally 代码块里。
介绍一下Java的异常体系
Throwable 是Java异常体系的顶层父类,它有两个直接子类 Error(错误) 和 Exception(异常)
Error(错误)一般是和虚拟机相关的问题,例如堆、栈溢出,或者硬件损坏等。
Exception(异常)又可以分为两类,分别是 检查型异常 和 运行时异常 。
1、检查型异常(Checked Exception):
检查型异常是在编译时,由编译器进行检查和抛出的,例如我们new 一个并不存在的类,或者我们的代码编写格式不符合Java语义标准,这些错误在编译时就会抛出,不解决就不能通过编译,也就没法运行代码。(实际开发中,我们使用的idea等工具,在写代码的时候,就会报错提示。)
2、运行时异常(Runtime Exception):
运行时异常是在程序运行时,因为代码逻辑上的错误而出现的,可以在编写代码时避免,例如访问 null 对象,数组越界等。编译器不会检查这类异常(也不好检查)。
请说一下异常的传递
当出现异常时,从出现异常的方法内,一层层向上传递。
主线程发生异常,一直传到 main 方法还未得到捕获处理,会退出线程。
子线程发生异常,一直传到子线程的方法栈顶还未得到捕获处理,会关闭线程,然后异常传递终止。不会影响到主线程。
介绍一下 finally
当我们有需要回收的资源,或者必须要执行的特殊逻辑,可以写在 finally 代码块里,这能最大程度保证我们用来收尾工作的代码总是能被执行到。
因为不管是否发生异常,或者在 try catch 里面 return ,finally 也总是能被执行到的。除非遇到程序在执行到 finally 前被杀死的这种极端情况。
finally 里面 return 会怎么样?
正常情况下,不要在 finally 里面 return ,因为这会导致 try 或者 catch 里面的 return 失效。
throw 也是同理,如果在 finally 里面 throw ,也会导致 try 或者 catch 里面的 throw 失效。
相关知识
Java的异常体系可以分为两大类,Error(错误) 和 Exception(异常),这也恰好对应着 Throwable 的两个子类。
Error(错误) 一般是和虚拟机相关的问题,例如堆、栈溢出,或者硬件损坏等。
Exception(异常) 又可以分为两类,分别是 检查型异常 和 运行时异常 。
1、检查型异常(Checked Exception):
检查型异常是在编译时,由编译器进行检查和抛出的,例如我们new 一个并不存在的类,或者我们的代码编写格式不符合Java语义标准,这些错误在编译时就会抛出,不解决就不能通过编译,也就没法运行代码。(实际开发中,我们使用的idea等工具,在写代码的时候,就会报错提示。)
2、运行时异常(Runtime Exception):
运行时异常是在程序运行时,因为代码逻辑上的错误而出现的,可以在编写代码时避免,例如访问 null 对象,数组越界等。编译器不会检查这类异常(也不好检查)。
异常的传递
当出现异常时,从出现异常的方法内,一层层向上传递。
主线程发生异常,一直传到 main 方法还未得到捕获处理,会退出线程。
子线程发生异常,一直传到子线程的方法栈顶还未得到捕获处理,会关闭线程,然后异常传递终止。不会影响到主线程。
异常的层级
Error 和 Exception 都继承于 Throwable 类
Java中所有异常类都是从 java.lang.Exception 类继承的子类,异常类有两个主要的子类:IOException 类和 RuntimeException 类。
常见的异常类
常见的运行时异常:
异常 | 描述 |
---|---|
ArithmeticException | 算术异常 当运算条件出现异常时,抛出。 例如一个整数 除以零 时,抛出此异常。 |
ArrayIndexOutOfBoundsException | 数组下标越界异常 数组的索引等于负数或者超过数据大小时,抛出此异常。 |
ArrayStoreException | 数组存储异常 将错误的对象类型存储到一个对象数组时抛出此异常。 |
ClassCastException | 类强制转换异常 将对象强制转换为不是实例类型本身或实例类型的子类时,抛出此异常。 |
IllegalArgumentException | 非法数据异常 向方法传递一个不合法或不正确的参数,抛出此异常。 |
IllegalMonitorStateException | 非法监控状态异常 如果当前的线程不是此对象锁的所有者,却调用该对象的notify(),notify(),wait()方法时抛出该异常。 |
IllegalStateException | 非法状态异常 当前状态,不能执行此操作时,抛出此异常。 |
IllegalThreadStateException | 非法线程状态异常 线程处于runing状态时再次调用 start() 或者线程处于 end 状态下调用 stop() 时抛出异常。 |
IndexOutOfBoundsException | 索引越界异常 当对数组、字符串的索引等于负数或者超过数据大小时,抛出此异常。 ArrayIndexOutOfBoundsException 继承于它 |
NegativeArraySizeException | 否定数组大小异常 试图创建大小为负的数组,则抛出该异常。 |
NullPointerException | 空指针异常 当一个类型对象等于 null 时,试图访问它的成员方法或成员变量时,抛出此异常。 |
NumberFormatException | 数字格式异常 试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。 |
SecurityException | 安全异常 由安全管理器抛出的异常,指示存在安全侵犯。 |
StringIndexOutOfBoundsException | 字符串索引超出界限异常 此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。 |
UnsupportedOperationException | 不支持的操作异常 当对Java提供的集合或一些工具类,例如List、Map等执行不支持的操作时,抛出该异常。 |
常见的检查型异常类:
异常 | 描述 |
---|---|
ClassNotFoundException | 未找到类异常 试图加载应用程序访问不到的类时,抛出此异常。 |
CloneNotSupportedException | 不支持克隆异常 当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。 |
IllegalAccessException | 非法访问异常 当一个类(非子类)试图访问另一个类使用 private 或 protected 修饰的变量、方法时,抛出此异常。 |
InstantiationException | 实例化异常 当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。 |
InterruptedException | 中断的异常 一个线程被另一个线程中断,抛出该异常。 |
NoSuchFieldException | 类无此字段异常 访问的变量不存在 |
NoSuchMethodException | 类无此方法异常 访问的方法不存在 |
常用异常方法
Throwable 类的主要方法:
方法及说明 |
---|
**public String getMessage()**获取异常的详细描述信息,在Throwable 类的构造函数中初始化了。 |
**public Throwable getCause()**获取 Throwable 对象异常原因。 |
**public String toString()**获取此 Throwable 的实际类型和详细描述信息(如果message不空)。 return (message != null) ? (s + ": " + message) : s |
**public void printStackTrace()**回溯打印到标准错误流。 |
**public StackTraceElement [] getStackTrace()**返回包含堆栈层次的数组。下标为0的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底。 |
**public Throwable fillInStackTrace()**用当前的调用栈层次填充Throwable 对象栈层次,添加到栈层次任何先前信息中。 |
捕获异常
使用 try / catch 关键字,可以进行异常捕获,try 区域,包裹着可能出现异常的代码块,catch 区域,捕获和编写异常的处理方案,语法如下:
try {
// 可能出现异常的代码部分
} catch (Exception e) {
// 出现异常时的处理逻辑
}
例子:
这里是一个典型的数组越界异常,我们创建一个List列表,并没有往里面添加数据,却获取了第1行的数据,此时会抛出数组越界的异常类型。
try {
// 可能出现异常的代码部分
List<String> list = new ArrayList();
System.out.print(list.get(0));
} catch (Exception e) {
// 出现异常时的处理逻辑
System.out.print("Exception thrown :" + e);
}
输出:
Exception thrown :java.lang.IndexOutOfBoundsException: Index: 1, Size: 0
多重异常捕获
Java允许一个 try 后面跟随多个 catch,这就是多重异常捕获,我们可以根据不同的异常类型,制定对应的异常处理方案。
try {
// 可能出现异常的代码部分
} catch (IndexOutOfBoundsException e) {
// 出现异常时的处理逻辑
} catch (Exception e) {
// 出现异常时的处理逻辑
}
注意: 多重异常捕获,是按照先后顺序进行遍历检查的,所以一定要遵循一个原则,子类异常排在前面,父类异常排在后面,因为Java的有多态性,如果父类异常排在前面,会导致子类异常也进入父类异常的代码块里处理,让我们的代码逻辑出现错误。
案例如下:
这是一个明显的数组越界异常,我们预期的是进入 IndexOutOfBoundsException 异常块进行处理,但由于 Exception 是 IndexOutOfBoundsException 的父类,异常被 Exception 代码块捕获了。
try {
// 可能出现异常的代码部分
List<String> list = new ArrayList();
System.out.print(list.get(0));
} catch (Exception e) {
// 出现异常时的处理逻辑
System.out.print("Exception thrown :" + e);
} catch (IndexOutOfBoundsException e) {
// 数组越界时的处理逻辑
System.out.print("数组越界啦 :" + e);
}
输出:
Exception thrown :java.lang.IndexOutOfBoundsException: Index: 1, Size: 0
finally 关键字
finally 关键字用来创建 try / catch 后面需要被执行的代码。
无论是否发生异常,finally 代码块总是会被得到执行(除非程序进程在执行 finally 之前被杀掉)。
finally 代码块一般用于清理、收尾善后等工作的执行。
语法如下:
try {
// 可能出现异常的代码部分
} catch (Exception e) {
// 出现异常时的处理逻辑
} finally {
// finally 要执行的逻辑
}
案例:
在 try 代码块中,会出现数组越界的异常,然后 catch 捕获到异常,虽然 catch 代码块中 返回了false,但是在返回前,还是会先执行 finally 代码块。
List<String> list = new ArrayList();
try {
// 可能出现异常的代码部分
System.out.println(list.get(0));
} catch (Exception e) {
// 出现异常时的处理逻辑
System.out.println("Exception thrown :" + e);
return false;
} finally {
// finally 要执行的逻辑
list.add("finally 添加的数据");
System.out.println("finally 代码块执行 :" + list.get(0));
}
执行结果:
Exception thrown :java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
finally 代码块执行 :finally 添加的数据
finally 特性
finally 不能独立于 try 存在
finally 代码块总是会被执行,除非程序进程在执行 finally 之前被杀掉
throws/throw 关键字
throws 关键字用于给方法声明一个它会抛出的异常列表,throws 关键字放在方法的尾部,多个可使用逗号分隔。
throw 关键字用于在方法内,向外抛出一个异常,这个异常可以是刚实例化的,也可以是其他地方捕获到的。
语法示例:
public void check() throws IndexOutOfBoundsException {
throw new IndexOutOfBoundsException("这是一个数组越界异常");
}
自定义异常
在Java中,我们可以自定义异常。
所有的异常,都必须是 Throwable 的子类。
我们可以根据自身的需求,去定制专属业务的异常类。
实例:
我们自定义一个异常类,在异常类中加入一个编码。
异常捕获者可以通过异常编码,来明确知道本次出现了什么错误。
/**
* 自定义异常
*/
public class CommonException extends Exception {
private String code;
public CommonException(String message, String code) {
super(message);
this.code = code;
}
public CommonException(CommonErrorEnum errorEnum) {
super(errorEnum.getDesc());
this.errorMsg = errorEnum.getDesc();
this.errorCode = errorEnum.getCode();
}
public CommonException(String msg, String code, Throwable cause) {
super(msg, cause);
this.errorMsg = msg;
this.errorCode = code;
}
public String getCode() {
return errorCode;
}
public void setCode(String code) {
this.code = code;
}
}
使用自定义异常
这个实例的业务是向前端开放一个【获取用户信息】的接口。
当 checkLogin()
校验 token
失败的时候,判定为【未登录】,并且抛出异常描述和异常编码。
这样前端在获取用户信息时,就可以根据异常编码,判断是否需要跳转登录页面。
@RequestMapping(value = "/common/")
public class CommonController {
@RequestMapping(value = "checkToken.json", method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
public JSONObject checkToken(String token){
try {
// 1、checkLogin 没有抛出异常,说明校验成功
checkLogin(token);
// 2、获取用户信息
Object data = getUserInfo(token);
// 3、返回用户信息
return setSuccessResult(data);
} catch(CommonException e){
// 返回自定义异常的信息,比如上文可能会抛出一个内容是【未登录】的自定义异常,
return setFailResult(e.getMessage(), e.getCode());
} catch(Exception e){
return setFailResult("未知异常:" + e.getMessage(), null);
}
}
private void checkLogin(String token){
if(null == getToken(token)){
// token校验失败,抛出自定义异常,异常内容是未登录
return new CommonException("未登录", "NO_LOGIN");
}
}
private JSONObject setSuccessResult(Object data){
JSONObject result = new JSONObject();
result.put("success", true);
result.put("data", data);
return result;
}
private JSONObject setFailResult(String msg, String code){
JSONObject result = new JSONObject();
result.put("success", false);
result.put("errorMessage", msg);
result.put("errorCode", code);
return result;
}
}