【Java专题】详解Java中的异常

什么是异常?

异常:就是程序在运行过程中出现的不正常情况,这种不正常情况就叫做异常。
下面举例说明: (运行下面的代码,就会输出相关异常)
/**
* @author Jason
* @create 2020-07-05 15:16
*/
public class ExceptionTest01 {
  public static void main(String[] args) {
    int num1=10;
    int num2=0;
    int num3=num1/num2;
    System.out.println(num1+"/"+num2+"="+num3);
  }
}

控制台输出:

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at ExceptionTest02.main(ExceptionTest02.java:9)

异常的作用是什么?

提高程序的健壮性,通过输出的异常信息,让程序员判断并修改代码。
 

异常是以什么形式存在的呢?

异常是以类的形式存在的,也就是说,当我们的程序遇到异常的时候,就是去实例化那个异常类,创建一个异常对象。
举例说明:
/**
* @author Jason
* @create 2020-07-05 15:04
*/
public class ExceptionTest02 {
  public static void main(String[] args) {
    NumberFormatException nfe = new NumberFormatException("数字格式化异常");
    System.out.println(nfe);

    NullPointerException npe = new NullPointerException("空指针议程发生了什么?");
    System.out.println(npe);
  }
}

输出结果:

java.lang.NumberFormatException: 数字格式化异常
java.lang.NullPointerException: 空指针议程发生了什么?  
 

那么异常类的底层图是什么样子的呢?

 

 

 

Java的异常处理机制:

Exception的直接子类:编译时异常(要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理编译器报错,因此得名编译时异常。) RuntimeException:运行时异常。(在编写程序阶段程序员可以预先处理,也可以不管,都行。)

 

编译时异常和运行时异常的区别?

编译时异常:编译时异常不是在编译阶段发生的异常,而是表示程序在编写的时候预先对这种异常进行处理,如果不处理编译器就会报错,编译时异常发生的概率较高,编译时异常又称为受检异常,或受控异常。
运行时异常:在编写程序阶段可以选择处理,也可以选择处理,也可以选择不处理,运行时异常发生概率较低,运行时还有另外一个名字:未受检异常或非受控异常
注意: 所有异常都是发生在运行阶段
 

java异常的处理方式:

第一种方式:在方法声明的位置上,使用throws关键字,抛给上一级。 谁调用我,我就抛给谁。抛给上一级。
第二种方式:使用try..catch语句进行异常的捕捉。这件事发生了,谁也不知道,因为我给抓住了。异常到此为止,不在上抛
 
思考:
 异常发生之后,如果我选择了上抛,抛给了我的调用者,调用者需要对这个异常继续处理,那么调用者处理这个异常同样有两种处理方式。
一、继续上抛:throws
二、自己处理:try...catch
 
注意: Java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM,JVM知道这个异常发生,只有一个结果。终止java程序的执行。
 
编译时异常实例:
/**
* @author Jason
* @create 2020-07-05 22:00
* 编译时异常
*/
public class ExceptionTest03 {
  //第一种方法:选择继续上抛异常
  public static void main(String[] args) throws ClassNotFoundException {
    //doSome()上有编译时异常,我们在调用这个方法的时候必须对这个异常预先进行处理,否则报错
    //doSome() //千万不可用不处理异常
    doSome();
  }
  
  //第二种方法:自己把异常拦截下来
  /*public static void main(String[] args) {
    //doSome()上有编译时异常,我们在调用这个方法的时候必须对这个异常预先进行处理,否则报错
    //doSome() //千万不可用不处理异常
    try {
      doSome();
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
  }*/

  public static void doSome()throws ClassNotFoundException{
    System.out.println("doSome!!");
  }
}

1、throws上抛给上一级处理实例:

