Java 异常

来源:互联网 发布:生成对抗网络理解 编辑:程序博客网 时间:2024/06/05 10:02

面对脚本类语言的突飞猛进,穷追猛打,Java还有什么优势?

一个优势就是Java强大的异常处理机制,深入学习并掌握Java异常和其处理机制

可以促进我们在项目中处理更有效的处理异常,使程序更健壮,对测试和调试友好。

 

0 . 什么是异常?

简单来说,异常是Java程序运行中出现的错误或者未达到预期的程序设定。


一.Java异常的分类



Java把异常当做对象来处理(作为面向对象语言,Java中一切都是对象,都由Object派生),并定义一个基类java.lang.Throwable作为所有异常的超类。Java中的异常分为两大类:错误Error和异常Exception。

 

1.1Throwable(可抛出)

异常类的最终父类,它有两个子类,Error与Exception。
Throwable中常用方法有:
getMessage():返回异常的消息内容,打日志时常用的e.getMessage()就是继承自方法。
printStackTrace():输出错误堆栈信息到控制台,catch方法块中对异常exception处理的默认方法,但在生产程序中一般不使用,通常做法是通过日志组建将异常信息打印到日志中。

 

Thorwable虽然是一个实体类,但一般还是当作接口或者虚基类看待,不直接在程序中使用

 

1.2 Error(错误):表示程序无法处理的错误,一般是指java虚拟机相关的问题,如系统崩溃、虚拟机出错误、动态链接失败等,与程序员的执行操作无关。理论上这些错误是不允许发生的,通常应用程序也无法处理这些错误,如果发生,也不应该试图通过程序去处理,所以Error不是try-catch的处理对象,也无须在其throws子句中声明该方法抛出任何Error或其子类,而JVM一般的处理方式是终止发生错误的线程,所以这些错误很可能使JVM虚拟机停止运行。

Error类常见子类有VirtualMachineError与AWTError。

1.3 VirtualMachineError(虚拟机错误):表示虚拟机出现错误。
在Java运行时内存中,除程序计数器外的虚拟机栈、堆、方法区在请求的内存无法被满足时都会抛出OutOfMemoryError;
而如果线程请求的栈深度超出虚拟机允许的深度时,就会抛出

StackOverFlowError。

1.3.1 LinkageError(连接错误):表示Java类的依赖错误

未识别类文件会抛出ClassFormatError,一般程序文件和编译器JDK版本不一致时会抛出此错误

类编译后做了不兼容更改时会抛出AbstractMethodError

1.3.2 AWTError(AWT组件出错)

 AWT是使用操作系统中的图形函数的抽象窗口工具,用C\C++编写,为了实现Java“一次编译,处处运行”的理念,AWT使用各个操作系统图形函数的交集,所以功能较差,但运行速度较快,适合嵌入式Java;

(原来主要用于功能机和简单智能设备,然而现在嵌入式Java都被安卓占了)

1.3.2 SwingError(SwingError组件出错)

Swing组件是基于AWT编写的图形界面系统,它用纯Java编写,所以必然实现了“一次编译,处处运行”,但相较于AWT运行速度较慢,适合PC使用。

(自JDK1.8之后官方提倡使用JavaFX开发图形界面)

 

1.3.3、Exception(异常):出现原因取决于程序,所以程序也理应通过try-catch处理。

I.可查异常和不可查异常

Java的异常(包括Exception和Error)分为可检查异常(checked exceptions)和不可检查异常(unchecked exceptions)。
可查异常(编译器要求必须处置的异常):正确的程序在运行中,预计会出现的异常,也是允许出现的异常,比如连接超时,文件未找到文件,IO错误,但这些异常出现后,必须指定处理的方式。

除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可检查异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。


不可检查异常(编译器不要求强制处置的异常):包括运行时异常(RuntimeException与其子类)和错误(Error)。

如果使用throw在方法体中抛出可查异常,则需要在方法头部声明方法可能抛出的异常类型。程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中(可能在上层调用函数中)从里向外寻找含有与其匹配的catch子句的try块。

 

II.运行时异常和非运行时异常

(1)运行时异常都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

