Java基础(十)异常处理

Java基础(十) 异常处理

  • 异常的处理流程;
  • 自定义异常;
  • 断言;

异常:我们的生活中,经常会遇到异常现象。比如我们在上班的路上,会遇到很多情况。我们的目标是:住处—>公司。但是你在上班的路上,会遇到各类的异常情况。比如:天气灾难(台风等)、地铁堵的你进不去、地铁发生事故。在我们的编程时也是如此,程序在运行的时候也会遇到各种各样的异常。

为了解决异常会产生的各种不良后果,我们可以有如下2种做法:

  1. 避免出现异常。代码内保证,比如调用实例的.set()方法前,先进行判断实例是否为空。即if(实例!=null){}
  2. 处理异常。在程序编写的过程中,考虑可能出现的异常类型,捕获异常并处理异常,保证程序的鲁棒性。

Java提供了一套完整的异常处理机制。正确使用这套机制,可以大大提高代码的健壮性。所谓程序的健壮性,指程序在多数情况下能够正常运行,返回预期的结果;如果遇到异常情况,程序可以采取周到的解决措施。

Java代码通过面向对象的思想来处理异常,使得代码具有更好的可维护性。Java的异常处理机制主要有如下几点的优点:

  1. 把不同类型的异常进行分类,用Java类表示异常,这种类被称为异常类。把异常情况表示为异常类,可以充分发挥类的可扩展和重用的优势。
  2. 异常的流程代码可以和业务代码进行分离,提高了程序的可重用性,简化了程序的结构。
  3. 可以灵活的处理异常。即可以处理异常;也可以不处理异常,仅将异常抛出,由异常的上一级或者调用着进行处理。

1. 异常的基本概念

  • 异常的产生条件
    • 整数相除计算中,除数为0;
    • 通过一个没有指向任何具体对象的引用去调用 实例对象的方法(NullPointer异常);
    • 通过数据的长度作为下标访问数组元素(OutOfBoundarys 数组越界);
    • 将一个引用强制准换位不想干的对象。(如 Integer类型 强制转换为 ArrayList类型)
  • 异常会改变程序正常的处理流程;异常产生后,程序的正常处理流程被打破,要不程序终止,要不程序会跳转到异常处理模块。
  • 当一个异常出现后,异常会被Java虚拟机封装成异常对象抛出。
  • 用来处理异常的代码被称为异常处理器。
  • 通过异常处理器来捕捉异常。
package com.us.demo.exception;

public class ExceptionTest {
    public static int divide(int firstNumber, int secondNumber){
        int result = 0;
        try{
            result =  firstNumber / secondNumber;
        }catch(ArithmeticException ex){
            System.out.println("Sorry,error in divide.");
            ex.printStackTrace();
        }
        return result; 
    }
    public static void main(String []args){
        divide(1,2);
        divide(1,0);
        divide(1,2);
        divide(1,2);
    }

} 

2. 异常的基本语句

2.1 try…catch 语句

在Java语言内,常用try...catch语句来处理异常。具体格式如下所示:

try{
// 可能会出现异常的代码
}catch(异常类型 异常参数){
// 异常处理代码
}catch(异常类型 异常参数){
// 异常处理代码
}
  • 如果try代码块内的代码没有抛出异常,try代码块会被顺序执行完,catch代码块的程序不会被执行;
  • 如果try代码块内的代码抛出catch处声明的异常类型,程序会跳过try代码块,直接执行catch代码块的内容;
    • 可以存在多个catch代码块,具体执行某一个。需要看try代码块内抛出的异常类型决定。
    • 同一个异常类型只能被一个异常处理器处理。不能声明2个异常处理器去处理同类异常。
    • 多个catch语句处理的异常类型不能越来越小。因为大大异常类型会被优先捕获,这样后面的异常将永远不会生效。
    • 不能捕获一个try代码块内不会抛出的异常类型。
  • 如果try代码块内抛出的异常类型,是catch代码块内未声明的异常类型,异常会被抛给调用者;那个调用了这段语句,谁就应当处理这段异常代码。

2.2 finally 语句(任何情况下都会被执行的代码)

由于异常会强制中断正常流程,这会使得某些不管在任何情况下都必须执行的步骤被忽略,从而影响程序的健壮性。

例如小王开了一家小店,在店里上班的正常流程为:打开店门、工作8个小时、关门。异常流程为:小王在工作时突然犯病,因而提前下班。例:

 public void work() {
      try {
        开门();
        工作8个小时();
        关门();
      } catch(Exception e) {
        //去医院
      }     
 }

假如小王在工作时突然犯病,那么流程会跳转到catch代码块,这意味着关门的操作不会被执行,这样的流程显然是不安全的,必须确保关门的操作在任何情况下都会被执行。finally代码块能保证特定的操作总是会被执行,它的形式如下:

