【Java SE基础 五】Java异常处理机制

简介: 【Java SE基础 五】Java异常处理机制

在了解了面向对象的相关技术后,我们就开始正式合规的写Java代码了,但是实际上,在写代码的过程种会经常有一些错误抛出来,我们称这些运行时抛出的错误为异常,我们要做的就是捕获并处理。

异常定义

异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。要理解Java异常处理是如何工作的,你需要掌握以下三种类型的异常:

  • 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。非运行时异常
  • 运行时异常: 运行时异常是可能被程序员避免的异常。例如用户输入了非法数据RunTimeException运行时异常
  • 错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。错误

异常指不期而至的各种状况,如:文件找不到、网络连接失败、除0操作、非法参数等。异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程

综合日常的实战,我可以这么定义异常处理:异常处理通常和日志紧密配合,在可能出现问题的地方捕获系统抛出的异常然后打出对应的日志,方便系统稳定运行和程序员排查问题,搞明白了基础定位和作用之后,再来详细看看异常体系怎么运作。

异常分类

所有的异常类是从 java.lang.Exception 类继承的子类。Exception 类是 Throwable 类的子类。除了Exception类外Throwable还有一个子类Error 。

Throwable分成了两个不同的分支,一个分支是Error,它表示不希望被程序捕获或者是程序无法处理的错误。另一个分支是Exception,它表示用户程序可能捕捉的异常情况或者说是程序可以处理的异常。其中异常类Exception又分为运行时异常(RuntimeException)和非运行时异常

  • Error,错误类对象由 Java 虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。例如,Java虚拟机运行错误(Virtual MachineError),当JVM不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。处理不了
  • Exception:Exception异常分为两类,一类时运行时异常,一类是非运行时异常。
  • RuntimeException(运行时异常),该类型的异常自动为你所编写的程序定义ArrayIndexOutOfBoundsException(数组下标越界)、NullPointerException(空指针异常)、ArithmeticException(算术异常)、MissingResourceException(丢失资源)、ClassNotFoundException(找不到类)等异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生不处理,运行会报错
  • 非运行时异常,类型上属于Exception类及其子类,从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。不处理,编译不能通过

还有依据是否是受检查异常而做的一个分类:什么是检查异常呢?在正确的程序运行过程中,很容易出现的、情理可容的异常状况,在一定程度上这种异常的发生是可以预测的,并且一旦发生该种异常,就必须采取某种方式进行处理。

  • 检查异常:除了RuntimeException及其子类以外,其他的Exception类及其子类都属于检查异常,当程序中可能出现这类异常,要么使用try-catch语句进行捕获,要么用throws子句抛出,否则编译无法通过。
  • 不受检查异常:包括RuntimeException及其子类和Error。

总结而言,不受检查异常为编译器不要求强制处理的异常,检查异常则是编译器要求必须处置的异常

异常处理

上文我们定位到异常有哪些,而且确定不处理错误,不推荐处理运行时异常(因为运行时异常一般为逻辑错误,程序应该从逻辑角度尽可能避免这类异常的发生),必须处理非运行时异常,如何处理异常呢?Java的异常处理本质上是抛出异常捕获异常,有五个关键字来搭配使用进行异常处理:

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

接下来详细的了解下异常处理的语法

try-catch-finally

try-catch-finally为一个完整的异常捕获到处理的流程,一个完整的异常处理和捕获的范例代码如下:

try{
  // 程序代码
}catch(异常类型1 异常的变量名1){
  // 程序代码
}catch(异常类型2 异常的变量名2){
  // 程序代码
}finally{
  // 程序代码
}

以下为有无异常的执行流程:

在发现语句1出错,后边的语句不会执行,也就是说语句2不会被执行了,而是执行finally之后的 try-catch之外的其它语句

语法示例

try-catch-finally分别就检查异常和运行时异常举个例子:

