【Java】异常

目录

 

1.异常的背景

1.1初识异常

1.2 防御式编程

1.3 异常的好处

2.异常的基本用法

2.1 捕获异常

2.2 异常处理流程

2.3 抛出异常

2.4 异常说明

2.5 关于finally的注意事项

3.Java异常体系

4.自定义异常类


1.异常的背景

1.1初识异常

我们平常接触到的异常:

1)除以0

System.out.println(10/0);

结果为:Exception in thread "main" java.lang.ArithmeticException: / by zero

2)数组下标越界

int [] arr={1,2,3};

System.out.println(arr[100]);

结果为:Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100

3)访问null对象

public class Test {

   public int num = 10;

   public static void main(String[] args) {

       Test t = null;      

 System.out.println(t.num);   } }

结果为: Exception in thread "main" java.lang.NullPointerException

所谓异常就是指程序在运行时出现错误时通知调用者的一种机制

关键字运行时指:

有些错误是这样的,例如将System.out.println拼写错了,此时编译过程中就会出错,这是“编译期”错误。

而运行时是指程序已经编译通过得到class文件了,再由JVM执行过程中出现的错误。

异常是有很多的,不同种类的异常具有不同的意义也有不同的处理方式。

1.2 防御式编程

错误在代码中是客观存在的,因此我们要让程序出现问题及时通知程序员,主要有两种主要方式:

  • LBYL:(Look Before You Leap )在操作之前就做充分的检查
  • EAFP:It's Easier to Ask Forgiveness than Permission. “事后获取原谅比事前获取许可更容易”,也就是先操作,遇到问题再处理

异常的核心思想就是EAFP

1.3 异常的好处

使用LRYL,正常流程和错误处理代码混在一起,代码整体显得比较混乱,二使用EAFP,正常流程和错误流程是分离开的,更容易理解代码。

2.异常的基本用法

2.1 捕获异常

基本语法:

try{

有可能出现异常的语句;

}[catch (异常类型 异常对象){

}……]

[finally{

异常的出口

}]

  • try代码中存放的是可能出现异常的代码
  • catch代码块中存放的是出现异常后的处理行为
  • finally代码中的代码用于处理善后工作,会在最后执行
  • 其中catch和finally都可以根据情况选择加或者不加

代码1:不处理异常

int [] arr={1,2,3};
System.out.println("before");
System.out.println(arr[100]);
System.out.println("after");

结果为:

我们一旦发现异常,程序就终止了,after没有正确输出

代码2:使用try catch 后的程序执行过程

int [] arr={1,2,3};
try{
    System.out.println("before");
    System.out.println(arr[100]);
    System.out.println("after");
}catch(ArrayIndexOutOfBoundsException e){
    //打印出现异常的调用栈
    e.printStackTrace();
}
System.out.println("after try catch");

结果为:

发现:一旦try中出现异常,那么try代码块中的程序就不会继续执行,而是交给catch中的代码来执行,catch执行完毕会继续往下执行。

关于异常的处理方式:

异常的种类有很多,我们要根据不同的业务场景来决定。

  • 对于比较严重的问题(例如和钱相关的场景),应该直接让程序崩溃,防止造成更严重的后果。
  • 对于不太严重的问(大多数场景),可以记录错误日志,并通过监控报警程序及时通知程序员
  • 对于可能会恢复的问题(和网络相关的场景),可以尝试进行重试

在我们当前的代码中采取的是经过简化的第二种方式,我们记录的错误日志时出现异常的方法调用信息,能很快速的让我们找到出现异常的位置,以后我们会采取更完备的方式来记录异常信息。

关于“调用栈”:

方法之间是存在相互调用关系的,这种调用关系我们可以用“调用栈”来描述,在JVM中有一块内存空间称为“虚拟机栈”专门存储方法之间的调用关系,当代码中出现异常的时候,我们就可以使用e.printStackTrace();方式查看出现异常代码的调用栈。

代码3:catch只能处理对应种类的异常

int [] arr={1,2,3};
try{
    System.out.println("before");
    arr=null;
    System.out.println(arr[100]);
    System.out.println("after");
}catch(ArrayIndexOutOfBoundsException e){
    //打印出现异常的调用栈
    e.printStackTrace();
}
System.out.println("after try catch");

结果为:

此时,catch语句不能捕获到刚才的空指针异常,因为异常类型不匹配

代码4:catch可以有多个

int [] arr={1,2,3};
try{
    System.out.println("before");
    arr=null;
    System.out.println(arr[100]);
    System.out.println("after");
}catch(ArrayIndexOutOfBoundsException e){
    //打印出现异常的调用栈
    System.out.println("数组下标越界异常");
    e.printStackTrace();
}catch (NullPointerException e){
    System.out.println("空指针异常");
    e.printStackTrace();
}
System.out.println("after try catch");

结果为:

 

一段代码可能会抛出多种不同的异常,不同的异常有着不同的处理方式,因此可以搭配多个代码块,如果多个异常的处理方式是完全相同的,也可以写成:

catch (ArrayIndexOutOfBoundsException | NullPointerException e) {

……

}

Exception类是所有异常类的父类,因此可用这个类型表示捕获所有异常(但是不建议使用)

