什么是异常
程序运行时发生的不被期望的事件,阻止了程序按照预期正常执行
常见的错误分类
编译错误
运行时错误
逻辑错误
java中的异常类
- java.lang 包下的 Throwable 类,这个类是所有异常类的父类,
- Throwable 类有 2个子类
- Error 出错后,程序无法处理 ,如 OutOfMemoryError
- Exception 出错后 ,程序可处理 , 如 ArrayIndexOutOfBoundException
- 异常两大类
- 可查异常(编译器要求必须要处理的异常,运行之前就报错)
- RuntimeException 及其子类除外 ,其它的Exception类及其子类 ,如 ClassNotFoundException ,NoSuchMethodException ⽅法不存在
- 不可查异常(编译器不要求强制处理的异常,运行时报错)
- 运行时异常 RunTimeException 及其子类 和 Error类 ,如ArrayIndexOutOfBoundException ,NullPointerException
- 可查异常(编译器要求必须要处理的异常,运行之前就报错)
所以 ,Exception 类可分为 可查异常 和不可查异常
- Throwable 常用方法
- public String getMessage()
- 获取异常的详细信息
- public Throwable getCause()
- 获取异常原因
- public void printStackTrace()
- 打印错误的堆栈信息 ,错误原因和位置
- public StackTraceElement [] getStackTrace()
- 堆栈层次的数组,下标为0的元素代表栈顶,最后⼀个元素代表⽅法调⽤堆栈的栈底
- public String getMessage()
public StackTraceElement [] getStackTrace()
返回一个表示该线程堆栈转储的堆栈跟踪元素数组。如果该线程尚未启动或已经终止,则该方法将返回一个零长度数组。如果返回的数组不是零长度的,则其第一个元素代表堆栈顶,它是该序列中最新的方法调用。最后一个元素代表堆栈底,是该序列中最旧的方法调用。
捕获异常之 Try Catch
-
格式
try{ // 可能发⽣异常的代码 }catch(AExceptionName e){ //出异常的时候处理 }catch(BExceptionName e){ //可以有多个catch }finally{ }
- catch 不能独立存在 ,必须依赖于try
- finally 可有可无,而且finally里的代码 一定会执行
- finally 代表结束 之后不可再跟 catch模块
- try 代码块包裹可能发生异常的代码,将异常捕获,但不会终止整个方法的执行
- 如果异常不用try 捕获,那么会终止整个方法 ,并报错打印堆栈信息
- 对于多个catch模块,当代码中发⽣异常,异常被抛给第⼀个 catch 块, 如果不匹配则继续往下⼀个catch进⾏传递
- try,catch和finally块有两种可能的组合:try-catch-finally或try-finally或try-catch。
- 总结 ;try 必须存在,后可接finally,catch, 或者三个都有
- try 不可单独存在
- try-catch里面定义的变量,只能在try - catch内部使用 , finally 不可访问;
- finally 最初用于资源的关闭
- finally 代码块里面有异常的话 ,finally里的语句执行到异常处终止,终止整个方法并直接报错
- catch 代码块里有异常的话,也会遇到异常语句终止方法,报错,但会执行 finally 语句
异常处理之 throws/throw
-
throws 声明异常 往外抛出 ,异常不可无限向上抛出,最终必须由调用者处理
public class Main { public static void readChar() throws IOException,RemoteException { int input = System.in.read(); } }
-
throws⼦句放在⽅法参数列表的右括号之后,⼀个⽅法可以声明抛出多个异常,
多个异常之间⽤逗号隔开
try catch中捕获了异常,捕获⾃⼰处理然后继续往外⾯抛异常
throw 语句
throw new ExceptionName(“异常信息”);
eg:throw new IOException(“File not found”);
public class ThrowMain { public static void main(String[] args) { try { divide(2,0); } catch (Exception e) { System.out.println("main"); System.out.println(e.getMessage()); } } public static int divide(int num1,int num2) throws Exception{ try { return num1 / num2; }catch (Exception e){ System.out.println("出异常!"); throw new Exception("参数异常!!!"); } } }
- throw 和 throws 搭配 一般用于自定义异常
- 单独使用throw 编译出错, 报错信息: java: 未报告的异常错误java.lang.Exception; 必须对其进行捕获或声明以 便抛出
- throw之后的语句不会执行, 程序在此中断
总结:当抛出⼀个被检查的异常,我们必须使⽤try-catch块来处理它,或者在⽅法声明中使⽤ throws⼦句继续往外抛
-
java 自定义异常
-
为什么要使⽤⾃定义异常
- 当前JDK内置的异常不满⾜需求,项⽬会出现特有异常
- ⾃定义异常可以让业务更清晰
-
如何进⾏⾃定义异常
-
异常都是继承⾃Exception类,所以我们要⾃定义的异常也需要继承这个基类。
-
//我们自定义一个NoException 异常类 public class NoException extends Exception { /** * 自定义异常 * */ private String msg; public NoException(){ super(); } public NoException( String msg){ //异常信息 super(msg); this.msg=msg; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } }
这里记录以下为啥还需要自定义一个msg
Exception 类中并没有定义 message属性 ,在Throwable类中定义了相关属性 ,但没有设置set方法
-
使用
-
public static void main(String[] args) { try { func(); } catch (NoException e) { System.out.println( e.getMsg()); } } public static void func()throws NoException{ throw new NoException("asdd"); }
-
- 对于不可查异常 和 error ,若使用try -catch 或者 throws 处理 ,编译不会出错 ,程序在运行时会自动抛出异常
异常屏蔽
- 什么是异常屏蔽
如果catch代码块中捕获了异常,但触发了新的异常,那么finally捕获并且重抛的异常会是后一个异常,这样原本的异常就会被忽略掉。
如果finally代码块中也抛出了异常,那么会是这个异常向上传递,try中的异常也就被“屏蔽”了。
这样的情况对bug调试很不利。
public class ExceptionShield {
public static void main(String[] args) {
testExceptionShield();
}
private static void testExceptionShield() {
try {
double a = 1 / 0;
System.out.println(a);
} catch (Exception e) {
int[] a = {1, 2};
System.out.println(a[2]);
} finally {
System.out.println("finally");
}
}
}
//运行结果
finally
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 2
at com.zzz.exception.ExceptionShield.testExceptionShield(ExceptionShield.java:15)
at com.zzz.exception.ExceptionShield.main(ExceptionShield.java:6)
可以看到,try内的原始错误应该为 java.lang.ArithmeticException: / by zero 异常,但却被catch内的新异常给“屏蔽”了。
-
解决方法 Suppressed()
-
java7 中 增加了addSuppressed方法把这些被抑制的方法记录下来。被抑制的异常会出现在抛出的异常的堆栈信息中,也可以通过getSuppressed方法来获取这些异常。这样做的好处是不会丢失任何异常,方便开发人员进行调试。
private static void testExceptionShield() { try { double a = 1 / 0; System.out.println(a); } catch (Exception e) { try { int[] a = {1, 2}; System.out.println(a[2]); } catch (Exception exception) { exception.addSuppressed(e); throw exception; //抛出 } } finally { System.out.println("finally"); } } //运行结果 Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 2 at com.zzz.exception.ExceptionShield.testExceptionShield(ExceptionShield.java:16) at com.zzz.exception.ExceptionShield.main(ExceptionShield.java:5) Suppressed: java.lang.ArithmeticException: / by zero at com.zzz.exception.ExceptionShield.testExceptionShield(ExceptionShield.java:11) ... 1 more
-
try - with - resources
- 作用
- Java7 中专门构造了这个try-with-resource的语法糖,它的主要目的是精简资源打开关闭的用法,
不用手动finally ,同 时它还会在字节码层面上自动使用Suppressed异常功能,
- 这里的resources可以是文件资源 ,也可以是创建的对象 :(所有实现了 java.lang.AutoCloseable 接口(其中,它包括实现了 java.io.Closeable 的所有对象),可以使用作为资源。)
- 语法
// try 后放资源,就可自动关闭
try(;;){ //多个资源以分号隔开
}catch(){
}
// eg:
public class Main2 {
public static void main(String[] args) {
try(ResourceSome some = new ResourceSome();
ResourceOther other = new ResourceOther()) {
some.doSome();
other.doOther();
} catch(Exception ex) {
ex.printStackTrace();
}
}
}
// 实现 AutoCloseable 接口
class ResourceSome implements AutoCloseable {
void doSome() {
System.out.println("do something");
}
@Override
public void close() throws Exception {
System.out.println("some resource is closed");
}
}
class ResourceOther implements AutoCloseable {
void doOther() {
System.out.println("do other things");
}
@Override
public void close() throws Exception {
System.out.println("other resource is closed");
}
}
- 资源关闭顺序 ,遵从 后开先闭原则
- try结束后自动调用的close方法,这个动作会早于finally和catch里调用的方法
- 不管是否出现异常,try()里的实例都会被调用close方法
- 凡是实现了AutoCloseable接口的类,在try()里声明该类实例的时候,在try结束后,close方法都会被调用
- try 块发生异常,然后自动调用 close 方法,如果 close 也发生异常,catch 块只会捕捉 try 块抛出的异常,close 方法的异常会在catch 中被压制,但是你可以在catch块中,用 Throwable.getSuppressed 方法来获取到压制异常的数组。
- try - with 结构中 ,catch 不能访问 try里声明的变量
- try-with-recourse 中,try 块中抛出的异常,在 e.getMessage() 可以获得,而调用 close() 方法抛出的异常在e.getSuppressed() 获得。
package Exception;
public class TryWith {
public static void main(String[] args) {
try( Resource r=new Resource()){
int a= 2/0;
} catch (Exception e) {
System.out.println(e.getMessage());
Throwable [] th = e.getSuppressed();
for (int i = 0; i < th.length; i++) {
System.out.println(th[i].getMessage());
}
}
}
}
class Resource implements AutoCloseable{
@Override
public void close() throws Exception {
System.out.println("资源关闭");
throw new ArrayIndexOutOfBoundsException("数组溢出!");
}
}
为啥要在 try 里声明变量呢? try 外面声明 就不可用 ;
防止声明的变量引用改变,导致资源释放不成功;
为此 ,可以加 final 修饰变量
jdk 7 和 8中
public static void main(String[] args) { final Resource r=new Resource(); try(Resource ri= r){ int a= 2/0; } catch (Exception e) { System.out.println(e.getMessage()); } }
jdk 9 以后 可以省略 final 和 try() 直接使用 变量引用
// 我用的 jdk 11 测试的 public static void main(String[] args) { final Resource r=new Resource(); Resource r1=new Resource(); try(r ; r1){ int a= 2/0; } catch (Exception e) { System.out.println(e.getMessage());
面试 try -catch -finally里加return ,throw 的问题
throw 和 return都会终止方法;
- finally覆盖catch
- 如果finally有return会覆盖catch里的throw,同样如果finally里有throw会覆盖catch里的return。
- 如果catch里和finally都有return, finally中的return会覆盖catch中的。throw也是如此。
- catch有return而finally没有:
- 当 try 中抛出异常且catch 中有 return 语句,finally 中没有 return 语句, java 先执行 catch 中非 return 语句,再执行 finally 语句,最后执行 catch 中 return 语句。
- try有return语句,后续还有return语句,分为以下三种情况:
- 如果finally中有return语句,则会将try中的return语句”覆盖“掉,直接执行finally中的return语句,得到返回值,这样便无法得到try之前保留好的返回值。
- 如果finally中没有return语句,也没有改变要返回值,则执行完finally中的语句后,会接着执行try中的return语句,返回之前保留的值。
- 如果finally中没有return语句,但是改变了要返回的值,这里有点类似与引用传递和值传递的区别,分以下两种情况,:
- 如果return的数据是基本数据类型或文本字符串,则在finally中对该基本数据的改变不起作用,try中的return语句依然会返回进入finally块之前保留的值。
- 如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。