当出现RuntimeException的时候,我们可以不处理。当出现这样的异常时,总是由虚拟机接管。比如:NullPointerException空指针异常,它就是运行时异常,并且这种异常还是最常见的异常之一。

 
出现运行时异常后,如果没有捕获处理这个异常(即没有catch),系统会把异常一直往上层抛,一直到最上层,如果是多线程就由Thread.run()抛出,如果是单线程就被main()抛出。抛出之后,如果是线程,这个线程也就退出了。如果是主程序抛出的异常,那么这整个程序也就退出了。运行时异常是Exception的子类,也有一般异常的特点,是可以被catch块处理的。只不过往往我们不对他处理罢了。也就是说,你如果不对运行时异常进行处理,那么出现运行时异常之后,要么是线程中止,要么是主程序终止。 


如果不想终止,则必须捕获所有的运行时异常,决不让这个处理线程退出。队列里面出现异常数据了,正常的处理应该是把异常数据舍弃,然后记录日志。不应该由于异常数据而影响下面对正常数据的处理。

常见运行时异常

①.NullPointerException:空指针异常。调用了不存在的对象或未经实例化或初始化的对象时会抛出,对null对象的方法属性调用就会抛出此异常(最常见的就是.toString方法抛出空指针异常)

②.ArithmeticException:算术条件异常。最常见的就是0作除数时会抛出。

③.ClassNotFoundException:类未找到异常。在通过反射Class.forName(“类名”)来获取类时,如果未找到则会抛出异常。

④.ClassCastException:类转换失败时抛出此异常

⑤.ArrayIndexOutOfBoundsException:数组索引越界异常。当试图操作数组的索引值为负数或大于等于数组大小时会抛出。

⑥.NegativeArraySizeException:数组长度为负值异常。一般在初始化数组大小为负值时抛出。

⑦. ArrayStoreException:数组类型不匹配值异常。例如将一个Object数组中加入一个Integer对象与一个String对象时,类型不匹配就会抛出。

⑧.IllegalArgumentException:非法参数异常。会在使用Java类库方法时传入参数值越界时或者参数数量,类型不匹配时抛出。

 

二. 异常处理

Java异常处理机制要求出现异常时在异常位置抛出异常,然后捕获异常并处理

从方法中抛出的任何异常都必须使用throws子句。捕捉异常通过try-catch语句或者try-catch-finally语句实现。

总体来说,Java规定:对于可查异常必须捕捉、或者声明抛出。允许忽略不可查的RuntimeException和Error。

2.1异常捕获

    

2.1.1 try-catch语句

在Java中,异常通过try-catch语句捕获。其一般语法形式为:

  1. try {  
  2.     // 可能会发生异常的程序代码  
  3. } catch (ExceptionTypeA e1){  
  4.     // 处理抛出的异常类型A
  5. }  
  6. catch (ExceptionTypeA e2){  
  7.      //处理的抛出的异常类型B 
  8. catch (ExceptionTypeA e3){  
  9.      //处理抛出的异常类型C
  10. }  
  11.  

try后的大括号内的程序块即可能发生异常的代码块,如果在代码块中发生异常,JVM将创建相应的异常对象,如果与catch块要捕获的异常对象相匹配,则进入处理块,否则将向上抛出,如上级无处理则线程死亡。

需要注意的是,一旦某个catch捕获到匹配的异常类型,将进入异常处理代码。一经处理结束,就意味着整个try-catch语句结束。其他的catch子句不再有匹配和捕获异常类型的机会。

Java通过异常类描述异常类型,异常类的层次结构如图1所示。对于有多个catch子句的异常程序而言,应该尽量将捕获底层异常类的catch子 句放在前面,同时尽量将捕获相对高层的异常类的catch子句放在后面。否则,捕获底层异常类的catch子句将可能会被屏蔽。

finally语句 表示无论异常是否发生都会执行的代码段

·               try {  

·     // 可能会发生异常的程序代码  

·  } catch (Exceptione) {  

·      

  } finally {  

·     // 无论异常是否发生,都将执行的代码块  

·  } 

fnally语句一般用来保护程序,避免使异常直接暴露或者线程终止

注意:finally语句只会在对应的try catch块执行时才会执行,程序在本finally 对应的try catch块之前抛出异常时,不会执行finally块。

当在try块或catch块中遇到return,continue,break等程序转移语句时,finally块将在方法返回之前被执行。

public class Test {

public static void main(String[] args) { 

System.out.println("reture value of test() : "+ test());

    }

    

public static int test(){

int i = 1;

        

try { 

System.out.println("try block"); 

            i= 1 / 0;

return 1; 

}catch (Exception e){

System.out.println("exception block");

return 2;

}finally { 

System.out.println("finally block"); 

        }

    }

}

执行结果

try block

exception block

finally block

reture value of test() : 2

 

在以下4种特殊情况下,finally块不会被执行:
1)在finally语句块中发生了异常。
2)在前面的代码中用了System.exit()退出程序。
3)程序所在的线程死亡。
4)关闭CPU。

 

