关于java:J2SE-I一一Java中的异常和处理

38次阅读

共计 11208 个字符,预计需要花费 29 分钟才能阅读完成。

简介

程序运行时,产生的不被冀望的事件,它阻止了程序依照程序员的预期失常执行,这就是异样。异样产生时,是任程序自生自灭,立即退出终止,还是输入谬误给用户?或者用 C 语言格调:用函数返回值作为执行状态?。

Java 提供了更加优良的解决办法:异样解决机制。

异样解决机制能让程序在异样产生时,依照代码的事后设定的异样解决逻辑,针对性地解决异样,让程序尽最大可能恢复正常并继续执行,且放弃代码的清晰。

Java 中的异样能够是函数中的语句执行时引发的,也能够是程序员通过 throw 语句手动抛出的,只有在 Java 程序中产生了异样,就会用一个对应类型的异样对象来封装异样,JRE 就会试图寻找异样处理程序来解决异样。

Throwable 类是 Java 异样类型的顶层父类,一个对象只有是 Throwable 类的(间接或者间接)实例,他才是一个异样对象,能力被异样解决机制辨认。JDK 中内建了一些罕用的异样类,咱们也能够自定义异样。

Java 异样的分类和类结构图

Java 规范裤内建了一些通用的异样,这些类以 Throwable 为顶层父类。

Throwable 又派生出 Error 类和 Exception 类。

谬误:Error 类以及他的子类的实例,代表了 JVM 自身的谬误。谬误不能被程序员通过代码解决,Error 很少呈现。因而,程序员应该关注 Exception 为父类的分支下的各种异样类。

异样:Exception 以及他的子类,代表程序运行时发送的各种不冀望产生的事件。能够被 Java 异样解决机制应用,是异样解决的外围。

总体上咱们依据 Javac 对异样的解决要求,将异样类分为 2 类。

非查看异样(unckecked exception):Error 和 RuntimeException 以及他们的子类。javac 在编译时,不会提醒和发现这样的异样,不要求在程序处理这些异样。所以如果违心,咱们能够编写代码解决(应用 try…catch…finally)这样的异样,也能够不解决。对于这些异样,咱们应该修改代码,而不是去通过异样处理器解决。这样的异样产生的起因多半是代码写的有问题。如除 0 谬误 ArithmeticException,谬误的强制类型转换谬误 ClassCastException,数组索引越界 ArrayIndexOutOfBoundsException,应用了空对象 NullPointerException 等等。

查看异样(checked exception):除了 Error 和 RuntimeException 的其它异样。javac 强制要求程序员为这样的异样做准备解决工作(应用 try…catch…finally 或者 throws)。在办法中要么用 try-catch 语句捕捉它并解决,要么用 throws 子句申明抛出它,否则编译不会通过。这样的异样个别是由程序的运行环境导致的。因为程序可能被运行在各种未知的环境下,而程序员无奈干涉用户如何应用他编写的程序,于是程序员就应该为这样的异样时刻筹备着。如 SQLException , IOException,ClassNotFoundException 等。

须要明确的是:检查和非查看是对于 javac 来说的,这样就很好了解和辨别了。

初识异样

上面的代码会演示 2 个异样类型:ArithmeticException 和 InputMismatchException。前者因为整数除 0 引发,后者是输出的数据不能被转换为 int 类型引发。

