第九章、异常处理


Java 语言提供了一套完善的异常处理机制。正确运用这套机制,有助于提高程序的健壮性。

9.1 Java 异常处理机制概述

Java 语言按照面向对象的思想来处理异常,使得程序具有更好的可维护性。Java 异常处理机制具有以下优点:

  • 把各种不同类型的异常情况进行分类,用 Java 类来表示异常情况,这种类被称为异常类。把异常情况表示成异常类,可以充分发挥类的可扩展和可重用的优势。
  • 异常流程的代码和正常流程的代码分离,提高了程序的可读性,简化了程序的结构。
  • 可以灵活地处理异常,如果当前方法有能力处理异常,就捕获并处理它,否则只需抛出异常,由方法调用者来处理它。

下面是一个车子出故障 CarWrongException 异常类和上班迟到 LateWrongException 异常类。

public class Worker {
    private Car car;
    public Worker(Car car) {
        this.car = car;
    }

    public void gotoWork() throws LateException {
        try {
            car.drive();
        } catch (CarWrongException e) {
            walk();
            Date date = new Date(System.currentTimeMillis());
            String reason = e.getMessage();

            //处理异常的过程中产生的新异常,gotoWork()不会再处理,而是声明抛出LateException
            //那谁来处理Worker类的LateException呢?显然是职工的老板
            throw new LateException(data, reaseon);
        }
    }

    public void walk() {
        /* on foot */
    }
}
public class Car {
    public void drive() throws CarWrongException{
        if (brakeFail())
            throw new CarWrongException("brake failed");
        if (engineFail())
            throw new CarWrongException("engine failed")
    }
}
public class CarWrongException extends Exception {
    public CarWrongException() {}
    public CarWrongException(String msg) {
        super(msg);
    }
}
public class LateWrongException extends Exception {
    private Date arriveTime;
    private String reason;

    public LateException(Date arriveTime, String reason) {
        this.arriveTime = arriveTime;
        this.reason = reason;
    }
    public Date getArriveTime() {
        return arriveTime;
    }
    public Strng getReason() {
        return reason;
    }
}

Java 虚拟机的方法调用栈

Java 虚拟机用方法调用栈(method invocation stack)来跟踪每个线程中一系列的方法调用过程。该堆栈保存了每个调用方法的本地信息(比如方法的局部变量)。每个线程都有一个独立的方法调用栈。

如果方法中的代码块可能抛出异常,有两种处理办法:

  • 在当前方法中通过 try-catch 语句捕获并处理异常,例如
    public class Sample {
        public void methodA (int money) {
            if (--money <= 0) throw new SpecialException("Out of Money :( ");
        } catch (SpecialException e) {
            /* deal exception: earn Money! :|  */
        }
    }
    
  • 在方法的声明处通过 throws 语句声明抛出异常,例如
    public class Sample {
        public void methodA(int money) throws SpecialException {
            if (--money <= 0) throw new SpecialException("Out of Money :( ");
        }
    }
    

当一个方法正常执行完毕后, Java 虚拟机会从调用栈中弹出该方法的栈结构,然后继续处理前一个方法。如果在执行方法的过程中抛出异常, Java 虚拟机必须找到能捕获该异常的 catch 代码块 。它首先查看当前方法是否存在这样的 catch 代码块,如果存在,就执行该 catch 代码块;否则,Java 虚拟机会从调用栈中弹出该方法的栈结构,继续到前一个方法中查找合适的 catch 代码块。

当 Java 虚拟机追溯到调用栈的最底部的方法时,如果仍然没有找到处理该异常的代码块, 将按以下步骤处理

  1. 调用异常对象的 printStackTrace() 方法,打印来自方法调用栈的异常信息。
    Exception in thread "main" Specia!Exception: Out of money 
        at Sample.methodA(Sample.java:3) 
        at Sample.main(Sample.java:11) 
    
  2. 如果该线程不是主线程,那么终止这个线程,其他线程继续正常运行。如果该线程是主线程(即方法调用栈的最底部为 main() 方法),那么整个应用程序被终止。