public void work() {
      try {
        开门();
        工作8个小时();
      } catch(Exception e) {
        //去医院
        return;
      } finally {
         关门();
      }
 }

当然finally代码块中代码也可位于catch语句块之后,例如:

public void work() {
    try {
      开门();
      工作8个小时();
    } catch(Exception e) {
      //去医院
    } 
    关门();
}

这在某些情况下是可行的,但是不推荐,这样。因为:

  • 把try代码的部分分离了开来,这样使得程序松散,可读性较差;
  • 影响程序的健壮性,例如catch部分继续有异常抛出,那么关门的动作就不会被执行。

3. 异常的其他信息

3.1 异常的调用栈

异常的处理过程中,所经历的一系列的方法的调用过程,被称为异常的调用栈。

  • 异常的传播(哪个调用,哪个处理)
    • 异常发生后,异常所在的方法可以处理;
    • 异常所在的方法没用处理,异常会被抛送给方法调用着,调用者可以处理;
    • 如果调用者没用处理,异常将会进一步抛出;如果异常一直没用被处理,那么异常将会被抛送给Java虚拟机,这将会导致程序的中断。
  • 如果异常没有被捕获,那么异常将会使你的程序停止运行。
    • 异常产生后,如果异常一直没用被捕获。那么该异常将会被抛Java虚拟机。程序将被终止。
  • 经常会使用的异常API
    • getMessage(): 获得具体的异常信息,可能为null;
    • printStatckTrace:打印异常在传播过程中所经历的一系列方法的信息,简称为异常处理方法调用栈信息;在程序调试阶段,此方法可用于追踪错误信息。

3.2 异常的层级关系

所有异常类的祖先为java.lang.Throwable类。它有2个直接的子类:

3.2.1 Error类

表示仅靠程序无法恢复的严重错误,比如内存空间不足,或者Java虚拟机调用方法栈溢出。在多数情况下,调用此方法,程序建议立即中止。

3.2.1 Exception类

表示程序可以处理的异常,这类异常主要分为2种:运行时异常和受检查异常。

  • 运行时异常

    • 基本概念

    RuntimeException及其子类被称为运行时异常,这种异常的特点是编译器不会去检查它们。也就是说,在程序内出现这样的异常时,即使没有try...catch语句去捕获它们,也没有Throws语句去抛出它们,程序还是会被编译通过。例如divide()方法的参数b为0, 执行a/b操作时会出现ArithmeticException异常,它属于运行时异常,Java编译器不会检查它。

    public int divide(int a, int b) {
             return a/b;      //当参数b为0, 抛出ArithmeticException
    }
    • 深入解析

    运行时异常表示无法让程序恢复运行的异常,导致这类异常的原因时进行了不当的操作。一旦出现错误操作,建议终止程序,因此Java编译器不检查此类异常。

    运行时异常,应当尽量的避免。在程序的调试阶段,遇到此类异常的正确做法是改进程序的设计和实现方式,修改程序的错误,避免出现这类异常。捕获它并使程序继续运行并不是明智的选择。

    • 对比

    与Error类对比:

    相同点:

    1)Java编译器不会去检查它们;

    2)运行时出现它们,都会导致程序终止;

    区别:

    1)Error类及其子类一般都是由Java虚拟机抛出的,在JDK预定义了一些错误类,比如OutOfMemoryErrorStackOutofMemoryError; runtimeException表示程序代码的错误;

    2)Error类一般不会扩展来创建用户的自定义用户类;RuntimeException时可以扩展的,用户可以根据自己的需求来创建相关的运行时异常。

  • 受检查异常
    除了RuntimeException及其子类外,其他Exception类及其子类都属于受检查异常(Checked Exception)。这种异常的特点是Java编译器会检查她们。也就是说,当程序内出现此类异常时,要么用try...catch语句去捕获异常,要么用Throws语句去抛出异常,否则会出现编译不通过的情况。

3.3 一些未经检查的RuntimeException

  1. java.lang.ArithimeticException

    算术异常,如除数为0;

  2. java.lang.NullPointerException

    空指针引用,如没用指定一个Reference,进行使用;即单有指针,但未有指向的数据空间,就调用程序。

  3. java.lang.ArrayIndexOutofBoundsException

    数组越界;因为数组的下标是从0开始计算,而size()或length是从1开始计算的。所以不小心会产生调用越界的异常情况。

  4. java.lang.ClassCastException

    数据类型转换异常。在强制类型转换 和 反射创建对象的时候会经常遇见此类异常。

  5. java.lang.NumberFormatException

    数据格式异常,如Integer.parseInt('a');

  6. java.lang.NegativeArraySizeException

    数组的长度在声明的时候,为负数。