/**
* @author Jason
* @create 2020-07-06 8:45
* 编译时异常处理:上抛形式处理
*/
public class ExceptionTest04 {
  public static void main(String[] args) {
    System.out.println("main begin");
    //这里就不建议继续上抛给main方法了,因为main方法会继续上抛给jvm,这里就只会对异常选择终止程序
    try {
      n1();
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    }
    System.out.println("main over");
  }


  public static void n1() throws FileNotFoundException {
    System.out.println("n1 begin");
    //继续上抛
    n2();
    System.out.println("n1 over");
  }


  public static void n2() throws FileNotFoundException {
    System.out.println("n2 begin");
    //此时的异常并没有消失,这里也需要我们对异常进一步处理,这里的处理方式很多:
    //继续上抛:这里可以选择IOException或Exception,当然这里也可以选择多个异常处理
    n3();
    System.out.println("n2 over");
  }


  public static void n3() throws FileNotFoundException {
    System.out.println("n3 begin");
    //分析保存原因:这里调用了一个构造方法,这个构造方法的底层如下:这个构造方法的声明位置上有一个编译时异常FileNotFoundException
    /*public FileInputStream(String name) throws FileNotFoundException {
      this(name != null ? new File(name) : null);
    }*/
    //此时我们有两种处理方法:自己处理异常或者选择上抛给调用者处理(此处我们选择上抛给调用者)
    new FileInputStream("文件路径...");
    System.out.println("n3 over");
  }
}

控制台输出:

main begin
n1 begin
n2 begin
n3 begin
main over
java.io.FileNotFoundException: 文件路径... (系统找不到指定的文件。)
    at java.io.FileInputStream.open(Native Method)
    at java.io.FileInputStream.<init>(FileInputStream.java:138)
    at java.io.FileInputStream.<init>(FileInputStream.java:93)
    at ExceptionTest04.n3(ExceptionTest04.java:44)
    at ExceptionTest04.n2(ExceptionTest04.java:33)
    at ExceptionTest04.n1(ExceptionTest04.java:25)
    at ExceptionTest04.main(ExceptionTest04.java:15)

注意:采用上报方式抛异常,此方法后续的代码不会执行,另外try语句块中某一行出现异常,该行后面的代码不会执行,try...catch捕捉异常后,后续的代码可以执行

 
2、try...catch捕获异常:
/**
* @author Jason
* @create 2020-07-06 9:34
* try...catch形式捕获异常深入学习
*/
public class ExceptionTest05 {
  public static void main(String[] args) {
    //第一种:
    /*try {
      FileInputStream fileInputStream = new FileInputStream("文件路径...");
      System.out.println("上面出现异常,这里无法执行...");
      //这里可以是具体异常的catch
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    }*/


    //第二种:
    /*try {
      FileInputStream fileInputStream = new FileInputStream("文件路径...");
      System.out.println("上面出现异常,这里无法执行...");
      //这里可以是直接父类异常的catch
    } catch (IOException e) { //多态:IOException e = new FileNotFoundException();
      e.printStackTrace();
    }*/


    //第三种:
    /*try {
      FileInputStream fileInputStream = new FileInputStream("文件路径...");
      System.out.println("上面出现异常,这里无法执行...");
      //这里可以是父类异常的catch
    } catch (Exception e) { //多态:Exception e = new FileNotFoundException();
      e.printStackTrace();
    }*/


    //JDK8新特性
    try {
      FileInputStream fileInputStream = new FileInputStream("文件路径...");
      System.out.println(5/0);
    } catch (FileNotFoundException | ArithmeticException |NullPointerException e) {
      e.printStackTrace();
    }
  }
}

3、我们在开发中有上抛异常、有try...catch捕获那么到底选择哪个呢?

希望调用者来处理,选择throws上报,其他情况使用try...catch方式捕获。
 

getMessage方法和printStackTrace方法