package com.example; 
import java. util .Scanner ;
 public class AllDemo{public static void main (String [] args ){System . out. println( "---- 欢送应用命令行除法计算器 ----") ;
            CMDCalculate ();} 
public static void CMDCalculate (){Scanner scan = new Scanner ( System. in); 
int num1 = scan .nextInt () ;
 int num2 = scan .nextInt () ;
 int result = devide (num1 , num2) ;
            System . out. println("result:" + result) ;
            scan .close () ;}
 public static int devide (int num1, int num2){return num1 / num2 ;}
} /*****************************************

---- 欢送应用命令行除法计算器 ----
2
0
Exception in thread "main" java.lang.ArithmeticException : / by zero
     at com.example.AllDemo.devide(AllDemo.java:30)
     at com.example.AllDemo.CMDCalculate(AllDemo.java:22)
     at com.example.AllDemo.main(AllDemo.java:12)

---- 欢送应用命令行除法计算器 ----
1
r
Exception in thread "main" java.util.InputMismatchException
     at java.util.Scanner.throwFor(Scanner.java:864)
     at java.util.Scanner.next(Scanner.java:1485)
     at java.util.Scanner.nextInt(Scanner.java:2117)
     at java.util.Scanner.nextInt(Scanner.java:2076)
     at com.example.AllDemo.CMDCalculate(AllDemo.java:20)
     at com.example.AllDemo.main(AllDemo.java:12)
*****************************************

异样是在执行某个函数时引发的,而函数又是层级调用,造成调用栈的,因为,只有一个函数产生了异样,那么他的所有的 caller 都会被异样影响。当这些被影响的函数以异样信息输入时,就造成的了 异样追踪栈

异样最先产生的中央,叫做 异样抛出点

从下面的例子能够看出,当 devide 函数产生除 0 异样时,devide 函数将抛出 ArithmeticException 异样,因而调用他的 CMDCalculate 函数也无奈失常实现,因而也发送异样,而 CMDCalculate 的 caller——main 因为 CMDCalculate 抛出异样,也产生了异样,这样始终向调用栈的栈底回溯。这种行为叫做异样的冒泡,异样的冒泡是为了在以后产生异样的函数或者这个函数的 caller 中找到最近的异样处理程序。因为这个例子中没有应用任何异样解决机制,因而异样最终由 main 函数抛给 JRE,导致程序终止。

下面的代码不应用异样解决机制,也能够顺利编译,因为 2 个异样都是非查看异样。然而上面的例子就必须应用异样解决机制,因为异样是查看异样。

代码中我抉择应用 throws 申明异样,让函数的调用者去解决可能产生的异样。然而为什么只 throws 了 IOException 呢?因为 FileNotFoundException 是 IOException 的子类,在解决范畴内。


@Test public void testException() throws IOException{ 
//FileInputStream 的构造函数会抛出 FileNotFoundException
    FileInputStream fileIn = new FileInputStream("E:\\a.txt"); 
int word; 
//read 办法会抛出 IOException
    while((word =  fileIn.read())!=-1) 
    {System.out.print((char)word);
    } 
//close 办法会抛出 IOException
    fileIn.clos
}

异样解决的根本语法

在编写代码解决异样时,对于查看异样,有 2 种不同的解决形式:应用 try…catch…finally 语句块解决它。或者,在函数签名中应用 throws 申明交给函数调用者 caller 去解决。

try…catch…finally 语句块

try{ 
//try 块中放可能产生异样的代码。// 如果执行完 try 且不产生异样,则接着去执行 finally 块和 finally 前面的代码(如果有的话)。// 如果产生异样,则尝试去匹配 catch 块。}catch(SQLException SQLexception){
 // 每一个 catch 块用于捕捉并解决一个特定的异样,或者这异样类型的子类。Java7 中能够将多个异样申明在一个 catch 中。//catch 前面的括号定义了异样类型和异样参数。如果异样与之匹配且是最先匹配到的,则虚拟机将应用这个 catch 块来解决异样。// 在 catch 块中能够应用这个块的异样参数来获取异样的相干信息。异样参数是这个 catch 块中的局部变量,其它块不能拜访。// 如果以后 try 块中产生的异样在后续的所有 catch 中都没捕捉到,则先去执行 finally,而后到这个函数的内部 caller 中去匹配异样处理器。// 如果 try 中没有产生异样,则所有的 catch 块将被疏忽。}catch(Exception exception){//...}finally{

    //finally 块通常是可选的。// 无论异样是否产生,异样是否匹配被解决,finally 都会执行。// 一个 try 至多要有一个 catch 块,否则,至多要有 1 个 finally 块。然而 finally 不是用来解决异样的,finally 不会捕捉异样。//finally 次要做一些清理工作,如流的敞开,数据库连贯的敞开等。}

须要留神的中央

1、try 块中的局部变量和 catch 块中的局部变量(包含异样变量),以及 finally 中的局部变量,他们之间不可共享应用。

2、每一个 catch 块用于解决一个异样。异样匹配是依照 catch 块的程序从上往下寻找的,只有第一个匹配的 catch 会失去执行。匹配时,不仅运行准确匹配,也反对父类匹配,因而,如果同一个 try 块下的多个 catch 异样类型有父子关系,应该将子类异样放在后面,父类异样放在前面,这样保障每个 catch 块都有存在的意义。

3、java 中,异样解决的工作就是将执行控制流从异样产生的中央转移到可能解决这种异样的中央去。也就是说:当一个函数的某条语句产生异样时,这条语句的前面的语句不会再执行,它失去了焦点。执行流跳转到最近的匹配的异样解决 catch 代码块去执行,异样被解决完后,执行流会接着在“解决了这个异样的 catch 代码块”前面接着执行。

有的编程语言当异样被解决后,控制流会复原到异样抛出点接着执行,这种策略叫做:resumption model of exception handling(复原式异样解决模式)

而 Java 则是让执行流复原到解决了异样的 catch 块后接着执行,这种策略叫做:termination model of exception handling(终结式异样解决模式)

public static void main(String[] args){ 
try {foo();
        }catch(ArithmeticException ae) {System.out.println("解决异样");
        }
} public static void foo(){ int a = 5/0;  
// 异样抛出点
        System.out.println("为什么还不给我涨工资!!!"); 
 ////////////////////// 不会执行
}

throws 函数申明

throws 申明:如果一个办法外部的代码会抛出查看异样(checked exception),而办法本人又没有齐全解决掉,则 javac 保障你必须在办法的签名上应用 throws 关键字申明这些可能抛出的异样,否则编译不通过。

throws 是另一种解决异样的形式,它不同于 try…catch…finally,throws 仅仅是将函数中可能呈现的异样向调用者申明,而本人则不具体解决。

采取这种异样解决的起因可能是:办法自身不晓得如何解决这样的异样,或者说让调用者解决更好,调用者须要为可能产生的异样负责。

public void foo() throws ExceptionType1 , ExceptionType2 ,ExceptionTypeN
{//foo 外部能够抛出 ExceptionType1 , ExceptionType2 ,ExceptionTypeN 类的异样,或者他们的子类的异样对象。}

finally 块

finally 块不论异样是否产生,只有对应的 try 执行了,则它肯定也执行。只有一种办法让 finally 块不执行:System.exit()。因而 finally 块通常用来做资源开释操作:敞开文件,敞开数据库连贯等等。

良好的编程习惯是:在 try 块中关上资源,在 finally 块中清理开释这些资源。

须要留神的中央:

1、finally 块没有解决异样的能力。解决异样的只能是 catch 块。

2、在同一 try…catch…finally 块中,如果 try 中抛出异样,且有匹配的 catch 块,则先执行 catch 块,再执行 finally 块。如果没有 catch 块匹配,则先执行 finally,而后去里面的调用者中寻找适合的 catch 块。

3、在同一 try…catch…finally 块中,try 产生异样,且匹配的 catch 块中解决异样时也抛出异样,那么前面的 finally 也会执行:首先执行 finally 块,而后去外围调用者中寻找适合的 catch 块。

这是失常的状况,然而也有特例。对于 finally 有很多恶心,偏、怪、难的问题,我在本文最初对立介绍了,电梯速达 ->:finally 块和 return

throw 异样抛出语句

throw exceptionObject

程序员也能够通过 throw 语句手动显式的抛出一个异样。throw 语句的前面必须是一个异样对象。

throw 语句必须写在函数中,执行 throw 语句的中央就是一个异样抛出点,它和由 JRE 主动造成的异样抛出点没有任何差异。

public void save(User user)
{if(user  == null) throw new IllegalArgumentException("User 对象为空"); //......
 }

异样的链化

在一些大型的,模块化的软件开发中,一旦一个中央产生异样,则如骨牌效应一样,将导致一连串的异样。假如 B 模块实现本人的逻辑须要调用 A 模块的办法,如果 A 模块产生异样,则 B 也将不能实现而产生异样,然而 B 在抛出异样时,会将 A 的异样信息覆盖掉,这将使得异样的本源信息失落。异样的链化能够将多个模块的异样串联起来,使得异样信息不会失落。

异样链化: 以一个异样对象为参数结构新的异样对象。新的异对象将蕴含先前异样的信息。这项技术次要是异样类的一个带 Throwable 参数的函数来实现的。这个当做参数的异样,咱们叫他本源异样(cause)。

查看 Throwable 类源码,能够发现外面有一个 Throwable 字段 cause,就是它保留了结构时传递的本源异样参数。这种设计和链表的结点类设计一模一样,因而造成链也是天然的了。

public class Throwable implements Serializable {private Throwable cause = this; public Throwable(String message, Throwable cause) {fillInStackTrace();
        detailMessage = message; this.cause = cause;
    } public Throwable(Throwable cause) {fillInStackTrace();
        detailMessage = (cause==null ? null : cause.toString());
 this.cause = cause;
    } //........
} 

上面是一个例子,演示了异样的链化:从命令行输出 2 个 int,将他们相加,输入。输出的数不是 int,则导致 getInputNumbers 异样,从而导致 add 函数异样,则能够在 add 函数中抛出

一个链化的异样。

public static void main(String[] args)
{System.out.println("请输出 2 个加数"); int result; try {result = add();
        System.out.println("后果:"+result);
    } catch (Exception e){e.printStackTrace();
    }
}
 // 获取输出的 2 个整数返回
private static List<Integer> getInputNumbers()
{List<Integer> nums = new ArrayList<>();
    Scanner scan = new Scanner(System.in); try {int num1 = scan.nextInt(); int num2 = scan.nextInt();
        nums.add(new Integer(num1));
        nums.add(new Integer(num2));
    }catch(InputMismatchException immExp){throw immExp;}finally {scan.close();
    } return nums;
} // 执行加法计算
private static int add() throws Exception
{ int result; try {List<Integer> nums =getInputNumbers();
        result = nums.get(0)  + nums.get(1);
    }catch(InputMismatchException immExp){throw new Exception("计算失败",immExp);  ///////////////////////////// 链化: 以一个异样对象为参数结构新的异样对象。} return result;
}

/*
请输出 2 个加数
r 1
java.lang.Exception: 计算失败
    at practise.ExceptionTest.add(ExceptionTest.java:53)
    at practise.ExceptionTest.main(ExceptionTest.java:18)
Caused by: java.util.InputMismatchException
    at java.util.Scanner.throwFor(Scanner.java:864)
    at java.util.Scanner.next(Scanner.java:1485)
    at java.util.Scanner.nextInt(Scanner.java:2117)
    at java.util.Scanner.nextInt(Scanner.java:2076)
    at practise.ExceptionTest.getInputNumbers(ExceptionTest.java:30)
    at practise.ExceptionTest.add(ExceptionTest.java:48)
    ... 1 more

*/ 