4. 创建属于自己的异常

4.1 异常的处理和声明

  1. 自己主动使用throw语句的时候代码会抛出异常;
  2. 使用try...catch...finally语句在代码内处理异常 或者 throw语句在方法声明上抛出;

异常处理语句的语法规则如下:

  1. try代码块不能脱离catch代码块或finally代码块而单独存在。try代码块后面至少有一个catch代码块或finally代码块。

  2. try代码块后面可以有零个或多个catch代码块,还可以有零个或至多一个finally代码块。如果catch代码块和finally代码块并存,finally代码块必须在catch代码块后面。

  3. try代码块后面可以只跟finally代码块。
  4. 在try代码块中定义的变量的作用域为try代码块,在catch代码块和finally代码块中不能访问该变量。
  5. 当try代码块后面有多个catch代码块时,Java虚拟机会把实际抛出的异常对象依次和各个catch代码块声明的异常类型匹配,如果异常对象为某个异常或其子类的实例,就执行这个catch代码块,而不会再执行其他的catch代码块。
  6. 如果一个方法可能出现受检查异常,要么用try…catch语句捕获,要么用throws子句声明将它抛出。
  7. throw语句后面不允许紧跟其它语句,因为这些语句永远不会被执行。

4.2 编写/使用自己的异常

在特定的领域,可以通过扩展Exception类或者RuntimeException类来进行扩展创建自定义的异常。异常类包含了异常的相关信息。这有助于负责异常捕获的catch代码块,正确的处理异常。

package com.us.demo.exception;

public class MyExceptionTest{
    public static void addUser() throws MyException{
        System.out.println("添加用户");
    }
    public static void main(String []args){
        addUser();// it is ok
        try{
            addUser();// it is ok
        }catch(MyException myEx){
            myEx.printStackTrace();
        }
    } 
}

class MyException extends RuntimeException{
    public MyException(){

    }
    public MyException(String message){
        super(message);
    }

}

5. 断言

假设要进行如下的计算:

double y = Math.sqrt(x);

为了让程序健壮,你会先进行测试检查并抛出异常而不让x的值为负数。

if(x<0) throw new IllealArgumentException("x < 0");

但是,就算是测试结束了,以后实际运行时x的值不会小于0。这种测试代码会一直保留在你的程序中。如果程序中有太多的检查,程序的运行就会慢好多。

如果在测试阶段会有这种检查,而在发布阶段能自动删除这些东西。该多好! 这就是断言机制。

  1. 断言使用

    在JDK1.4中,Java语言引入一个新的关键字: assert。 该关键字有两种形式:

    assert 条件 以及 assert 条件: 表达式

    这两种形式都会对条件进行评估,如果结果为假则抛出AssertionError。 在第二种形式中,表达式会传入AssertionError的构造器并转成一个消息字符串。

    表达式字符串部分的唯一目的就是生成一个消息字符串。AssertionError对象并不存储表达式的值,因此你不可能在以后获取它。

    要断言x不是负数,只需要使用如下简单的语句:assert x >= 0 ;

    或者你可以将x的值传递给AssertionError对象,从而可以在以后显示: assert x >= 0 : x;

  2. 断言内容代码编译

    因为assert是一个新的关键字,因此在使用时需要告诉编译器你编译所使用jdk的版本号。

    javac -source 1.4 MyClass.java

    在jdk的后续版本中,对断言的支持成为默认特性(我们使用的是JDK5.0以上,使用不需要使用这个编译,默认就支持的)。

  3. 断言内容代码执行

    默认情况下,断言是关闭的。要通过-enableassertions或者-ea选项来运行程序以打开断言:

    java -enableassertions com.briup.ch07.Xxxx
    java -ea com.briup.ch07.Xxxx

    打开或关闭断言是类装载器的功能。当断言功能被关闭时,类装载器会跳过那些和断言相关的代码,因此不会降低程序运行速度。

    注意:使用eclipse运行代码的时候也是可以传参数的(包括俩种参数)

    java -xx com.briup.ch07.Test yy

    xx是给JVM传的参数 yy是给Test类的main方法传的参数

  4. 断言举例

package com.us.demo.exception;

public class AssertTest {
    public static void judge(int x){
        assert x >= 0 : true;
    }
    public static void main(String []args){
        judge(1);
        judge(-2);
//      //Exception in thread "main" java.lang.AssertionError
//      at com.us.demo.exception.AssertTest.judge(AssertTest.java:5)
//      at com.us.demo.exception.AssertTest.main(AssertTest.java:9)
    }
}

Eclipse 调试技巧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值