/**
* @author Jason
* @create 2020-07-06 10:19
* getMessage方法和printStackTrace方法
*/
public class ExceptionTest06 {
  public static void main(String[] args) {
    //getMessage方法:获取异常简单的描述信息
    NullPointerException nullPointerException = new NullPointerException("空指针异常...");
    String message = nullPointerException.getMessage();
    System.out.println(message);


    //printStackTrace方法:打印异常追踪的堆栈信息
    try {
      m1();
    } catch (FileNotFoundException e) {
      e.getMessage();
      e.printStackTrace();
    }
  }


  public static void m1() throws FileNotFoundException {
    m2();
  }


  public static void m2() throws FileNotFoundException {
    m3();
  }


  public static void m3() throws FileNotFoundException {
    FileInputStream fileInputStream = new FileInputStream("文件路径...");
  }
}

控制台打印信息:

空指针异常...
 
java.io.FileNotFoundException: 文件路径... (系统找不到指定的文件。)
    at java.io.FileInputStream.open(Native Method)
    at java.io.FileInputStream.<init>(FileInputStream.java:138)
    at java.io.FileInputStream.<init>(FileInputStream.java:93)
    at ExceptionTest06.m3(ExceptionTest06.java:34)
    at ExceptionTest06.m2(ExceptionTest06.java:30)
    at ExceptionTest06.m1(ExceptionTest06.java:26)
    at ExceptionTest06.main(ExceptionTest06.java:18)

注意:异常信息需要从上往下看,很多使用后面的异常是因为前面引起的。

 

try...catch中的finally子句