public class Fileexception {
  public static void main(String[] args) {
    FileInputStream in = null;  //一定要定义在try外边,记住作用域是在大括号之间
    try {
      in = new FileInputStream("myfile.txt");
      int b;
      b = in.read();
      while (b != -1) {
        System.out.print((char) b);
        b = in.read();
      }
//一定要先写FileNotFoundException,因为FileNotFoundException是IOException的子类,因为写catch是先小后大,
//否则,捕获底层异常类的catch子句将可能会被屏蔽
    } catch (FileNotFoundException e) {   
      e.printStackTrace();
    } catch (IOException e) {
      System.out.println(e.getMessage());
    } finally {
      try {
        in.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }
}

以下为一个运行时算数异常,不推荐使用,要在逻辑执行时避免。

public class TestException {  
    public static void main(String[] args) {  
        int a = 1;  
        int b = 0;  
        try { // try监控区域               
            if (b == 0) throw new ArithmeticException(); // 通过throw语句抛出异常  
            System.out.println("a/b的值是:" + a / b);  
            System.out.println("this will not be printed!");
        }  
        catch (ArithmeticException e) { // catch捕捉异常  
            System.out.println("程序出现异常,变量b不能为0!");  
        }  
        System.out.println("程序正常结束。");  
    }  
}

嵌套try

try语句可以被嵌套。也就是说,一个try语句可以在另一个try块的内部。

class NestTry{
    public static void main(String[] args){
        try{
            int a = args.length;
            int b = 42 / a;
            System.out.println("a = "+ a);
            try{
                if(a == 1){
                a = a/(a-a);
                }
                if(a == 2){
                    int c[] = {1};
                    c[42] =99;
                }
            }catch(ArrayIndexOutOfBoundsException e){
                System.out.println("ArrayIndexOutOfBounds :"+e);   //数组越界异常
            }    
        }catch(ArithmeticException e){
            System.out.println("Divide by 0"+ e);    //除0异常
        }
    }
}

输出结果为

a=0:Divide by 0java.lang.ArithmeticException: / by zero
a=1:Divide by 0java.lang.ArithmeticException: / by zero
a=2:ArrayIndexOutOfBounds :java.lang.ArrayIndexOutOfBoundsException

每次进入try语句,异常的前后关系都会被推入堆栈。如果一个内部的try语句不含特殊异常的catch处理程序,堆栈将弹出,下一个try语句的catch处理程序将检查是否与之匹配。

  • a=0时,外面的try块将产生一个被0除的异常。
  • a=1时,嵌套的try块产生一个被0除的异常,由于内部的catch块不匹配这个异常,它将把异常传给外部的try块,在外部异常被处理。
  • a=2时,内部try块产生一个数组边界异常

整个过程将继续直到一个catch语句被匹配成功,或者是直到所有的嵌套try语句被检查完毕。如果没有catch语句匹配,Java运行时系统将处理这个异常。

try-catch-finally组合情况

我们都说这个组合来处理异常,那么这几个关键字都是必须的么?关键字组合有如下几种:

  1. try+catch后有没有finally无所谓,try+catch+finally可以使用;try+catch可以使用
  2. try必不可少,try+finally可以,try+catch可以
  3. 三个关键字不能单独使用任何一个即使是try如果不加catch也必须有finally(声明了异常就一定要处理,不管是在catch还是finally中)

总结就是必须有try+(catch,finally,catch+finally)三个组合中的一种

return的执行时机

我们知道不管有没有出现异常,finally块中代码都会执行,但是如果说在以上三种关键字所管辖的块内包含return,代码该按照何种顺序执行呢?

finally没有return

finally里没有return的场景,但try或catch里有,finally仍然会执行,因此在return返回时不是直接返回变量的值,而是复制一份,然后返回,因此,对于基本类型的,finally的改变没有影响,对于引用类型的就有影响了

package test;
/
 * @author 田茂林
 * @data 2017年9月6日 下午9:46:42
 */
public class TestFinally{
    //基本类型
  public static int testFinally1(){ 
    int result =1;
    try {
      result = 2;
      return result;
    } catch (Exception e) {
      return 0;
    }finally{
      result =3;
      System.out.println("execult Finally1");
    }
  }
     //引用类型
  public static StringBuffer testFinally2(){       
    StringBuffer s = new StringBuffer("Hello");
    try {
      return s;
    } catch (Exception e) {
      return null;
    }finally{
      s.append("world");
      System.out.println("execult Finally2");
    }
  }
  public static void main(String[] args) {
              int result1 = testFinally1();
              System.out.println(result1);
              StringBuffer result2 = testFinally2();
              System.out.println(result2);
  }
}

输出结果

execult Finally1
2
execult Finally2
Helloworld

对于finally块中没有return语句的情况,方法在返回之前会先将返回值保存在局部变量表中的某个slot中,然后执行finally块中的语句,之后再将保存在局部变量表中某个slot中的数据放入操作数栈的栈顶并进行返回,因此对于基本数据类型而言,若在finally块中改变其值,并不会影响最后return的值

finally里有return

finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。返回值是finally里的return返回的值

package test;
/**
 * @author 田茂林
 * @data 2017年9月6日 下午9:46:42
 */
public class TestFinally {
  @SuppressWarnings("finally")
  public static int testFinally() { // 基本类型
    int i = 1;
    try {
      ++i;
      return i;
    } catch (Exception e) {
    } finally {
      return i++; // 2
      // return ++i; //3
    }
  }
  public static void main(String[] args) {
    int result1 = testFinally();
    System.out.println(result1);
  }
}

如果是return i++返回i的值,return ++i返回的是++i的值,也就是永远返回当前值,对于finally块中包含了return语句的情况,则在try块中的return执行之前,会先goto到finally块中,而在goto之前并不会对try块中要返回的值进行保护,而是直接去执行finally块中的语句,并最终执行finally块中的return语句,而忽略try块中的return语句,因此最终返回的值是在finally块中改变之后的值。

throws和throw

如果一个方法没有捕获到一个检查性异常,那么该方法必须使用 throws 关键字来声明。throws 关键字放在方法签名的尾部。也可以使用 throw 关键字抛出一个异常,一个throws子句列举了一个方法可能引发的所有异常类型。

import java.io.*;
public class className
{
  public void deposit(double amount) throws RemoteException
  {
    // Method implementation
    throw new RemoteException();
  }
  //Remainder of class definition
}

throws异常抛出示例

下面是用throws关键字抛出异常的举例,异常从f2()抛出并层层上抛,直到main方法处理异常

public class ManangeException {
    //直到抛到main方法里,不要再main方法里写throws,main方法会打印堆栈信息,但最好在main方法里处理异常
    //main方法调用了f2()方法,并监控和捕获可能抛出的异常
  public static void main(String[] args) {  
         ManangeException m=new ManangeException();
         try {
      m.f2();
    } catch (IOException e) {
      System.out.println("没有找到该文件");
    }
  }
    //f()方法调用了f2()方法,并抛出可能的异常
  public void f2() throws IOException{  //一级一级往外抛
    f();
  }
  //f2()方法抛出可能的异常
  public void f() throws FileNotFoundException,IOException{   //不处理只抛出
    FileInputStream in = null; 
    in = new FileInputStream("myfile.txt");
    int b;
    b = in.read();
    while (b != -1) {
      System.out.print((char) b);
      b = in.read();
    }
  }

如果在main方法中也不处理并继续向上抛的话,可以看到如下的异常堆栈信息:

Exception in thread "main" java.io.FileNotFoundException: myfile.txt (系统找不到指定的文件。)
  at java.io.FileInputStream.open0(Native Method)
  at java.io.FileInputStream.open(FileInputStream.java:195)
  at java.io.FileInputStream.<init>(FileInputStream.java:138)
  at java.io.FileInputStream.<init>(FileInputStream.java:93)
  at packageB.Person.f(Person.java:24)
  at packageB.Person.f2(Person.java:19)
  at packageB.Person.main(Person.java:14)

throw异常抛出示例

除了对可能的异常抛出在方法上声明,还可以通过指定异常抛出直接手动抛出异常

class TestThrow{
      static void proc(){
            try{
                throw new NullPointerException("demo");
            }catch(NullPointerException e){
                System.out.println("Caught inside proc");
                throw e;
            }
        }
        public static void main(String [] args){
            try{
                proc();
            }catch(NullPointerException e){
                System.out.println("Recaught outside ");
            }
        }
}

返回结果如下:

Caught inside proc
Recaught outside

自定义异常

使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承Exception类即可。在程序中使用自定义异常类,大体可分为以下几个步骤:

  1. 创建自定义异常类,必须继承Exception类
  2. 在方法中通过throw关键字抛出异常对象。如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;
  3. 如果当前方法不处理,那么在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作。

自定义异常举例如下:

class MyException extends Exception {
  private int id;
  public MyException(String message, int id) {
    super(message);
    this.id = id;
  }
  public int getId() {
    return id;
  }
}
public class TestMyException {
    public static void main(String[] args) {
    TestMyException t=new TestMyException();
    t.m(1);
  }
     public void m(int i){
       try {
      regist(i);               //方法体里边不能再写方法,但是可以直接调用就好
    } catch (MyException e) {
      System.out.println("出现故障");
    }
     }
     public void regist(int num) throws MyException{
       if(num<0) 
           throw new MyException("不合法的人数", 1);    //这要是异常抛出,则不会打印下边的那句话,因为没有捕获处理
       System.out.println(num);
     }
}


相关文章
|
6天前
|
存储 Java 开发者
探索Java中的异常处理:从基础到高级
【6月更文挑战第1天】本文将深入探讨Java中的异常处理机制,从基本概念到高级应用。我们将通过实例和代码示例,详细解释如何捕获和处理异常,以及如何使用自定义异常来提高代码的健壮性和可读性。无论你是Java初学者还是有经验的开发者,这篇文章都将为你提供有价值的知识和技巧。
16 4
|
1天前
|
Java 程序员 数据库连接
Java中的异常处理:理解try-catch块的工作原理
本文深入探讨了Java编程语言中异常处理的核心机制——try-catch块。我们将通过具体示例,详细解释异常的产生、捕获和处理过程,以及如何有效地利用这一机制来提高代码的健壮性和可维护性。
|
2天前
|
Java
Java中的异常处理:深入理解try-catch-finally语句
【6月更文挑战第4天】在Java编程中,异常处理是一项重要的技能。本文将深入探讨try-catch-finally语句的工作原理,以及如何在Java程序中有效地使用它们来处理可能出现的错误和异常情况。我们将通过实例来演示如何捕获和处理异常,以及如何使用finally块来确保资源的清理和释放。
|
3天前
|
Java 数据库连接
Java中的异常处理:深入理解try-catch语句
【6月更文挑战第4天】在Java编程中,异常处理是一项重要的技能。本文将深入探讨Java中的异常处理机制,特别是try-catch语句的用法和原理。我们将通过实例来演示如何在代码中捕获和处理异常,以及如何利用finally块确保资源的正确释放。
7 1
|
3天前
|
SQL Java 编译器
29. 【Java教程】异常处理
29. 【Java教程】异常处理
14 3
|
6天前
|
存储 安全 Java
深入理解Java堆栈:机制、特性与应用
深入理解Java堆栈:机制、特性与应用
13 1
|
6天前
|
Java 关系型数据库 MySQL
【Java——SPI机制详解】
SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是 解耦。 当服务的提供者提供了一种接口的实现之后,需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的
|
6天前
|
缓存 安全 Java
【Java——反射机制详解】
RTTI(Run-Time Type Identification)运行时类型识别。在《Thinking in Java》一书第十四章中有提到,其作用是在运行时识别一个对象的类型和类的信息。主要有两种方式:一种是“传统的”RTTI,它假定我们在编译时已经知道了所有的类型;另一种是“反射”机制,它允许我们在运行时发现和使用类的信息。 反射就是把java类中的各种成分映射成一个个的Java对象 例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
|
6天前
|
Java 编译器 程序员
【Java ——异常机制详解】
try – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。 catch – 用于捕获异常。catch用来捕获try语句块中发生的异常。 finally – finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。 throw – 用于抛出异常。
|
6天前
|
Java 编译器 测试技术
【Java 基础 - 注解机制详细解释】
不能使用关键字extends来继承某个@interface,但注解在编译后,编译器会自动继承java.lang.annotation.Annotation接口. 虽然反编译后发现注解继承了Annotation接口,请记住,即使Java的接口可以实现多继承,但定义注解时依然无法使用extends关键字继承@interface。 区别于注解的继承,被注解的子类继承父类注解可以用@Inherited: 如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解。