一、异常处理概念
-
引文
异常是指在程序运行之中因为代码而产生的一种错误,在不支持异常处理的程序语言之中,每一个运行时所发生的的错误必须由程序员手动控制。但是Java语言之中的异常处理机制则会避免这些问题。 -
错误与异常
软件开发之中,程序的错误是无法避免的。程序中的错误会有很多种类,一般分为三种,语法错误,语义错误和逻辑错误三种。
即便程序中有三种心智的错误,但是Java系统中根据错误的严重程度不同,将程序运行时出现的错误分为两类,错误以及异常。
错误指的是程序在执行过程中所遇到的硬件或操作系统的错误,如内存溢出,虚拟机错误等,错误会导致程序无法运行。
异常则是指硬件和操作系统正常时,程序遇到的运行错误,有些异常是由于算法的错误引起,或者疏忽引起,如除数为0,数组下标越界等等。 Java语言的异常处理机制使程序本身可以捕获和处理异常。
因为异常时可以监测和处理的,所以就会产生相应的异常处理机制,目前绝大多数的语言都提供了异常处理机制,而错误处理一般由系统承担,语言的本身不提供错误处理机制。 -
Java语言的异常处理机制
Java语言提供的异常处理机制是通过面向对象的方法来处理异常的,所以在Java语言中,所有异常都是以类的形式存在的,除了内置的异常类以外,Java语言也允许用户自行定义异常类。
在一个程序的运行过程之中,如果发生了异常事件,则代表该异常产生了一个“异常对象”,并把它交给运行系统,再由运行系统寻找相应的代码处理这一异常。生成异常并把它交给运行系统的过程称为抛出异常。异常本身作为一个对象,产生一个异常就是产生一个异常对象。该异常对象中可能包含了异常时间类型以及发生异常时应用程序目前的状态和调用过程等必要的信息。
异常跑出后,运行系统从生成异常对象的代码开始,沿方法的调用栈逐层回溯查找,查找到包含相应异常处理的方法,并把异常对象提交给该方法为止,这一过程称为捕获异常。
Java语言之中定义了许多异常类,每个异常类都代表一种运行错误,类中包含了该运行错误的信息和处理错误的方法等内容,每当Java程序运行过程中发生一个可识别的运行错误时,即该错误有一个异常类与之对应,系统均会产生一个相应的该异常类的对象,一旦一个异常对象产生了,系统之中就一定会有相应的机制处理它,从而会保证整个程序运行时候的安全性。
异常的本质是一个在程序执行过程中发生的事件,这个事件将会中断程序的正常执行,当Java方法的内部发生异常的时候,这个方法即会创建一个该异常的对象,并且把它传递给运行时环节。创建一个异常对象并将它传递给运行时的环境的过程就是抛出一个异常。运行时候环境从异常发生的方法开始查找异常处理程序,如果异常处理程序捕获到的异常类型和这个程序能够处理异常的类型相同,那么这个程序就叫做合适的异常处理程序,然后异常处理机制将控制权从发生异常的程序交给能处理该异常的异常处理程序,如果没有找到合适的异常处理程序,运行时环境将终止程序执行。
简单来说,就是发现异常的代码可以“抛出”一个异常,运行系统能够“捕获”该异常,并且交由程序员编写的相应代码进行异常处理。
二、异常处理类
- 因为Java语言中定义了许多异常类,且每个异常类代表着一种运行错误,所以说,Java原因的异常类是处理运行时错误的特殊类,类中包含了该运行错误的信息和处理错误的方法等内容。
- 在异常类的层次最上层有一个单独的类叫做
Throwable
,是java.lang
包中的一个类,这个类用来表示所有的异常情况,该类派生了两个子类java.lang.Error
和java.lang.Exception
。Error
代表着程序运行时Java系统内部的错误。包括,内存溢出错,栈溢出错,动态链接错等等。通常Java程序不对这种错误进行直接的处理,而是交由操作系统进行处理。Exception
则是供应用程序使用的,是用户程序能够捕捉到的异常情况,一般情况之下,通过产生它的子类来创建自己的异常,即Exception
类对象是Java程序抛出和处理的对象,它有各种不同的子类分别对应于各种不同类型的异常。因为应用程序不处理Error
类,所以一般来说的异常都是指Exception
类及其子类。
- Java程序对错误与异常的处理方式有三种:1.程序不能处理的错误。2.程序应该避免而可以不去捕获的运行时异常。3.必须捕获的非运行时的异常。
三、异常的处理
- Java中,异常处理是通过
try,catch,finally,throw,throws
五个关键字来实现的。 - 不要看异常的理论一大堆,用起来海星的。
- 例如:
public class Employee {
private String empNo;
private String name;
private int salary;
public String getEmpNo(){
return this.empNo;
}
public String getName(){
return this.name;
}
public int getSalary(){
return this.salary;
}
public void setEmpNo(String empNo){
this.empNo = empNo;
}
public void setName(String name){
this.name = name;
}
public void setSalary(int salary){
this.salary = salary;
}
public static Employee str2Employee(String str) throws Exception{
String[] out = str.split(",");
if(out.length!=3){//如果输入的格式不正确,数组分割就不是三个了
throw new Exception("请按照规定的格式输入信息");//抛出一个异常
}
Employee ex = new Employee();
ex.setEmpNo(out[0]);
ex.setName(out[1]);
ex.setSalary(Integer.valueOf(out[2]));
return ex;
}
public static void main(String[] args){
String info="00001,Lily,2000";
try{
Employee emp= Employee.str2Employee(info);
System.out.println(emp.getEmpNo()+"-"+emp.getName()+"-"+emp.getSalary());
}catch(Exception e){//捕获异常
System.out.println("输出格式错误,请重输");
}
}
try
:表示程序正常执行代码,但是如果程序在执行try
代码块的时候出现了"非预期"的情况,JVM则会产生一个异常对象,而这个异常对象则会被后面相应的catch
块捕获。catch
:表示一个异常捕获块,当程序执行try
块引发异常的时候,这个异常对象则会被catch
给捕获。throw
:用于手动抛出异常对象,throw
后面需要一个异常对象。throws
:用于方法签名中声明抛出一个或者多个异常类,throws
关键字后面可以紧跟一个或者多个异常类。finally
:代表异常处理流程中总会执行的代码块。
对于一个完整的异常处理流程而言,try
块是必须的,后面可以跟一个或者多个catch
块,最后还可以带一个finally
块。
try{
//要检查的语句块
}catch(异常类名 形参对象名){
//异常发生时的处理语句序列
}finally{
//一定会运行的语句序列
}
四、抛出异常
捕获一个异常前,必须要有一段代码生成一个异常对象并把它抛出,根据异常类型的不同,抛出异常的方法也不相同。可以分为:1.系统自动抛出的异常。2.指定方法抛出异常。
所有系统定义的运行时异常都可以有系统自动抛出,而指定方法抛出异常需要是用关键字throw
或throws
来明确制定在方法内抛出异常。如用户程序自定义的异常不可能依靠系统自动抛出,这种情况就必须借助于throw
或throws
语句来定义何种情况算是产生了此种异常对应的错误,并应该抛出了这个异常。
-
抛出异常的方法与调用方法处理异常
实际编程中,异常的处理可能会在方法之外进行处理,就如我们上面写的例子。那么,该方法就抛出异常,有方法的调用者负责处理该异常。这个时候与异常有关的方法就只有两个:一个是抛出异常的方法,一个是处理异常的方法。-
抛出异常的方法
如果在一个方法内部的语句执行时引发某种异常,但是不能够确定如何进行处理,则此方法应该声明抛出异常,表示该方法不对这些异常进行处理,而由该方法的调用者去进行处理。则在方法之内的异常我们并未使用try-catch
语句捕获异常和处理异常的代码。一个方法声明抛出异常有如下的两种方式,
方式一:在方法体内使用throw
语句抛出异常对象。
throw 由异常类所产生的对象;
其中,有“异常类所产生的的对象”是一个从Throwable
派生的异常类对象。
方式二:在方法头部添加throws
子句表示方法将抛出异常。带有throws
子句的方法声明格式如下:
[修饰符] 返回值类型 方法名(参数列表) throws 异常类列表
-
处理异常的方法
由一个方法抛出异常之后,该方法内没有处理异常的语句,则系统就回将异常想上传递,由调用他的方法来处理这些异常,若是上层调用方法中仍然没有处理异常的语句,则可以再往上追溯到更上层,这样子可以一层一层网上追溯,一直到main()方法,这时JVM肯定会处理的,这样子可以编译通过了。也就是说,如果某个方法抛出了异常之后,调用它的方法肯定要捕获并处理异常,否则就会出现错误的。public class demo { public static void main(String[] args) { int num1 = 5, num2 = 0; try{ if (num2 == 0) throw new ArithmeticException(); else System.out.println(num1 + "/" + num2 + "=" + num1 / num2); }catch(ArithmeticException e){ System.out.println(e); e.printStackTrace();//输出当前异常对象的对战使用轨迹 } } } /*输出 java.lang.ArithmeticException java.lang.ArithmeticException at sample.demo.main(demo.java:8) */
在这个例子中,我们故意从try块中抛出由异常类所产生的的对象,但是即便不使用
throw
抛出异常,系统也会自动抛出异常的。public class demo { public static void main(String[] args) { int num1 = 5, num2 = 0; try{ System.out.println(num1 + "/" + num2 + "=" + num1 / num2); }catch(Exception e){ System.out.println(e); e.printStackTrace(); } } } /* java.lang.ArithmeticException: / by zero java.lang.ArithmeticException: / by zero at sample.demo.main(demo.java:7) */
所以说在程序代码中抛出系统定义的运行时异常并没有太大的意义,通常从程序代码中抛出的是自己编写的异常,因为系统并不会自动帮我们抛出他们。
-
由方法抛出异常交系统处理
对于程序需要处理的异常,一般编写try-catch-finally
语句捕获并处理,但是对于程序中无法处理必须交由系统处理的异常,由于系统直接调用的是主方法main()
,所以可以在主方法头使用throws
子句声明抛出异常交由系统处理。如下面程序,编译可以通过,运行也可以。import java.io.FileInputStream; import java.io.IOException; public class demo { public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream("autoexec.bat"); } }
直接在主方法
main
中抛出异常,让Java默认的异常处理机制来进行处理,即若在主方法中没有使用try-catch
语句捕获异常,则必须在声明主方法头部后加上throws IOException
子句。import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class demo{ public static void main(String[] args) { String str; BufferedReader buf; buf = new BufferedReader(new InputStreamReader(System.in)); while(true){ try{ System.out.println("请输入字符串:"); str = buf.readLine(); if(str.length() > 0) break; else throw new IOException(); }catch (IOException e){ System.out.println("必须输入字符串!"); continue; } } String s = str.toUpperCase(); System.out.println("转换大写后字符串为:"+s); } }
-
五、自动关闭资源的try语句
Java程序中经常要创建一些对象(如打开的文件,数据连接等),这些对象在使用之后需要关闭,如果忘记关闭可能会引起一些问题,在JDK7之前,通常使用finally
子句来确保一定会调用close()
方法关闭打开的资源,但是如果调用close()
方法也可能抛出异常,这个时候就需要在finally
块内嵌套一个try-catch
语句,这样会显得程序代码就回冗长。但是自从JDK7之后,为开发人员提供了自动关闭资源的功能,这样也为管理资源提供了一种更加简便的方式,这种功能是通过try-with-resources
语句,也称为自动资源管理语句。该语句能够自动关闭在try-catch
语句块中使用的资源,该处资源指的是程序完成之后,必须关闭的对象,try-with-resources
语句确保了每个资源在语句结束时被关闭。try-with-resources
语句的格式如下:
try(声明或初始化资源的代码){
使用资源对象res的语句
}
其中声明或初始化资源的时候,初始化一个或多个资源的时候,需要使用“;”进行分隔开,当try
语句执行结束时会自动关闭这些资源,需强调一点,并非所有资源都可以自动关闭的,只有实现java.lang.AutoCloseable
接口的那些资源可以自动关闭,而改接口之中只有一个抽象方法:void close() throws Exception
。
自动关闭的try
语句相当于包含了隐式的finally
语句块,该finally
语句块会自动调用res.close()
方法关闭前面所访问的资源。因此自动关闭资源的try
语句后面可以带有一个或多个finally
块,也可以没有。如果含有catch
和finally
子句,则catch
和finally
子句将会在try-with-resources
语句中打开的资源被关闭之后的到调用。
public class demo {
public static void main(String[] args) throws IOException {
try(Scanner in = new Scanner(Paths.get("t.txt"))){
while (in.hasNext())
System.out.println(in.nextLine());
}
}
}
输出t.txt的内容,Scanner
类是实现了AutoCloseable
接口的类,所以实现了close()
方法,所以对象in是可以自动关闭的资源。当代码块退出或者发生异常的时候都会自动调用in.close()
方法关闭资源,与finally
子句用法相同。如果资源关闭时候出现异常,那么try
语句块中其他异常都会被忽略,可以在catch
语句块中调用getSuppressed()
方法将“被忽略的异常”重新显示出来。
六、自定义异常类
创建自定义异常类:
- 声明一个新的异常类,用户定义的异常类必须是
Throwable
类的直接或间接子类。Java推荐用户自定义的异常类一Exception
为直接弗雷,也可以使用某个已经存在的系统异常类或用户自己定义的异常类为其父类。 - 为用户自定义的异常类定义属性和方法,或覆盖父类的属性和方法,使得这些属性和方法能够体现该类所对应的错误信息。
- 但是用户自定义异常不能够自动抛出,需要借助于
throw
语句来定义何种情况算是产生了该异常所对应的错误,并且抛出这个异常类的对象。
package sample;
class circleException extends Exception{
double radius;
circleException(double r){
this.radius = r;
}
public String toString(){
return "半径 r=" + this.radius + "不是一个正数";
}
}
class circle{
private double radius;
public void setRadius(double r) throws circleException{
if(r < 0){
throw new circleException(r);
}
else radius = r;
}
public void show(){
System.out.println("圆的面积是:" + 3.14*this.radius*this.radius);
}
}
public class demo{
public static void main(String[] args) {
circle cir = new circle();
try {
cir.setRadius(-2.0);
}catch (circleException e){
System.out.println(e.toString());
}
}
}