一、Java异常体系结构

Java异常体系结构

Java中的异常都是从Throwable类继承的,主要分为:

  1. Error

    Error是程序无法处理的异常,它是由Java虚拟机(JVM)产生和抛出的,例如OutOfMemoryError、StackOverflowError等。这些异常发生时,JVM一般会选择终止线程。

  2. Exception

    Exception是程序本身可以处理的异常,分为:

  • 运行时异常

    运行时异常都是从RuntimeException类继承而来,例如:NullPointerException、ClassCastException、IndexOutOfBoundsException等。这些异常被称为“不受检查异常”,它们属于错误,将被自动捕获,在程序中可以处理,也可以不处理。

  • 非运行时异常

    非运行时异常是RuntimeException以外的异常,这些异常必须处理,否则程序无法编译通过。由于这些异常是在编译时被强制检查的,所以被称为“被检查的异常”。例如:IOException、SQLException等。

二、自定义异常

自定义异常必须从已有的异常类继承,最好是继承意义相近的异常类。

  • 自定义异常1:
package com.strikeback.exception;

public class ChokingException extends Exception{
	public ChokingException() {
		super();
	}
	public ChokingException(String message) {
		super(message);
	}
}

package com.strikeback.exception;

public class Human {
	public void eatHamburg() throws ChokingException{
		System.out.println("eating hamburg, will be choking");
		throw new ChokingException();
	}	
	public void eatBread() throws ChokingException{
		System.out.println();
		System.out.println("eating bread, will be choking");
		throw new ChokingException("chocked by bread");
	}
	public static void main(String[] args) {
		Human human = new Human();
		try {
			human.eatHamburg();
		} catch (ChokingException e) {
			//使用标准输出流输出
			e.printStackTrace(System.out);
			System.out.println("chocked, please drink water");
		}
		try {
			human.eatBread();
		} catch (ChokingException e) {
			//信息被输出到标准错误流
			e.printStackTrace();
			System.out.println("chocked, please drink water");
		}
	}
}

自定义异常1

  • 自定义异常2:使用了Logger的Exception
package com.strikeback.exception;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Logger;

public class LogException extends Exception{

	private static Logger logger = Logger.getLogger("LogException");
	
	public LogException(){
		StringWriter trace = new StringWriter();
		printStackTrace(new PrintWriter(trace));
		logger.severe(trace.toString());
	}
}

自定义异常2

  • 自定义异常3:更常见的logException方式

    上一种方式在异常类中记录日志,不需要客户端开发者处理;更常见的方式是由开发者在异常处理程序中记录日志。

package com.strikeback.exception;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Logger;

public class TestLoggingException {

	private static Logger logger = Logger.getLogger("LoggingException");
	
	public static void logException(Exception e){
		StringWriter trace = new StringWriter();
		e.printStackTrace(new PrintWriter(trace));
		logger.severe(trace.toString());
	}
	
	public static void main(String[] args) {
		Object obj = null;
		try{
			obj.toString();
		}catch(NullPointerException e){
			logException(e);
		}
	}
}

自定义异常3

  • 自定义异常4

    对于自定义异常,还可以添加异常类的成员和额外的构造函数,也可以重写getMessage()方法,以产生更详细的异常信息。

package com.strikeback.exception;

public class MultiLangException extends Exception{

	private String enMsg;

	public MultiLangException(String msg, String enMsg){
		super(msg);
		this.enMsg = enMsg;
	}
	
	public String getEnMsg() {
		return enMsg;
	}
	
	@Override
	public String getMessage() {
		return String.format("%s[%s]", super.getMessage(), getEnMsg());
	}
}

package com.strikeback.exception;

public class TestMultiLangException {

	public static void main(String[] args) {
		try {
			throw new MultiLangException("自定义异常", "my exception");
		} catch (MultiLangException e) {
			e.printStackTrace();
			System.out.println("Message[enUs]: " + e.getEnMsg());
		}
	}
}

自定义异常4

三、栈轨迹(StackTrace)

  • getStackTrace()

    可以通过Throwable的getStackTrace()方法获得一个由栈轨迹中的元素构成的数组,其中的每一个元素都表示栈中的一桢。第0个元素是栈顶元素,是调用序列中的最后一个方法调用,数组中的最后一个元素是栈底元素,是调用序列中的第一个方法调用。

package com.strikeback.exception;

public class WhoCalled {