一般说来,在 Java 程序中使用 try-catch 语句不会对应用的性能造成很大的影响。仅当异常发生时, Java 虚拟机需要执行额外的操作,来定位处理异常的代码块,这时会对性能产生负面影响。如果抛出异常的代码块和捕获异常 的代码块位千同一个方法中,那么这种影响就会小一 些;如果 Java 虚拟机必须搜索方法调用栈来寻找异常处理代码块,对性能的影响就比较大了。尤其是当异常处理代码块位于调用栈的最底部时,Java 虚拟机定位异常处理代码块需要大量的工作。

9.2 运用 Java 异常处理机制

本节介绍如何在应用程序中运用这种机制,来处理实际的异常情况。

try-catch 语句:捕获异常

try {
    /* code that may cause exception */
} catch (Exception e) {
    /* deal with exception */
}

finally 语句:任何情况下必须执行的代码

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

public class Sample {
    public void work() throws LeaveEarlyException {
        try {
            openDoor();
            slack();
        } catch (DischargeException e) {
            throw new LeaveEarlyException();
        } finally {
            closeDoor();
        }
    }
}

throws 子句:声明可能会出现的异常

如果一个方法可能会出现异常,但没有能力处理这种异常,可以在 方法声明处throws 子句来声明抛出异常。一个方法可能会出现多种异常, throws 子句允许声明抛出多个异常。

throw 语句:抛出异常

throw 语句用于抛出异常。值得注意的是,由 throw 语句抛出的对象必须是 java.lang.Throwable 类或者其子类的实例。

try {
    checkBalance();
} catch (NoMoneyException e) {
    buyLotteryTicket();
    if (money < 0) throw e;                //若果异常没能处理则继续抛出
}

异常处理语句语法规则

  • try 代码块后面可以有零个或多个 catch 代码块,还可以有零个或至多一个 finally 代码块。如果 catch 代码块和 finally 代码块并存, finally 代码块必须在 catch 代码块后面。
  • try 代码块后面可以只跟 finally 代码块。
    public class Sample {
        public static void main(String[] args) throws SpecailException {
            try {
                new Sample().methodA(1);
                System.out.println("main");
            } finally {
                System.out.println("finally");
            }
        }
    }
    
  • 在 try 代码块中定义的变量的作用域为 try 代码块,在 catch 代码块和 finally 代码块中不能访问该变量。
  • 当 try 代码块后面有多个 catch 代码块时,Java 虚拟机会把实际抛出的异常对象依次和各个 catch 代码块声明的异常类刑匹配,如果异常对象为某个异常类型或其子类的实例,就执行这个 catch 代码块,不会再执行其他的 catch 代码块。
  • 为了简化编程,从 JDK7 开始,允许在一个 catch 子句中同时捕获多个不同类型的异常,用符号 | 来分割。
  • 如果一个方法可能出现受检查异常,要么用 try-catch 语句捕获,要么用 throws 子句声明将它抛出,否则会导致编译错误。
    void m1() throws IOException{}                          //合法
    void m2() { m1(); }                                 //编译错误,必须捕获或声明抛出异常
    void m3() throws IOException{ m1(); }                   //合法
    void m4() throws Exception{ m1(); }                     //合法
    void m5() {try { m1();}  catch(IOException e) {..} }    //合法
    
  • 针对前面一条语法规则,从 JDK7 开始,如果在 catch 子句中捕获的异常被声明为 final 类型,那么当 catch 代码块中继续抛出该异常时,可以不用在定义方法时用 throws 子句声明将它抛出。
    void method () {                            //无需声明catch代码中抛出的用final修饰的异常
        try {
            ..
        } catch (final Throwable ex) {          //被捕获异常使用final修饰
            ..
            throw ex;                           //继续抛出异常
        }
    }
    
  • throw 语句后面不允许紧跟其他语句,因为这些语句永远不会被执行。

