Java学习——异常处理详解

606 阅读12分钟

1、区分异常与错误

2、异常的原因

3、异常的分类

4、异常体系

5、Java内置非检查性异常

6、检查性异常类

7、异常方法

8、try-catch-finally 捕获异常

9、try-catch 实例

10、throws/throw 抛出异常

11、throw 和 throws 的区别

12、自定义异常

13、良好的编码习惯

1、区分异常与错误

并不是所有的错误都是异常。

比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error;如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛出 java.lang.ArithmeticException异常

在java中,阻止当前方法或作用域的情况,称之为异常。

2、异常的原因

  • 用户输入了非法数据。
  • 要打开的文件不存在。
  • 网络通信时连接中断,或者JVM内存溢出。

3、异常的分类

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

4、异常体系

  1. Java中的所有不正常类都继承于Throwable类。Throwable主要包括两个大类,一个是Error类,另一个是Exception类;

  1. Error类中又包括虚拟机错误线程死锁,一旦Error出现了,程序就彻底的挂了,被称为程序终结者

  1. Exception类,也就是通常所说的“异常”。主要指编码、环境、用户操作输入出现问题,Exception主要包括两大类,非检查异常(RuntimeException,此类异常虚拟机会自动抛出)和检查异常(其他的一些异常,此类异常需要手动添加捕获语句来处理,说以叫检查异常)

  1. RuntimeException异常主要包括以下四种异常(其实还有很多其他异常,这里不一一列出):空指针异常数组下标越界异常类型转换异常算术异常。RuntimeException异常会由java虚拟机自动抛出并自动捕获(就算我们没写异常捕获语句运行时也会抛出错误!!),此类异常的出现绝大数情况是代码本身有问题应该从逻辑上去解决并改进代码。

  1. 检查异常,引起该异常的原因多种多样,比如说文件不存在、或者是连接错误等等。跟它的“兄弟”RuntimeException运行异常不同,该异常我们必须手动在代码里添加捕获语句来处理该异常,这也是我们学习java异常语句中主要处理的异常对象

5、Java内置非检查性异常

异常 描述
ArithmeticException 当出现异常的运算条件时,抛出此异常。例如,一个整数"除以零"时,抛出此类的一个实例。
ArrayIndexOutOfBoundsException 用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。
ArrayStoreException 试图将错误类型的对象存储到一个对象数组时抛出的异常。
ClassCastException 当试图将对象强制转换为不是实例的子类时,抛出该异常。
IllegalArgumentException 抛出的异常表明向方法传递了一个不合法或不正确的参数。
IllegalMonitorStateException 抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。
IllegalStateException 在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。
IllegalThreadStateException 线程没有处于请求操作所要求的适当状态时抛出的异常。
IndexOutOfBoundsException 指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
NegativeArraySizeException 如果应用程序试图创建大小为负的数组,则抛出该异常。
NullPointerException 当应用程序试图在需要对象的地方使用 null 时,抛出该异常
NumberFormatException 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
SecurityException 由安全管理器抛出的异常,指示存在安全侵犯。
StringIndexOutOfBoundsException 此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。
UnsupportedOperationException 当不支持请求的操作时,抛出该异常。

6、检查性异常类

异常 描述
ClassNotFoundException 应用程序试图加载类时,找不到相应的类,抛出该异常。
CloneNotSupportedException 当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。
IllegalAccessException 拒绝访问一个类的时候,抛出该异常。
InstantiationException 当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。
InterruptedException 一个线程被另一个线程中断,抛出该异常。
NoSuchFieldException 请求的变量不存在
NoSuchMethodException 请求的方法不存在

7、异常方法

下面的列表是 Throwable 类的主要方法:

方法 说明
public String getMessage() 返回关于发生的异常的详细信息。这个消息在Throwable 类的构造函数中初始化了。
public Throwable getCause() 返回一个Throwable 对象代表异常原因。
public String toString() 使用getMessage()的结果返回类的串级名字。
public void printStackTrace() 打印toString()结果和栈层次到System.err,即错误输出流。
public StackTraceElement [] getStackTrace() 返回一个包含堆栈层次的数组。下标为0的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底。
public Throwable fillInStackTrace() 用当前的调用栈层次填充Throwable 对象栈层次,添加到栈层次任何先前信息中。