备注:catch进行类型匹配的时候,不光会匹配相同的异常对象,也会捕捉目标异常类型的子类对象

代码5:finally表示最后的善后作用,例如释放资源

代码可见上面所示代码

注:无论是否存在异常,finally中的代码一定都会执行,保证最终一定会执行的Scanner的close方法。

代码6:使用try负责回收资源

刚才的代码可以有一种等价写法,将Scanner对象在try的()中创建,就能保证在try执行完毕后自动调用Scanner的close方法。

try(Scanner sc=new Scanner(System.in)){
    int num=sc.nextInt();
    System.out.println("num="+num);
}catch(Exception e){
    e.printStackTrace();
}

2.2 异常处理流程

程序先执行try中的代码;

如果try中的代码出现异常,就会结束try中的代码,看和catch中的异常类型是否匹配;

如果找到匹配的异常类型,就会执行catch中的代码;

如果没有找到匹配的异常类型,就会将异常向上传递到上层调用者;

无论是否找到匹配的异常类型,finally中的代码都会被执行到(在该方法结束之前执行);

如果上层调用者也没有处理的了异常,就继续向上传递;

一直到main方法也没有合适的代码处理异常,就会交给JVM来进行处理,此时程序就会异常终止。

2.3 抛出异常

除了Java内置的类会抛出异常一些异常之外,也可以手动抛出某个异常,使用throw关键字完成这个操作。

public static void main(String[] args) {  
  System.out.println(divide(10, 0)); 
  }
 
public static int divide(int x, int y) {  
  if (y == 0) {      
    throw new ArithmeticException("抛出除 0 异常");  
   }    
      return x / y;
}

结果为:

在这个代码中,我们可以根据实际情况来抛出需要的异常,在构造异常对象同时可以指定一些描述信息。

2.4 异常说明

我们在处理异常的时候,通常希望知道这段代码中究竟会出现那些可能的一次异常。

我们可以使用throws关键字,把可能抛出的异常显示的标注在方法定义的位置,从而提醒调用者要注意捕获这些异常。

public static int divide(int x,int y)throws ArithmeticException{
    if(y==0){
        throw new ArithmeticException("抛出除0异常");
    }
    return x/y;
}

2.5 关于finally的注意事项

finally中的代码保证一定会执行到,但这也会带来一些麻烦。

public static void main(String[] args) {
     System.out.println(func());
}
public static int func(){
    try{
        return 10;
    }finally {
        return 20;
    }
}

注意:

finally执行的时机是在方法返回之前(try或者catch中如果有return会在这个return之前执行finally),但是如果finally中也存在return语句,那么就会执行finally中的return,从而不会执行到try中原有的return。

一般我们不建议在finally中写return(被编译器当做一个警告)

3.Java异常体系

Java内置了丰富的异常体系,用来表示不同情况的异常

下图表示Java内置的异常类之间的继承关系:

  • 顶层类 Throwable派生出两个重要的子类,Error和Exception
  • 其中Error指的是Java运行时内部错误和资源耗尽错误,应用程序不抛出此类异常,这种内部错误一旦出现,除了告知用户并使用程序终止之外,再无能无力,这种情况很少出现。
  • Exception是我们所使用的异常类的父类
  • 其中Exception有一个子类称为RuntimeException。这里又派生出很多我们常见的异常类

Java语言规范将派生出Error类或RuntimeException类的所有异常称为非受查异常,所有的其他异常称为受查异常。

如果一段代码可能抛出受查异常,那么必须显示进行处理

public static void main(String[] args) {
System.out.println(readFile());
}
public static String readFile() {
// 尝试打开文件, 并读其中的一行.
File file = new File("d:/test.txt");
// 使用文件对象构造 Scanner 对象.
Scanner sc = new Scanner(file);
return sc.nextLine();
}
// 编译出错
Error:(13, 22) java: 未报告的异常错误java.io.FileNotFoundException; 必须对其进行捕获或声明以便 抛出

 

查看Scanner的构造方法可以发现,存在FileNoFoundException这样的异常说明。

如FileNoFoundException这样的异常就是受查异常,如果不显示处理,编译无法通过

显示处理的方式有两种:

方法1:使用try catch 包裹起来

public static void main(String[] args) {
System.out.println(readFile());
}
public static String readFile() {
File file = new File("d:/test.txt");
Scanner sc = null;
try {
sc = new Scanner(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return sc.nextLine();
}

方法2:在方法上加上异常说明,相当于将处理动作交给上级调用者

public static void main(String[] args) {
try {
System.out.println(readFile());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public static String readFile() throws FileNotFoundException {
File file = new File("d:/test.txt");
Scanner sc = new Scanner(file);
return sc.nextLine();
}

小提示:使用IDEA中的alt+enter能够快速修正代码

4.自定义异常类

Java中虽然已经内置了丰富的异常类,但是我们实际场景中可能还有一些情况需要我们对异常类进行扩展,创建符合我们实际情况的异常。

注意事项:

  • 自定义异常通常继承自Exception或者RuntimeException
  • 继承自Exception的异常默认是受查异常
  • 继承自RuntimeException的异常默认是非受查异常。

 

 

 

 

发布了58 篇原创文章 · 获赞 8 · 访问量 9202
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 创作都市 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览