异常流程的运行过程

异常流程由 try-catch-finally 语句来控制。如果程序中还包含 returnSystem.exit()语句,会使流程变得更加复杂。本节结合具体例子来说明异常流程的运行过程。

  1. finally 语句不被执行的唯一情况是先执行了用于终止程序的 System.exit() 方法。
    public static void main(String[] args) {
        try {
            System.out.println("begin");
            System.exit(0);
        } finally {
            System.out.println("finally");          //不会执行
        }
        System.out.println("end");                  //不会执行
    }   
    //最终打印解果:begin
    //调用了java.lang.System类静态方法exit()终止当前的java虚拟机进程
    
    public class Sample {
        public static void main(String[] args) throws Exception {
            try {
                System.out.println("begin");
                new Sample().method();                  //假设出现异常
                System.exit(0);
            } catch (Exception e) {
                System.out.println("Wrong");
                throw e;                                //如果把此行注释掉,将得到不同的运行结果
            } finally {
                System.out.println("finally");          
            }
            System.out.println("end");                  
        }   
    }
    //保留该行,打印结果:begin wrong finally java.lang.SpecialException ..
    //注释改行,打印结果:begin wrong finally end
    
  2. return 语句用于退出本方法。 在执行 try 或 catch 代码块中的 return 语句时,假如有 finally 代码块,会先执行 finally 代码块。
    public class WithReturn { 
        public int methodA(int money) throws SpecialException{ 
            if(--money <= 0) throw new SpecialException("Out of money"); 
            return money;
        }     
        public int methodB(int money){ 
            try{ 
                System.out.println("begin"); 
                int result=methodA(money);              //假设抛出异常
                return result;                          //没有执行
            } catch (SpecialException e) { 
                System.out.println(e.getMessage()); 
                return -100; 
            }finally{ 
                System.out.println("Finally"); 
            }
        }  
        public static void main(String[] args) {
            System.out.println(new WithReturn().methodB(1)); 
        } 
    } 
    //打印结果:begin, out of money, finally, -100
    
  3. finally 代码块虽然在 return 语句之前被执行,但 finally 代码块不能通过重新给变量赋值的方式来改变 return 语句的返回值。
    public class Sample {
        public static int test() {
            int a = 0;
            try {
                return a;
            } finally {
                a = 1;
            }
        }
        public static void main(String[] args) throws Exception {
            System.out.println(test()); 
        } 
    }
    //打印结果:0
    
  4. 建议不要在 finally 代码块中使用 return 语句,因为它会导致以下两种潜在的错误。第一种错误是覆盖 try 或 catch 代码块的 return 语句;第二种错误是丢失异常。
    public class FinallyReturn { 
        public int methodA(int money) throws SpecialException{ 
            if(--money <= 0) throw new SpecialException("Out of money"); 
            return money; 
        }
        public int methodB(int money){ 
            try {
                return methodA(money);                  //可能抛出异常
            } catch(SpecialException e){ 
                return -100; 
            } finally { 
                return 100;             //会覆盖 try 和 catch 代码块的 return 语句
            }
        }  
        public static void main(String[] args) { 
            FinallyReturn s = new FinallyReturn();  
            System.out.println(s.methodB(1));           //打印 100
            System.out.println(s.methodB(2));           //打印 100
        }
    }
    
    public class ExLoss{
        public int methodA(int money) throws SpecialException { 
            if(--money <= 0) throw new SpecialException("Out of money"); 
            return money;
        } 
        public int metbodB(int money) throws Exception { 
            try{ 
                return methodA(money);              //可能抛出异常
            } catch (SpecialException e) { 
                throw new Exception("Wrong"); 
            }finally{
                return 100;
            } 
        }
        public static void main(String[] args) { 
            try { 
                System.out.println(new ExLoss().methodB(1));    //打印 100
                System.out.println("No Exception"); 
            }catch (Exception e) { 
                System.out.println(e.getMessage()); 
            } 
        } 
    } 
    //methodB()方法的 catch 代码块继续抛出异常,按理说 main() 方法的 catch 代码块应该捕获并处理该异常
    //但由于 methodB() 方法的 finally 代码块有返回值,异常被丢失了,
    //main()方法没有捕获到 methodB()方法的异常。
    //以上程序的打印结果为:100, No Exception
    