自定义异样

如果要自定义异样类,则扩大 Exception 类即可,因而这样的自定义异样都属于查看异样(checked exception)。如果要自定义非查看异样,则扩大自 RuntimeException。

依照国际惯例,自定义的异样应该总是蕴含如下的构造函数:

  • 一个无参构造函数
  • 一个带有 String 参数的构造函数,并传递给父类的构造函数。
  • 一个带有 String 参数和 Throwable 参数,并都传递给父类构造函数
  • 一个带有 Throwable 参数的构造函数,并传递给父类的构造函数。

 上面是 IOException 类的残缺源代码,能够借鉴。

public class IOException extends Exception
{static final long serialVersionUID = 7818375828146090155L; public IOException()
    {super();
    } public IOException(String message)
    {super(message);
    } public IOException(String message, Throwable cause)
    {super(message, cause);
    } public IOException(Throwable cause)
    {super(cause);
    }
}

异样的注意事项

1、当子类重写父类的带有 throws 申明的函数时,其 throws 申明的异样必须在父类异样的可控范畴内——用于解决父类的 throws 办法的异样处理器,必须也实用于子类的这个带 throws 办法。这是为了反对多态。

例如,父类办法 throws 的是 2 个异样,子类就不能 throws 3 个及以上的异样。父类 throws IOException,子类就必须 throws IOException 或者 IOException 的子类。

 至于为什么?我想,兴许上面的例子能够阐明。

 View Code

