Java-异常处理(5)


异常处理

程序错误.png
程序错误1.png

一、 Java异常基础

1.1 为什么要引入异常处理机制?

程序的错误分为:
编译错误:程序员编写程序时语法上出现的错误;
运行错误:程序员编写的程序在语法上没有错误,但是程序在运行时出现错误,本章就主要针对该类错误讲解 - - 即异常、异常类和异常处理机制。

1.2 以往的错误处理方法

主要缺陷:
 程序复杂
 可靠性差
 返回信息有限
 返回代码标准化困难 
以往的程序开发过程中,常常采用返回值进行处理。例如,在编写一个方法,可以返回一个状态代码,调用者
根据状态代码判断出错与否。若状态代码表示一个错误,则调用该错误的处理程序进行相应的处理,或显示一
个错误页面或错误信息。

举例 以往的错误处理方法:采用返回值进行处理

举例:实现将一个文件从硬盘加载近来,导致加载可能失败的运行错误有硬盘错误、文件无法找到等
int status=loadTextfile();
If (status!=1){
  //something unusual happened, describe it
 switch(status) {
    case 2: 
       //file not found
      break;
   case 3:
       //disk error
   default:
      //other error}
}else {
    //file loaded OK, continue with program} 


1.3 Java异常处理方法

Java异常处理方法:Java为运行错误引入了异常、异常类和异常处理机制。
异常:特殊的运行错误,是在程序运行过程中发生的、会打断程序正常执行的错误
 例如:
 除0溢出
 文件找不到
数组元素下标越界 

异常类:Java用面向对象的方法处理异常,Java的异常类是处理运行时错误的特殊类,每一种异常类对应一种
特定的运行错误,每一个异常事件由一个异常类的对象来代表。
例如:除0溢出(ArithmeticException)
   文件找不到(FileNotFoundException)
   数组元素下标越界(ArrayIndexOutofBoundsException)


异常处理机制:抛出异常——捕捉异常:
1. 当出现了一些错误,方法都会产生一个异常对象,这个异常对象将交由运行系统来处理。此过程就称为抛出
(throwing)异常包括:系统抛出、用户自定义抛出。
2. 接着,运行系统开始寻找合适的处理方法,来处理这个异常。如果系统找到了一个适合的处理该异常方法,
这一过程就叫捕获异常


1.4 异常处理的语法支持

-try,catch,throws,throw,finally
(1) try包含可能出现异常的语句块;
(2) 一个或多个catch块紧随try{}块,每个catch块通常处理指定类型的异常;
(3) finally引导块紧随catch块后,主要用于清理现场(可有可无)。
格式:
try
{ ……
}catch( ExceptionName1 e )
{ ……}
catch( ExceptionName2 e )
{ ……}
finally
{ ……}
注意:finally总是执行,catch块不一定执行


1.5 总结

(1) Java的异常处理把错误集中起来统一处理。程序员只需要说明何处可能出现异常,如何处理即可;
(2) 采用面向对象的思想标准化了各种错误的类型;
(3) Java把程序运行过程中可能遇到的问题分为两类,一类是致命性的,即程序遇到了非常严重的不正常状
态,不能简单地恢复执行,这就是错误(对应Error类),如程序运行过程中内存耗尽。另一类是非致命性的,
通过某种处理后程序还能继续运行,这就是异常(对应Exception类)


二、 异常类的层次

2.1 异常分类

☕异常类:异常在Java中都是作为类的实例(对象)的形式出现的。
  如 Throwable类, Exception类, Error类……

☕Java中异常分类:
(1) Error类及其子类:描述Java运行时刻系统内部的错误或资源枯竭导致的错误,无法恢复和抛出,发生几率小;
(2) Exception类及其子类:普通程序可以从中恢复,分为运行时异常非运行时异常

 异常在Java中也是作为类的实例的形式出现的。Java中的所有的异常类都是从Throwable类派生出来的。Throwable类有两个直接子类:java.lang.Errorjava.lang.Exception


异常类的层次结构如下图所示。
58.png
(1) 🔺Error类及其子类主要用来描述一些Java运行时刻系统内部的错误或资源枯竭导致的错误。普通的程序不能从这类错误中恢复,也无法抛出这种类型的错误,这类错误出现的几率是很小的。
(2) 🔺另一个异常类的子类是Exception类和它的子类。在编程中错误的处理主要是对这类错误的处理,如除数为零、数组下标越界等。类Exception是普通程序可以从中恢复的所有规范了的异常的父类。


2.2 Exception类的子类

☕Exception类子类有两种:
运行时异常非运行时异常(一般异常)     区别???
(1)运行时异常:RuntimeException类及其所有子类。 运行时异常是程序员编写程序不正确所导致的异常,理论上,程序员经过检查和测试可以查出这类错误。如除数为零等,错误的强制类型转换、数组越界访问、空引用
(2)非运行时异常(一般异常):指可以由编译器在编译时检测到的、可能会发生在方法执行过程中的异常,如找不到指定的文件等,这不是程序本身的错误,如果这些异常情况没有发生,程序本身仍然是完好的。  
注意:编译器强制要求Java程序必须捕获声明抛出所有非运行时异常,但对运行时异常不作要求。运行时异常编译可以通过但是运行时出现异常;非运行时异常编译的时候就通不过。

例如:格式不正确的URL、试图为一个不存在的类找到一个代表它的类的对象。除了runtimeexception及其子类以外,其他exception类的子类都是非运行时异常。
//运行时异常和非运行时异常区别举例  
[5-1] RuntimeExceptionDemo1.java
class RuntimeExceptionDemo1{  
public static void main(String args[]){
    int i=0;
System.out.println(2/0);
}}

该程序能编译通过,而在运行时,出现如下提示:
> javac RuntimeExceptionDemo1.java
> java RuntimeExceptionDemo1
Exception in thread "main" java.lang.ArithmeticException: / by zero at 
RuntimeExceptionDemo1.main(RuntimeExceptionDemo1.java:4)
//这里的"ArithmeticException"
[5-2] NonRuntimeExceptionDemo1.java
import java.io.*;
class NonRuntimeExceptionDemo1
{ 
      public static void main(String args[]){
       FileInputStream in=new FileInputStream("text.txt");
        int s;
        while((s=in.read())!=-1) System.out.print(s);
        in.close();
   }
}
会出现如下的错误提示:
javac NonRuntimeExceptionDemo1.java
NonRuntimeExceptionDemo1.java:5: unreported exception java.io.FileNotFoundException; must
 be caught or declared to be thrown
FileInputStream in=new FileInputStream("text.txt");
                   ^
NonRuntimeExceptionDemo1.java:7: unreported exception java.io.IOException; mustbe
 caught or declared to be thrown
while((s=in.read())!=-1) System.out.print(s);
                ^
NonRuntimeExceptionDemo1.java:8: unreported exception java.io.IOException; mustbe caught 
or declared to be thrown
in.close();

☕对运行时异常的说明:由于运行时异常可能会出现在程序的任何地方,而且出现的可能性非常大,因而由程序本身去检测运行异常出现与否,将会使程序的开销过大,所以
运行时异常是由Java运行时系统在程序的运行过程中检测到的,它可能在程序中任意部位发生,而且其数目可能很大,因此Java编译器允许程序不对它进行处理。这时,java运行时系统会把生成的运行时异常对象交给默认的异常处理,在标准输出设备上显示异常的内容以及发生异常的位置
建议对于运行时异常用户不要去捕获,捕获就是承认这个错误,但是一般我们编程的希望是发生错误能改正就尽量改正,除非不是由程序员自己意愿决定的错误。


☕对运行时异常和非运行时异常的总结:

(1) 当出现java.lang.ArithmeticException运行时异常时,不需要用户在程序中对其进行处理,而直接由Java运行时系统进行处理;
(2) 对于非运行时异常,Java编译器对程序进行编译的时候,便指出用户需要①捕获该类异常或者②声明抛出。即对于非运行时异常,用户需要在程序中进行处理,否则编译时无法通过。
① 捕获该类异常
[5-2] NonRuntimeExceptionDemo1.java
import java.io.*;
class NonRuntimeExceptionDemo1
{ 
      public static void main(String args[]){
        try{
             FileInputStream in=new FileInputStream("text.txt");
            int s;
            while((s=in.read())!=-1) System.out.print(s);
           in.close();
              }catch(Exception e){}
   }
}


② 声明抛出
[5-2] NonRuntimeExceptionDemo1.java
import java.io.*;
class NonRuntimeExceptionDemo1
{ 
      public static void main(String args[])throws Exception{
      FileInputStream in=new FileInputStream("text.txt");
      int s;
      while((s=in.read())!=-1) System.out.print(s);
      in.close();
}
}


2.3 对于运行时异常和非运行时异常的一些说明

1. ☕编译器和异常:编译器强制要求程序员捕获或声明抛出非运行时异常
到底为什么要这么做呢???
对于运行时异常,编译器不强制要求,但用户也可以自己去捕获
这个时候会出现什么效果呢???

2. ☕运行时系统和异常:
(1) 异常都有抛出的轨迹;
(2) 对于所有异常,某个方法产生的异常 ,如果没有被捕获,就会自动抛给方法的调用者(但是对非运行时异常一定要声明抛出),如果调用者还没有捕获,再抛给调用者的调用者,以此类推,直到main方法里发现还没有捕获,那么运行时系统就会来处理这个异常,把异常信息和异常的轨迹信息打印给用户。

3. ☕两点建议:
(1) 建议对于运行时异常用户不要去捕获,捕获就是承认这个错误,但是一般我们编程者希望是发生错误能改正就尽量改正,除非不是由程序员自己意愿决定的错误;
(2) 建议对于非运行时异常,用户应该去捕获,以交代程序员的处理该错误的代码,实在不行,就声明抛出给方法的调用者。

3. ☕异常类常用的方法:
public Exception()
public Exception(String s)  :该参数一般表示该异常对应的错误的描述
public String toString() :返回描述当前异常对象信息的字符串
public String getMessage() :返回描述当前异常对象信息的详细信息。
public void printStackTrace() :打印当前异常对象使用堆栈的轨迹。

5S.png

4DI6.png


三、 try-catch-finally异常处理

3.1 概括try-catch-finally语句

用户处理异常的三种方法:
(1)用户可以用try-catch-finally语句进行抛出捕获处理
(2)如果不想捕获和处理异常,可以通过throws语句声明要抛出的异常
(3) 用户可以定义自己的异常类,并用throw语句来抛出。

运行时异常是由Java运行时系统在程序的运行过程中检测到的,它可能在程序中任意部位发生,而且其数目可能很大,因此Java编译器允许程序不对它进行处理。这时,java运行时系统会把生成的运行时异常对象交给默认的异常处理,在标准输出设备上显示异常的内容以及发生异常的位置。即:运行时异常:会输出到设备显示哪里存在错误,请更正;非运行时异常:编译报错时提示必须添加非运行时异常处理,比如加try{}…catch(类名 对象){}或者使用throws来抛出异常
try-catch-finally语句对程序运行进行监控,捕获和处理异常通常形式: 
try{
调用可能产生异常的方法及其它java语句;
}
catch(异常类名1 异常对象名e){
异常处理语句块;
}
catch(异常类名2 异常对象名e){
异常处理语句块;
}
finally{
最终处理;
}
[5-3]访问文本文件text.txt,并将其在屏幕上打印出来。
import java.io.*;
class TryCatchFinally
{ 
      public static void main(String args[]){
       try{
        FileInputStream in=new FileInputStream("text.txt");
        int s;
        while((s=in.read())!=-1) System.out.print(s);
        in.close();
       }
       catch(FileNotFoundException e){
          System.out.println(“捕获异常:”+e);   //e会调用toString()方法回显异常信息给程序员
       }
       catch(IOException e){
          System.out.println("捕获异常:"+e);
       }
       finally{
       System.out.println("finally块总是执行!");
       } }
}

运行结果:
捕获异常:java.io.FileNotFoundException: text.txt (系统找不到指定的文件。)
finally块总是执行!


3.2 try语句

try{}:将可能抛出一个或者若干个异常的代码放入try语句块中。
注意:应当尽量减小try代码块的大小,不要将整个程序代码全部放入try语句块中,而是应当仔细分析代码,在可能出现异常情况的地方用try进行监控。

因为当发生异常时,程序控制由try块转到catch块,Java将跳过try中后面的语句,且永远不会从catch块返回到try块。因此若将整个程序代码都放在try中,若一开始发生异常,则后面的语句将永远不会被执行,从而影响了程序的实现。

3.3 catch语句

📒(1) try语句后面必须跟有一个或多个catch语句来处理try中产生的异常事件。如果try语句中未产生异常,
那么catch语句将不执行。
📒(2)catch语句需要一个参数:一个异常类名和该异常类的对象。注意该异常类必须是Throwable类的子类.
📒(3) try块中发生了一个异常,try-catch语句就会自动在try块后面的各个catch块中,找出与该异常类相
匹配的参数。当参数符合以下3个条件之一时,就认为这个参数与产生的异常相匹配:
(1)参数与产生的异常属于一个类;
(2)参数是产生的异常的父类;
(3)参数是一个接口时,产生的异常实现了这一接口。
📒(4) 注意:
(1) 当产生的异常找到了第一个与之相匹配的参数时,就执行包含这一参数的catch语句中的Java代码,执
行完catch语句后,程序恢复执行,但不会回到异常发生处继续执行,而是执行try-catch结构后面的代码。
(2) 可以用一个catch块来处理多个异常类型,此时catch的参数应该是这多个异常的父类。
(3) 有多个catch块时,要细心安排catch块的顺序。 将子类的catch块放在前面,父类的catch块放在后面。

3.4 finally子句

1.finally语句: 无论在try块中是否产生异常,也不管产生的异常是否会被捕获,finally中的语句最终都会
被执行。
2.作用: 为异常处理事件提供一个清理机制,例如清理打开文件、Socket、JDBC连接之类的资源。

如果程序用到了文件、Socket、JDBC连接之类的资源,即使遇到了异常,也要正确释放占用的资源。
finally语句可以说是为异常处理事件提供的一个清理机制. 一般是用来关闭文件或释放其他的系统资源,作
为try-catch-finally结构的一部分,可以没有finally语句,如果存在finally语句,不论try块中是否发生
了异常,是否执行过catch语句,都要执行finally语句。


3. 带有finally子句的try-catch-finally语句的形式如下:
try {}           //…是正常执行的代码, 可能产生异常
       catch (异常类1 e) {}    //…是异常类1的处理代码
       catch (异常类2 e) {}    //…是异常类1的处理代码
        ……
      catch (异常类n e) {}   //…是异常类2的处理代码
      finally {}                   //…是执行清除工作的语句
4.执行过程:
(1) try块中的语句没有产生异常。在这种情况下,Java首先执行try块中的所有的语句,然后执行finally子句
中的代码,最后执行try…catch..finally块后面的语句;
(2) try块中的语句产生了异常,而且此异常在方法内被捕获(有catch匹配)。 在这种情况下,Java首先执行try
块中的语句,直到产生异常处,然后跳过此try块中剩下的语句,执行捕获此异常的catch子句的处理代码;
然后执行finally子句中的代码;
(3) 如果在catch子句又重新抛出了异常。也会执行finally,然后将这个异常抛出给方法的调用者;
(4) try块中产生了异常,而此异常在方法内没有被捕获(没有catch匹配) 。在这种情况下,Java将执行try块
中的代码直到产生异常,然后跳过try块中的代码而转去执行finally子句中的代码,最后将异常抛出给方法
的调用者。


四、 throws-throw抛出异常

4.1 throws抛出异常

📒throws说明:
在设计可能会抛出异常的方法时,可以有两个选择:
(1) 使用try-catch-finally处理方法中的异常
(2) 声明抛出异常:不捕获异常(没有throws方法的创建异常类的实例和抛出异常。是个空语句。),而是将异常交由上一层处理,在其他地方捕获异常。如果使用后者,那么应该(在某些情况下)向编译器表明:此方法可能会抛出异常,但方法本身不会捕获它。可以在方法头中用throws子句来实现此功能。

(2.1)带throws异常说明的方法说明形式如下:
… 方法名(…) [throws 异常类列表]
{ 方法体 }


📒注意:
(1)方法抛出的异常类是throws子句中指定的异常类或其子类。
(2)并不是所有可能发生的异常都要在方法的说明中指定,从Error类中派生出的异常和从RuntimeException类中派生的异常就不用在方法声明中指定。

📒在下列情况下Java方法可以声明抛出异常:
(1)调用的方法抛出了异常;
(2)检测到了错误并使用throw语句抛出异常;
1:调用的方法抛出了异常 
class Test {……
 public String getInput() throws IOException
 {……
    System.in.read();
}
}2:检测到了错误并使用throw语句抛出异常
import java.io.*;
 class Test {……
 public String getInput() throws IOException
 {……
     IOException ae =new IOException("buffer is full");
    throw ae; 
}3throws多个异常
class Animation
{                                                   
 public Image loadImage(String s) throws  
                 EOFException, MalformURLException  
 {
  ……
 }
} 


4.2 throw抛出异常

📒throw说明:
要使用throw,则必须在方法名后面指出throws;但是使用throws,抛出可以是空语句不用写,交给上一层处理!!
在捕获一个异常前,必须有一段Java代码来生成和抛出一个异常对象。Java用throw语句抛出异常。throw语句的格式如下:
throw ThrowableObject;


异常对象的生成和抛出可以有以下三种情况:
   (1)Java运行时系统
   (2)JDK中某个类
   (3)在程序中创建异常对象抛出

使用throw语句应注意:
(1)一般这种抛出异常的语句应该在满足一定条件执行,例如把throw语句if分支中
(2)含有throw语句的方法,应该在方法头定义中用throws语句声明所有可能抛出的异常


抛出异常有这样三步:
(1)确定异常类;
(2)创建异常类的实例;
(3)抛出异常。
举例
static String getInput() throws IOException{
  char[] buffer =new char[20];
   int counter = 0;
   boolean flag =true;
   while(flag) {
       buffer[counter] =(char)System.in.read(); 

      if(buffer[counter]=='\n') flag = false;
      counter++;
      if(counter>=20){
} IOException ae =new IOException("buffer is full"); 
      throw ae; }
    return new String(buffer);
}

 "//IOException ae =new IOException(""buffer is full"");  throw ae;"使用throw


五、 正确地使用异常

由于异常使用起来非常方便,以至于在很多情况下可能会滥用异常。但是,使用异常处理会降低程序运行的
速度,几点建议:

(1) 在可以使用简单的测试就能完成的检查中,不要使用异常来代替它。例如:
if (ins!=null) //使用ins引用对象
{ … }

(2) 不要过细地使用异常。最好不要到处使用异常,更不要在循环体内使用异常处理, 可以将它包裹在循环
体外面。
(3)不要捕获了一个异常而又不对它做任何的处理。
try
{
…… //正常执行的代码
}
catch(Exception e) { }

(4) 将异常保留给方法的调用者并非不好的做法。
对于有些异常,将其交给方法的调用者去处理是一种更好的处理办法。没有类似这样if(counter>=20){
IOException ae =new IOException(“buffer is full”);
throw ae; }的语句,是个空语句。






  Reprint please specify: Treecatee Java-异常处理(5)

 Previous
Java运算符和数据类型转换(3.4-3.6) Java运算符和数据类型转换(3.4-3.6)
   运算符及表达式、运算符的优先级、数据类型转换 3.4-3.6学习知识点 运算符及表达式 算术运算符 关系运算符 逻辑运算符 位运算符 赋值运算符 逻辑运算符 运算符的优先级 数据类型转换
2019-01-11
Next 
Java变量及其传递、内部类(4.9-4.10) Java变量及其传递、内部类(4.9-4.10)
   变量及其传递 & 内部类   一、 Java变量及其传递1.1 基本数据变量与引用型变量1. 📒Java变量: (1). 基本型变量(8种)   char,b
2018-12-27
  TOC