9.3 Java 异常类

在程序运行中,任何中断正常流程的因素都被认为是异常。按照面向对象的思想,Java 语言用 Java 类来描述异常。所有异常类的祖先类为 Java.lang.Throwable 类,它的实例表示具体的异常对象,可以通过 throw 语句抛出。

Throwable 类提供了访问异常信息的一些方法:getMessage(): 返回 String 类型的异常信息。printStackTrace():打印跟踪方法调用栈而获得的详细异常信息。在程序调试阶段,此方法可用于跟踪错误。

Throwable 类有两个直接子类:

  • Error 类:表示单靠程序本身无法恢复的严重错误,比如内存空间不足,或者 Java 虚拟机的方法调用栈溢出。在大多数情况下,遇到这样的错误时,建议让程序终止。
  • Exception类:表示程序本身可以处理的异常,本章所有例子都针对这类异常。当程序运行时出现这类异常,应该尽可能地处理异常,并且使程序恢复运行 ,而不应该随意终止程序。

Error 类及其子类表示程序本身无法修复的错误,它和运行时异常的相同之处是:Java 编译器都不会检查它们,当程序运行时出现它们,都会终止程序。两者的不同之处在于:Error 类及其子类表示的错误通常是由 Java 虚拟机抛出的,在 JDK 中预定义了一些错误类,比如 OutOfMemoryError 和 StackOutotMemoryError。在应用程序中,一般不会扩展 Error 类,来创建用户自定义的错误类。而 RuntimeException 类表示程序代码中的错误,它是可以扩展的,用户可以根据特定的问题领域,来创建相关的运行时异常类。

下面对一些常见的异常做简要的介绍:

  • IOException:操作输入流和输出流时可能出现的异常。
  • ArithmeticException: 数学异常。如果把整数除以 0,就会出现这种异常。
  • NullPointerException:空指针异常。当引用变量为 null 时,试图访问对象的属性或方法,就会出现这种异常。
  • IndexOutOfBoundsException:下标越界异常,它的子类 ArrayIndexOutOfBoundsException 表示数组下标越界异常。
  • ClassCastException: 类型转换异常。
  • IllegalArgumentException:非法参数异常,可用来检查方法的参数是否合法。

Exception 类还可分为两种:运行时异常受检查异常。运行时异常是应该尽量避免的,在程序调试阶段,遇到这种异常时,正确的做法是改进程序的设计和实现方式,修改程序中的错误,从而避免这种异常。捕获它并且使程序恢复运行并不是明智的办法。

RuntimeException 类及其子类都称为运行时异常,这种异常的特点是 Java 编译器不会检查它, 也就是说,当程序中可能出现这类异常时,即使没有用 try-catch 语句捕获它 ,也没有用 throws 子句声明抛出它,也会编译通过。由于程序代码不会处理运行时异常,因此当程序在运行时出现了这种异常时,就会导致程序异常终止。

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

9.4 用户自定义异常

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

//以下代码定义了 一个服务器超时异常:
public class ServerTimedOutException extends Exception {
    private String reason;
    private int port;
    public ServerTimedOutException (String reason, int port) {
        this.reason = reason;
        this.port = port;
    }
    public String getReason() {
        return reason;
    }
    public int getPort() {
        return port;
    }
}
//使用 throw语句抛出异常:不能连接80端口
throw new ServerTimedOutException("Could not connect", 80);

异常转译和异常链