2、Java 程序能够是多线程的。每一个线程都是一个独立的执行流,独立的函数调用栈。如果程序只有一个线程,那么没有被任何代码解决的异样 会导致程序终止。如果是多线程的,那么没有被任何代码解决的异样仅仅会导致异样所在的线程完结。

也就是说,Java 中的异样是线程独立的,线程的问题应该由线程本人来解决,而不要委托到内部,也不会间接影响到其它线程的执行。

finally 块和 return

首先一个不容易了解的事实:在 try 块中即使有 return,break,continue 等扭转执行流的语句,finally 也会执行。

public static void main(String[] args)
{int re = bar();
    System.out.println(re);
}
private static int bar() 
{
    try{return 5;} finally{System.out.println("finally");
    }
}
/* 输入:finally
5
*/

 很多人面对这个问题时,总是在演绎执行的程序和法则,不过我感觉还是很难了解。我本人总结了一个办法。用如下 GIF 图阐明。

 也就是说:try…catch…finally 中的 return 只有能执行,就都执行了,他们独特向同一个内存地址(假如地址是 0x80)写入返回值,后执行的将笼罩先执行的数据,而真正被调用者取的返回值就是最初一次写入的。那么,依照这个思维,上面的这个例子也就不难理解了。

finally 中的 return 会笼罩 try 或者 catch 中的返回值。