8、try-catch-finally 捕获异常

(1)try块:负责捕获异常,一旦try中发现异常,程序的控制权将被移交给catch块中的异常处理程序。

try语句块不可以独立存在,必须与 catch 或者 finally 块同存。

(2)catch块:如何处理?比如发出警告:提示、检查配置、网络连接,记录错误等。执行完catch块之后程序跳出catch块,继续执行后面的代码。

编写catch块的注意事项:多个catch块处理的异常类,要按照先catch子类后catch父类的处理方式,因为会【就近处理】异常。

(3)finally:最终执行的代码,用于关闭和释放资源。

try{
//一些会抛出的异常
}catch(Exception e){
//第一个catch
//处理该异常的代码块
}catch(Exception e){
//第二个catch,可以有多个catch
//处理该异常的代码块
}finally{
//最终要执行的代码
} 

当异常出现时,程序将终止执行,交由异常处理程序(抛出提醒或记录日志等),异常代码块外代码正常执行。 try会抛出很多种类型的异常,由多个catch块捕获多钟错误。

多重异常处理代码块顺序问题:先子类再父类(顺序不对编译器会提醒错误),finally语句块处理最终将要执行的代码。

9、try-catch 实例

实例一:

package com.hysum.test;

public class TryCatchTest {
    /**
     * divider:除数
     * result:结果
     * try-catch捕获while循环
     * 每次循环,divider减一,result=result+100/divider
     * 如果:捕获异常,打印输出“异常抛出了”,返回-1
     * 否则:返回result
     * @return
     */
    public int test1(){
        int divider=10;
        int result=100;
        try{
            while(divider>-1){
                divider--;
                result=result+100/divider;//此处divider为0时就会抛出异常,进入catch,return的值就变为了-1
            }
            return result;
        }catch(Exception e){
            e.printStackTrace();
            System.out.println("异常抛出了!!");
            return -1;
        }
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        TryCatchTest t1=new TryCatchTest();
        System.out.println("test1方法执行完毕!result的值为:"+t1.test1());
    }
    
}

运行结果:

结果分析:结果中的红色字抛出的异常信息是由e.printStackTrace()来输出的,java.lang.ArithmeticException: / by zero说明了这里我们抛出的异常类型是算数异常,后面跟着原因:by zero 表示由0造成的算数异常,下面两行at表明了造成此异常的代码具体位置。

实例二:

下面的例子中声明有两个元素的一个数组,当代码试图访问数组的第三个元素的时候就会抛出一个异常。

ExcepTest.java 文件代码:

// 文件名 : ExcepTest.java
import java.io.*;
public class ExcepTest{
 
   public static void main(String args[]){
      try{
         int a[] = new int[2];
         System.out.println("Access element three :" + a[3]);
      }catch(ArrayIndexOutOfBoundsException e){//捕获固定异常
         System.out.println("Exception thrown  :" + e);
      }
      System.out.println("Out of the block");
   }
}

以上代码编译运行输出结果如下:

Exception thrown  :java.lang.ArrayIndexOutOfBoundsException: 3
Out of the block

实例三:

多重捕获块 的语法如下所示:

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

如果保护代码中发生异常,异常被抛给第一个 catch 块。

如果抛出异常的数据类型与 ExceptionType1 匹配,它在这里就会被捕获。

如果不匹配,它会被传递给第二个 catch 块。

如此,直到异常被捕获或者通过所有的 catch 块。

例子:

try {
    file = new FileInputStream(fileName);
    x = (byte) file.read();
} catch(FileNotFoundException f) { // Not valid!无效
    f.printStackTrace();
    return -1;
} catch(IOException i) {
    i.printStackTrace();
    return -1;
}

10、throws/throw 抛出异常

throw ---- 将产生的异常抛出,是抛出异常的一个动作
throws --- 声明将要抛出何种类型的异常(声明)。 调用者必须做出处理(捕获或继续抛出)

① throw:

一般会用于程序出现某种逻辑时程序员主动抛出某种特定类型的异常。如:

public static void main(String[] args) { 
    String s = "abc"; 
    if(s.equals("abc")) { 
      throw new NumberFormatException(); 
    } else { 
      System.out.println(s); 
    } 
    //function(); 
}

运行结果:

Exception in thread "main" java.lang.NumberFormatException
at test.ExceptionTest.main(ExceptionTest.java:67)

② throws:

语法格式:

 public void 方法名(参数列表)throws 异常列表{
    //调用会抛出异常的方法或者:
    throw new Exception();
 }

当某个方法可能会抛出某种异常时用throws 声明可能抛出的异常,然后交给上层调用它的方法程序处理。如:

public static void function() throws NumberFormatException{ //声明可能抛出的异常
    String s = "abc"; 
    System.out.println(Double.parseDouble(s)); 
  } 
    
  public static void main(String[] args) { 
    try { 
      function(); 
    } catch (NumberFormatException e) { 
      System.err.println("非数据类型不能转换。"); 
      //e.printStackTrace(); 
    } 
}

11、throw 和 throws 的区别

  • throws可以单独使用,但throw不能,
    throw要么和try-catch-finally语句配套使用,要么与throws配套使用。但throws可以单独使用,然后再由处理异常的方法捕获。

  • throws语句用在方法声明后面,表示再抛出异常,由调用这个方法的上一级方法中的语句来处理,必须做出处理(捕获或继续声明)
    throw语句用在方法体内,表示抛出异常,由方法体内的语句处理。
    简单说,throws出现在方法函数头;而throw出现在函数体。

  • throws表示出现异常的一种可能性,并不一定会发生这些异常;
    throw则是抛出了异常,执行throw则一定抛出了某种异常对象。

举例子:

throws e1,e2,e3只是告诉程序这个方法可能会抛出这些异常,方法的调用者可能要处理这些异常,而这些异常e1,e2,e3可能是该函数体产生的。 throw则是明确了这个地方要抛出这个异常。如:

void doA(int a) throws (Exception1,Exception2,Exception3){
      try{
         ......
 
      }catch(Exception1 e){
       throw e;
      }catch(Exception2 e){
       System.out.println("出错了!");
      }
      if(a!=b)
       throw new Exception3("自定义异常");
}

分析:

1.代码块中可能会产生3个异常,(Exception1,Exception2,Exception3)

2.如果产生Exception1异常,则捕获之后再抛出,由该方法(doA)的调用者去处理。

3.如果产生Exception2异常,则该方法自己处理了(即System.out.println("出错了!");)。所以该方法就不会再向外抛出Exception2异常了,void doA() throws Exception1,Exception3 里面的Exception2也就不用写了。因为已经用try-catch语句捕获并处理了。

4.Exception3异常是该方法的某段逻辑出错,程序员自己做了处理,在该段逻辑错误的情况下抛出异常Exception3,则该方法的调用者也要处理此异常。这里用到了自定义异常,该异常下面会由解释。

12、自定义异常

在 Java 中你可以自定义异常:

  • 所有异常都必须是 Throwable 的子类。
  • 如果希望写一个检查性异常类,则需要继承 Exception 类。
  • 如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。
  • 举例:class MyException extends Exception{ }

举例:

package com.hysum.test;

public class MyException extends Exception {//自定义异常 MyException
     /**
     * 错误编码
     */
    private String errorCode;

   
    public MyException(){} //无参构造函数
    
    /**
     * 构造一个基本异常.
     *
     * @param message
     *        信息描述
     */
    public MyException(String message) //带参数的构造函数
    {
        super(message);
    }

   

    public String getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(String errorCode) {
        this.errorCode = errorCode;
    }

    
}

使用自定义异常抛出异常信息:

package com.hysum.test;

public class Main {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        String[] sexs = {"男性","女性","中性"};
                  for(int i = 0; i < sexs.length; i++){
                      if("中性".equals(sexs[i])){
                          try {
                            throw new MyException("不存在中性的人!");//把消息传进自定义异常中
                        } catch (MyException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                     }else{
                         System.out.println(sexs[i]);
                     }
                } 
    }

}

运行结果:

13、良好的编码习惯

1、处理运行时异常时,采用逻辑去合理规避同时辅助try-catch处理

2、在多重catch块后面,可以加一个catch(Exception)来处理可能会被遗漏的异常

3、对于不确定的代码,也可以加上try-catch,处理潜在的异常

4、尽量去处理异常,切忌只是简单的调用printStackTrace()去打印

5、尽量添加finally语句块去释放占用的资源