在分层的软件结构中,会存在自上而下的依赖关系,也就是说上层的子系统会访问下层子系统的 API。当位于最上层的子系统不需要关心来自底层的异常的细节时,常见的做法是捕获原始的异常,把它转换为一个新的不同类型的异常,再抛出新的异常,这种处理异常的办法称为 异常转译

例如上传文件时可能会捕获 IOException 或者SQLException 。但是用户没有必要关心异常的底层细节,他们仅需要知道上传图像失败,具体的调试和排错由系统管理员或者软件开发人员来处理。因此上传操作捕获原始异常后,在 catch 代码块吧原始异常信息记入日志,然后向用户抛出 UploadException 异常。

JDK1.4 以上版本中的 Throwable 类支持异常链机制。所谓 异常链 就是把原始异常包装为新的异常类,也就是说在新的异常类中封装了原始异常类,这有助于查找产生异常的根本原因。

//支持异常链的异常类 BaseException.java

import java.io.*;
public class BaseException extends Exception {
    protected Throwable cause = null;                       //保存原始Java异常

    public BaseException() {}
    public BaseException(String msg) {
        super(msg);                                         //调用父类构造方法
    }
    public BaseException(Throwable cause) {                 //参数cause指定原始异常
        this.cause = cause;
    }
    public BaseException(String msg, Throwable cause) {     //参数cause指定原始异常
        super(msg);
        this.cause = cause;
    }
    public Throwable initCause(Throwable cause) {
        this.cause = cause;
        return this;
    }
    public Throwable getCause() {
        return cause;
    }
    public void printStackTrace() {
        printStackTrace(System.err);
    }
    public void printStackTrace(PrintStream outStream) {
        printStackTrace(new PrintWriter(outStream));
    }
    public void printStackTrace(PrintWriter writer) {
        super.printStackTrace(writer);
        if (getCause() != null)
            getCause().printStackTrace(writer);
        writer.flush()
    }
}
//假设 UploadException 类扩展了 BaseException 类

public class UploadException extends BaseException{
    public UploadException(Throwable cause) {
        super(cause);
    }
    public UploadException(String msg) {
        super(msg);
    }
}
//以下是把 IOException 包装为 UploadException 的代码

try {
    //上传图像文件
} catch(IOException e) {
    //把原始异常信息记录到日志中 ,便于排错
    ...
    //把原始异常包装为 UploadException
    UploadException ue = new UploadException(e);
    throw ue;
}

处理多样化异常

在实际应用中,有时需要一个方法同时抛出多个异常。例如,用户提交的 HTML 表单上有多个字段域,业务规则要求每个字段域的值都符合特定规则,如果不符合规则,就抛出相应的异常。有效的做法是每次当用户提交表单后,验证所有的字段域, 然后向用户显示所有的验证错误信息。不幸的是,在 Java 方法中一次只能抛出一个异常对象。因此需要开发者自行设计支持多样化异常的异常类。

//提供了一种支持多样化异常的异常类 BaseException.java

package multiex; 
import java.util.List; 
import java.util.ArrayList;
import java.io.PrintStream; 
import java.io.PrintWriter;

public class BaseException extends Exception { 
    protected Throwable cause= null; 
    private List<Throwable> exceptions = new ArrayList<Throwable>(); 
    
    public BaseException() {} 
    public BaseException(String msg){ 
        super(msg);
    } 
    public BaseException(Throwable cause) { 
        this.cause= cause;
    } 
    public BaseException(String msg, Throwable cause){ 
        super(msg); 
        this.cause = cause; 
    }

    public List getExceptions() { 
        return exceptions;
    } 
    public void addException(BaseException ex){ 
        exceptions.add(ex); 
    } 
    public Throwable initCause(Throwable cause) { 
        this.cause = cause; 
        return this;
    } 
    public Throwable getCause() {                           //返回原始的异常
        return cause; 
    }
    