public static void main(String[] args)
    { int result;

        result = foo();
        System.out.println(result); /////////2
 result = bar();
        System.out.println(result); /////////2
 }

    @SuppressWarnings("finally") public static int foo()
    {trz{ int a = 5 / 0;} catch (Exception e){return 1;} finally{return 2;}

    }

    @SuppressWarnings("finally") public static int bar()
    {try { return 1;}finally {return 2;}
    }

finally 中的 return 会克制(毁灭)后面 try 或者 catch 块中的异样

class TestException
{public static void main(String[] args)
    { int result; try{result = foo();
            System.out.println(result); // 输入 100
        } catch (Exception e){System.out.println(e.getMessage()); // 没有捕捉到异样
 } try{result = bar();
            System.out.println(result); // 输入 100
        } catch (Exception e){System.out.println(e.getMessage()); // 没有捕捉到异样
 }
    } //catch 中的异样被克制
    @SuppressWarnings("finally") public static int foo() throws Exception
    {try { int a = 5/0; return 1;}catch(ArithmeticException amExp) {throw new Exception("我将被疏忽,因为上面的 finally 中应用了 return");
        }finally {return 100;}
    } //try 中的异样被克制
    @SuppressWarnings("finally") public static int bar() throws Exception
    {try { int a = 5/0; return 1;}finally {return 100;}
    }
}

finally 中的异样会笼罩(毁灭)后面 try 或者 catch 中的异样

class TestException
{public static void main(String[] args)
    { int result; try{result = foo();
        } catch (Exception e){System.out.println(e.getMessage()); // 输入:我是 finaly 中的 Exception
 } try{result = bar();
        } catch (Exception e){System.out.println(e.getMessage()); // 输入:我是 finaly 中的 Exception
 }
    } //catch 中的异样被克制
    @SuppressWarnings("finally") public static int foo() throws Exception
    {try { int a = 5/0; return 1;}catch(ArithmeticException amExp) {throw new Exception("我将被疏忽,因为上面的 finally 中抛出了新的异样");
        }finally {throw new Exception("我是 finaly 中的 Exception");
        }
    } //try 中的异样被克制
    @SuppressWarnings("finally") public static int bar() throws Exception
    {try { int a = 5/0; return 1;}finally {throw new Exception("我是 finaly 中的 Exception");
        }

    }
}

下面的 3 个例子都异于常人的编码思维,因而我倡议:

  • 不要在 fianlly 中应用 return。
  • 不要在 finally 中抛出异样。
  • 加重 finally 的工作,不要在 finally 中做一些其它的事件,finally 块仅仅用来开释资源是最合适的。
  • 将尽量将所有的 return 写在函数的最初面,而不是 try … catch … finally 中。

正文完
 0