又及:这只是通常意义上的try catch finally运行规则,实际上finally的运行规则十分复杂,需要深入JVM虚拟机运行原理进行探究,初步学习只需知道一上内容即可。

2.2抛出异常

  任何Java代码都可以抛出异常,如:自己编写的代码、来自Java开发环境包中代码,或者Java运行时系统。无论是谁,都可以通过Java的throw语句抛出异常。从方法中抛出的任何异常都必须使用throws子句。

2.2.1. throws语句声明异常

   如果一个方法可能会出现异常,但没有能力处理这种异常或者不需要在本层级处理异常可以通过throws子句来声明向上层抛出异常。

     throws语句用在方法定义时声明该方法要抛出的异常类型,如果抛出的是Exception异常类型,则该方法被声明为抛出所有的异常。多个异常可使用逗号分割。

 

例:test方法向上级抛出异常,上级方法需要捕获并处理异常

publicclass Test {

    public static void main(String[] args)  {

        try {

            System.out.println("returnvalue of test(): " + test());

        }

        catch (Exception e)

        {

            e.printStackTrace();

        }

    }

 

    public static int test() throws Exception{

        int i = 1;

 

        try {

            System.out.println("tryblock");

            return i;

        }finally {

            System.out.println("finallyblock");

        }

    }

}

2.2.2throw语句主动抛出异常

 throw总是出现在函数体中,用来抛出一个Throwable类型的异常。经常被用来处理用户自定义的异常,在程序逻辑满足某些条件时,主动的抛出异常。

 

程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中(可能在上层调用函数中)从里向外寻找含有与其匹配的catch子句的try块。
  我们知道,异常是异常类的实例对象,我们可以创建异常类的实例对象通过throw语句抛出。该语句的语法格式为:
    throw new exceptionname;
    例如抛出一个IOException类的异常对象:
    throw new IOException;
    要注意的是,throw 抛出的只能够是可抛出类Throwable或者其子类的实例对象。下面的操作是错误的:
    throw new String("exception");

    这是因为String不是Throwable 类的子类。

     如果抛出了检查异常,则还应该在方法头部声明方法可能抛出的异常类型。该方法的调用者也必须检查处理抛出的异常。

       如果所有方法都层层上抛获取的异常,最终JVM会进行处理,处理也很简单,就是打印异常消息和堆栈信息。如果抛出的是ErrorRuntimeException,则该方法的调用者可选择处理该异常。


三.异常处理的技巧

程序中异常的出现,通过异常堆栈信息,我们可以明确一下几个问题

  • 什么出了错?
  • 在哪出的错?
  • 为什么出错?

异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪“抛出,异常信息回答了“为什么“会抛出

一个异常的错误信息堆栈,表明了异常的类型(数据库连接异常),异常的错误信息(端口不允许连接),异常的线程,异常发生的位置,具体到每个调用块的行数。

 

 

Java提供了大量异常子类,如需更加具体,你也可以定义自己的异常类。通过大量的具体的异常子类,Java让明确捕获异常变得容易,因为我们可以对同一try块定义多个catch块,从而对每种异常分别进行恰当的处理。

 



原创粉丝点击