    public void printStackTrace() { 
        printStackTrace(System.err); 
    }
    public void printStackTrace(PrintStream outStream) { 
        printStackTrace(new Print Writer(outStream)); 
    } 
    public void printStackTrace(PrintWriter writer) { 
        super.printStackTrace(writer); 
        if (getCause() != null)  
            getCause().printStackTrace(writer); 
        writer.flush(); 
    }
}  
// BaseException 用法
public void check() throws BaseException {
   
    BaseException be = new BaseException();
   
    try {
        checkField1();
    } catch (FieldException e) {
        be.addException(e);
    }

    try {
        checkField2();
    } catch (FieldException e) {
        be.addException(e);
    }
    if (be.getException().size() > 0)
        throw be;
}

9.5 异常处理原则

  • 异常只能用于非正常情况,不能用异常来控制程序的正常流程。
  • 在 JavaDoc 文档中应该为方法可能抛出的所有异常提供说明文档。无论是受检查异常,还是运行时异常,都应该通过 JavaDoc 的 @throws 标签来描述产生异常的条件。
  • 应该尽可能地避免异常,尤其是运行时异常。
    • 许多运行时异常是由于程序代码中的错误引起的,只要修改了程序代码的错误,或者改进了程序的实现方式,就能避免这种错误。
    • 有些异常是由于当对象处于某种状态下,不适合某种操作造成的,通过提供状态测试方法来避免此类异常。
  • 应该尽力保持异常的原子性。异常的原子性是指当异常发生后,各个对象的状态能够恢复到异常发生前的初始状态,而不至于停留在某个不合理的中间状态。
    • 最常见的办法是先检查方法的参数是否有效,确保当异常发生时还没有改变对象的初始状态。
    • 编写一段恢复代码,由它来解释操作过程中发生的失败,并且使对象状态回滚到初始状态。
    • 在对象的临时副本上进行操作,当操作成功后, 把临时副本中的内容复制到原来的对象中。
  • 避免过于庞大的 try 代码块。
  • 在 catch 子句中指定昙体的异常类型。
  • 不要在 catch 代码块中忽略被捕获的异常

9.6 记录日志

在程序中输出日志总的来说有 3 个作用:

  • 监视代码中变量的变化情况,把数据周期性地记录到文件中供其他应用进行统计分析工作。
  • 跟踪代码运行时轨迹,作为日后审计的依据。
  • 承担集成开发环境中的调试器的作用,向文件或控制台打印代码的调试信息。

要在程序中输出日志,最普通的做法是在代码中嵌入许多打印语句,这些打印语句可以把日志输出到控制台或文件中。比较好的做法是构造一个日志操作类来封装此类操作,而不是让一系列的打印语句充斥代码的主体。

在强调可重用组件开发的今天,可以直接使用 Java 类库的 java.util.logging 日志操作包。这个包中主要有 4 个类:

  • Logger 类:负责生成日志,并能够对日志信息进行分级别筛选,通俗地讲,就是决定什么级别的日志信息应该被输出,什么级别的日志信息应该被忽略。
  • Handler 类:负责输出日志信息,它有两个子类: ConcoleHandler 类(把日志输出到 DOS 命令行控制台)和 FileHandler 类(把日志输出到文件中)。
  • Formatter 类:指定日志信息的输出格式。它有两个子类:SimpleFormatter 类(表示常用的日志格式)和 XMLFormatter 类(表示基于 XML 的日志格式)。
  • Level 类:表示日志的各种级别,它的静态常量如 Level.INFOLevel.WARNINGLevel.CONFIG 等分别表示不同的日志级别。
import java.util.logging.*;
class Logger Tester1{
    public static void main(String[] args) {
        Logger myLogger = Logger.getLogger("mylogger");     //声明一个日志记录器对象
        myLogger.setLevel(Level.WARNING);

        myLogger.info("this is a normal msg");              //INFO 级别的日志
        myLogger.warning("this is a warning msg");          //WARNING 级别的日志
        myLogger.severe("this is a severe msg");            //SEVERE 级别的日志
    }
}