如果没有finally语句,前面的语句一旦出现了异常,后面的语句就不会被执行,如果是需要被关闭的流呢? 但是流又必须关闭,因为它是需要占用资源的,所以这里就体会到了finally的关键。通过把流放到finally中,这样就可以确保他一定被执行到,也就不用担心他的关闭问题了。
下面看看这个简单的实例代码:
/**
* @author Jason
* @create 2020-07-06 10:39
* try...catch中的finally子句
*/
public class ExceptionTest07 {
  public static void main(String[] args) {
    FileInputStream fileInputStream = null;
    try {
      fileInputStream = new FileInputStream("F:\\学习资料\\JavaSE-资料\\001-JavaSE课堂笔记+思维导图\\02-JavaSE零基础每日复习与笔记\\day18课堂笔记.txt");
      String str = null;
      str.toString();
      System.out.println("Hello Exception!");
      //注意:此处不会被执行,由于上面的代码行出现了异常,但是流又必须关闭,因为它是需要占用资源的,所以这里就体会到了finally的关键
      //fileInputStream.close();
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    }catch (IOException e){
      e.printStackTrace();
    }catch (NullPointerException e){
      e.printStackTrace();
    }finally {
      //将流放到finally中关闭是最保险的,因为finally中的代码无论如何都是会被执行到的
      if (fileInputStream != null){ //避免空指针异常
        try {
          System.out.println("Hello Jason!");
          fileInputStream.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
    System.out.println("Hello World!");
  }
}

控制台输出结果:

java.lang.NullPointerException
    at ExceptionTest07.main(ExceptionTest07.java:16)
Hello Jason!
Hello World!

final  finally  finalize有什么区别?

1、final是关键字,表示最终的,不变的 ,如:final int i = 10;
2、finally关键字和try联合使用,经常在异常处理机制中使用,finally语句中的代码一定会执行
3、finalize是object类中的一个方法,作为方法名出现,finalize是标识符

自定义异常

1、为何需要自定义异常?
JDK中的异常不够用,在实际开发中,有很多业务出现异常后JDK中没有这些和业务相关的异常,所有这个时候就需要我们自己自定义异常。
2、如何自定义异常呢?
第一步:编写一个类继承Exception类或RuntimeException
第二步:提供两个构造方法,一个是无参数的,一个是有String类型参数的构造方法
3、自定义异常实例:
/**
* @author Jason
* @create 2020-07-06 15:26
* 自定义编译时异常
*/
public class ExceptionTest09 {
  public static void main(String[] args) {
    MyException e = new MyException("用户名不能为空!");
    String message = e.getMessage();
    System.out.println(message);
    e.printStackTrace();
  }
}

/**
* @author Jason
* @create 2020-07-06 15:29
* 自定义编译时异常
*/
public class MyException extends Exception{
    public MyException(){}
    public MyException(String s) {
      super(s);
    }
}

控制台输出结果:

用户名不能为空!
MyException: 用户名不能为空!
    at ExceptionTest09.main(ExceptionTest09.java:7)

自定义异常的使用:

/**
* @author Jason
* @create 2020-07-06 15:53
*/
public class ExceptionTest10 {
  public static void main(String[] args) {


    // 创建栈对象
    MyStack stack = new MyStack();


    // 压栈
    try {
      stack.push(new Object());
      stack.push(new Object());
      stack.push(new Object());
      stack.push(new Object());
      stack.push(new Object());
      stack.push(new Object());
      stack.push(new Object());
      stack.push(new Object());
      stack.push(new Object());
      stack.push(new Object());
      // 这里栈满了
      stack.push(new Object());
    } catch (MyStackOperationException e) {
      // 输出异常的简单信息。
      System.out.println(e.getMessage());
    }


    // 弹栈
    try {
      stack.pop();
      stack.pop();
      stack.pop();
      stack.pop();
      stack.pop();
      stack.pop();
      stack.pop();
      stack.pop();
      stack.pop();
      stack.pop();
      // 弹栈失败
      stack.pop();
    } catch (MyStackOperationException e) {
      System.out.println(e.getMessage());
    }
  }
}

/**
* @author Jason
* @create 2020-07-06 15:50
*/
public class MyStack {
  private Object[] elements;
  private int index;

  /**
   * 无参数构造方法。默认初始化栈容量10.
   */
  public MyStack() {
    // 一维数组动态初始化
    // 默认初始化容量是10.
    this.elements = new Object[10];
    // 给index初始化
    this.index = -1;
  }


  /**
   * 压栈的方法
   * @param obj 被压入的元素
   */
  public void push(Object obj) throws MyStackOperationException {
    if(index >= elements.length - 1){
      throw new MyStackOperationException("压栈失败,栈已满!");
    }
    // 程序能够走到这里,说明栈没满
    // 向栈中加1个元素,栈帧向上移动一个位置。
    index++;
    elements[index] = obj;
    // 在声明一次:所有的System.out.println()方法执行时,如果输出引用的话,自动调用引用的toString()方法。
    System.out.println("压栈" + obj + "元素成功,栈帧指向" + index);
  }


  /**
   * 弹栈的方法,从数组中往外取元素。每取出一个元素,栈帧向下移动一位。
   * @return
   */
  public void pop() throws MyStackOperationException {
    if(index < 0){
      throw new MyStackOperationException("弹栈失败,栈已空!");
    }
    // 程序能够执行到此处说明栈没有空。
    System.out.print("弹栈" + elements[index] + "元素成功,");
    // 栈帧向下移动一位。
    index--;
    System.out.println("栈帧指向" + index);
  }

  // set和get也许用不上,但是你必须写上,这是规矩。你使用IDEA生成就行了。
  // 封装:第一步:属性私有化,第二步:对外提供set和get方法。
  public Object[] getElements() {
    return elements;
  }

  public void setElements(Object[] elements) {
    this.elements = elements;
  }

  public int getIndex() {
    return index;
  }

  public void setIndex(int index) {
    this.index = index;
  }
}

/**
* @author Jason
* @create 2020-07-06 15:52
*/
public class MyStackOperationException extends Exception{ // 编译时异常!
  public MyStackOperationException(){
  }

  public MyStackOperationException(String s){
    super(s);
  }
}

 

 
注意: 重写之后的方法不能比重写之前的方法抛出更多的异常,可以更少
 
 
 
 
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

技术蜗牛-阿春

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值