	public static void wash(){
		try {
			throw new Exception("no water");
		} catch (Exception e) {
			//异常抛出的原因
			System.err.println(e.getCause());
			//异常的消息信息
			System.err.println(e.getMessage());
			
			for(StackTraceElement ste : e.getStackTrace()){
				System.out.println(String.format("%s -- %s -- %s -- %s", ste.getFileName(), 
						ste.getClassName(), ste.getMethodName(), ste.getLineNumber()));
			}
		}
	}
	
	public static void cook(){
		wash();
	}
	
	public static void eat(){
		cook();
	}
	
	public static void main(String[] args) {
		eat();
	}
}

  • 重新抛出异常时的栈轨迹

    如果只是把当前异常对象重新抛出,printStackTrace()方法还是显示原异常的调用栈轨迹。

package com.strikeback.exception;

public class StackTraceTest {

	public static void stepOne() throws Exception{
		throw new Exception("thrown from stepOne()");
	}
	
	public static void stepTwo() throws Exception{
		try {
			stepOne();
		} catch (Exception e) {
			e.printStackTrace(System.out);
			throw e;
		}
	}
	
	public static void main(String[] args) {
		try {
			stepTwo();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

如果想更新异常信息,可以调用fillInStackTrace()方法,此方法是通过把当前调用栈信息填入原异常对象而建立的,它会返回一个Throwable对象。

	...
	public static void stepTwo() throws Exception{
		try {
			stepOne();
		} catch (Exception e) {
			e.printStackTrace(System.out);
			throw (Exception)e.fillInStackTrace();
		}
	}
	...

如果在捕获异常之后抛出另一种异常,得到的效果和调用fillInStackTrace()方法的效果类似。

class MyException extends Exception{

	public MyException() {
		super();
	}

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

	...
	public static void stepTwo() throws Exception{
		try {
			stepOne();
		} catch (Exception e) {
			e.printStackTrace(System.out);
			throw new MyException("error");
		}
	}
	...

四、异常链

异常链是指:在捕获一个异常后抛出另一个异常,在抛出的新异常中将原异常信息保留下来,这样在最后捕获的异常中可以通过记录的关系追踪到异常最初发生的位置。异常类Throwable提供了可以接受一个cause(:Throwable)的构造函数来实现异常链。

在Throwable的子类中,只有Error、Exception、RuntimeException三个基本的异常类提供了带cause参数的构造器,如果要把其他类型的异常链接起来,应该使用initCause()方法。

package com.strikeback.exception;

public class TestExceptionCause {

	public static void wash() throws Exception{
		try {
			throw new Exception("no water");
		} catch (Exception e) {
			throw new Exception("exception in wash", e);
		}
	}
	
	public static void cook() throws Exception{
		try {
			wash();
		} catch (Exception e) {
			throw new Exception("exception in cook because exception in wash", e);
		}
	}
	
	public static void eat() throws Exception{
		try {
			cook();
		} catch (Exception e) {
			throw new Exception("exception in eat because exception in cook", e);
		}
	}
	
	public static void main(String[] args) {
		try {
			eat();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
}

五、Finally

try{}catch(){}finally{}结构中,finally块中的语句总是会被执行即使出现意外的异常。finally块中经常用来做清理资源的操作,例如:对数据库连接和文件的关闭。

1、return与finally

package com.strikeback.exception;

public class FinallyTest {

	public static void main(String[] args) {
		try{
			System.out.println("do something...");
			return;
		}finally{
			System.out.println("finally...");
		}
	}
}

程序输出:

do something...
finally...

2、异常丢失

package com.strikeback.exception;

public class FinallyTest {

	public static void main(String[] args) {
		try{
			throw new RuntimeException();
		}finally{
			return;
		}
	}
}

上面的程序在运行时抛出了异常,但由于在finally中使用了return,因此不会输出任何异常信息

3、另一种异常丢失

package com.strikeback.exception;

public class VeryImportException extends Exception{
	//这是一个非常重要的异常,必须正确处理掉
	@Override
	public String toString() {
		return "a verty important exception!";
	}
}


package com.strikeback.exception;

public class TrivialException extends Exception{
	//这个一个不太重要的异常
	@Override
	public String toString() {
		return "a trivial exception!";
	}
}

package com.strikeback.exception;

public class ExceptionLost {

	public static void dispose() throws TrivialException{
		throw new TrivialException();
	}
	
	public static void doImportantThing() throws VeryImportException{
		throw new VeryImportException();
	}
	
	public static void main(String[] args) {
		try {
			try {
				doImportantThing();
			} finally {
				dispose();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

异常丢失

如上图所示,这种异常处理方式会导致异常(VeryImportException)丢失。

参考资料:

Java编程思想(第四版)