以上代码将 myLogger 对象的日志级别设为 WARNING, 因此,只有 warning() 方法和 server()方法生成的日志会被输出。DOS控制台输出:

警告:this is a warning msg
严重:this is a severe msg
//如果要把日志输出到文件中,可以采用以下方式:
import java.util.logging.*;
import java.io.IOException;
class Logger Tester2{
    public static void main(String[] args) throws IOException {
        Logger myLogger = Logger.getLogger("mylogger");     //声明一个日志记录器对象
        
        FileHandler fileHandler = new FileHandler("C:\\test.log");
        fileHandler.setLevel(Level.INFO);               //设定向文件中写日志的级别
        myLogger.addHandler(fileHandler);               //把 FileHandler 与 Logger 对象关联

        myLogger.info("this is a normal msg");              //INFO 级别的日志
        myLogger.warning("this is a warning msg");          //WARNING 级别的日志
        myLogger.severe("this is a severe msg");            //SEVERE 级别的日志
    }
}

运行以上程序,将在 C:\test.log 文件中记录以下 XML 格式的日志:

<?xml version="l.0" encoding="GBK" standalone="no"?> 
<!DOCTYPE log SYSTEM "logger.dtd"> 
<log> 
<record>
    ...
    <message>this is a normal msg</message>
</record> 
<record>
    ...
    <message>this is a warning msg</message>
</record> 
<record> 
    ...
    <message>this is a severe msg</message>
</record> 
</1og> 

默认情况下, FileHandler 类与 XMLFormatter 类关联,即向文件中输出的日志是基于 XML 格式的。也可以自定义一个继承 Fomatter 类的子类,然后覆盖它的 format()方法,在该方法中指定客制化的日志输出格式。

import java.util.logging.*;
import java.io.IOException; 
public class LoggerTester3 { 
    static class MyFormatter extends Formatter {            //自定义的日志输出格式类
        public String format(LogRecord record) {            //覆盖 format()方法             
            return "<" + record.getLevel() + ">:" + record.getMessage()+ "\n"; 
        }
    }

    public static void main(String[] args) throws IOException { 
        Logger myLogger = Logger.getLogger("mylogger"); 
        FileHandler fileHandler = new FileHandler("C: \\test log");
        fileHandler.setFormatter(new MyFormatter());            //设置自定义的日志输出格式

        myLogger.addHancller(fileHancller);                
        myLogger.info("this is a normal msg");              
        myLogger.warning("this is a waring msg");               
    }
}

运行以上程序,将在 C:\test.log 文件中输出以下格式的日志:

<INFO>:this is a normal msg
<WARNING>:this is a waring msg

9.7 使用断言

从 JDKl.4 开始,引入了断言机制,异常处理的一种高级形式。使用断言的语法有两种形式为:

assert 条件表达式
assert 条件表达式 : 包含错误信息的表达式 

以上 assert 为 Java 关键字。条件表达式的值是一个布尔值。当条件表达式的值为 false 时,就会抛出 一个 Assertion Error,这是一个错误,而不是一个异常。在第二种形式中,第二个表达式会被转换成作为错误消息的字符串。

public class AssertTester {
    public int divide(int a, int b) {
        assert b != 0 : "CANNOT divide zero!!!";          //使用断言
        return a / b;
    }
    public static void main(String[] args) {
        AssertTester t = new AssertTester();
        System.out.println(t.divide(1, 0));
    }
}

当程序运行时,断言在默认情况下是关闭的,这意味着程序中的断言语句不会被执行。在 Java 命令中启用断言需要使用 -ca 参数,禁用断言使用 -da 参数。例如,用命令 java -ea AssertTester 来执行 AssertTester 类,将会输出以下错误信息:

Exception in thread "main" java.lang.AssertionError: CANNOT divide zero!!!
at AssertTester.divide(AssertTester.java:4) 
at AssertTester.main(AssertTester.java:10) 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值