Java 处理错误与捕获异常

1 异常的分类

所有的异常都派生于Throwable类,然后分成两个分支:Error和Excpetion。
在这里插入图片描述

Error类层次结构描述Java运行时系统的内部错误和资源耗尽错误。这种情况一般很少出现,应用程序也不用抛出这种类型的对象,出现了这种错误时,一般通告用户并安全终止程序即可。

Exception类是主要需要关注的,分为两个分支:RuntimeException、IOException。

由程序错误导致的异常属于RuntimeException,一般包含:

  • 错误的类型转换
  • 数组访问越界
  • 访问null指针

程序本身没有问题,但由于像I/O错误导致的异常属于IOException,一般包含:

  • 试图在文件尾部后面读取数据
  • 试图打开一个不存在的文件
  • 试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在

Java将派生于Error类或RuntimeException类的所有异常称为非受查(unchecked)异常,所有其他的异常称为受查(checked)异常。受查异常要么方法内部处理,要么通过方法的声明,提示调用方处理(比如文件找不到等情况)。非受查异常要么不可控制,要么避免发生。

2 声明受查异常

方法应该在其首部声明所有可能抛出的异常,例如:

public FileInputStream(String name) throws FileNotFoundException

一般不必将所有可能抛出的异常都进行声明,总的来说,一个方法必须声明所有可能抛出的受查异常,而非受查异常要么不可控制(Error),要么就应该避免发生(程序编写错误导致的RuntimeException)。

3 抛出异常与自定义异常类

抛出异常的过程一般为:找到合适的异常类 -> 创建这个类的一个对象 -> 将对象抛出,例如:

String readData(Scanner in) throws EOFException
{
	...
	while (...)
	{
		if (!in.hasNext()) 
		{
			if (n < len)
				throw new EOFException();
		}
		...
	}
	return s
}

如果JDK中的任何标准异常类都没有能够充分地描述清楚问题,那么就需要自己定义一个派生于Exception类或其子类的异常,例如

class FileFormatException extends IOException
{
	public FileFormatException() {}
	public FileFormatException(String gripe)
	{
		super(gripe);
	}
}

在上面的例子张就可以抛出自己定义的FileFormatException了。

4 捕获异常

对于不知道怎么处理的异常,一般将其抛出传递即可;对于知道如何处理的异常,则应该将其捕获处理。

4.1 try-catch语句块捕获异常

想要捕获一个异常,必须设置try-catch语句块,如下所示:

try
{
	code
	more code
	more code
}
catch (ExceptionType e)
{
	handler for this type
}

如果try语句块中代码没有抛出任何异常,那么程序将跳过catch子句,否则跳过try语句块后面的部分,执行catch子句中的处理器代码。

4.2 捕获多个异常

try
{
	code that might throw exceptions
}
catch (FileNotFoundException e)
{
	emergency action for missing files
}
catch (UnknownHostException e)
{
	emergency action for unknown hosts
}
catch (IOException e)
{
	emergency action for all other I/O problems
}

若捕获的异常类型彼此之间不存在子类关系,可以合并catch子句:

catch (FileNotFoundException | UnknownHostException e)
{ ... }

4.3 再次抛出异常与异常链

在catch子句中可以抛出一个异常,这样做的目的是改变异常的类型:

try
{
	access the database
}
catch (SQLException e)
{
	logger.log(level,message,e);
	Throwable se = new ServletException("database error");
	se.initCause(e);
	throw e;
}

将原始异常设置为新异常的原因,并记录原始异常信息,然后将新异常抛出。通过这样的包装技术,可以让用户抛出子系统中的高级异常,而不会丢失原始异常的细节。

4.4 finally子句

当代码抛出异常时,就会终止方法中剩余代码的处理,并退出这个方法的执行,如果方法获得了一些本地资源,并且只有这个方法知道,而这些资源在退出方法之前必须回收,这样就会产生资源回收问题。

在try-catch语句后面添加finally子句,不管是否有异常被捕获,finally子句中的代码都被执行。

InputStream in = new FileInputStream(...);
try
{
	code that might throw exceptions
}
catch (IOException e)
{
	show error message
}
finally
{
	in.close();
}

不管有没有捕获异常,都会跳转到finally子句中关闭文件读取流。为了达到解耦合的目的,也可以将try/catch和try/finally语句块分开使用:

InputStream in = new FileInputStream(...);
try
{
	try
	{
		code that might throw exceptions
	}
	finally
	{
		in.close();
	}
}
catch (IOException e)
{
	show error message
}

这样内层try语句块只有确保关闭输入流这一个职责,而外层try语句块还能够报告finally子句中出现的错误。

当try子句和finally子句都包含return语句时,这个返回值有可能会覆盖try语句中的返回值。

4.5 带资源的try语句

仅通过finally子句关闭资源可能会出现这种情况:若try语句块中代码抛出了一些非IOException的异常,而执行到finally语句块中调用close方法也有可能抛出IOException异常。出现这种情况时,原始的异常将会丢失,转而抛出close方法的异常。

通过使用带资源的try语句块(try-with-resources),可以解决以上问题:

try (Resource res = ...)
{
	work with res
}

当try语句退出时,会自动调用res.close()。当try语句和关闭资源语句都抛出异常时,关闭资源语句的异常会“被抑制”,原来的异常会重新抛出。被抑制的异常将自动捕获,并由addSuppressed方法增加到原来的异常,如果对这些异常感兴趣,可以调用getSuppressed方法,它会得到从close方法抛出并抑制的异常列表。

带资源的try语句块也可以指定多个资源,例如:

try (Scanner in = new Scanner(new FileInputStream("/usr/share/dict/words"),"UTF-");
	 PrintWriter out = new PrintWriter("out.txt"))
{
	while (in.hasNext())
		out.println(in.next().toUpperCase());
}

5 分析堆栈轨迹元素

堆栈轨迹(stack trace)是一个方法调用过程的列表,当Java程序正常终止,而没有捕获异常时,这个列表就会显示出来。

通过调用Throwable类的printStackTrace方法访问堆栈轨迹的文本描述信息。

void printStackTrace()

或者使用Throwable类的getStackTrace方法获得一个StackTraceElement对象数组,StackTraceElement类含有能够获得文件名和当前执行的代码行号的方法,同时也包含有能够获得类名和方法名的方法。

StackTraceElement[] getStackTrace()

Java核心技术卷1 第10版 程序清单7-1打印了递归阶乘函数的堆栈情况:

/**
 * A program that displays a trace feature of a recursive method call
 */
public class StackTraceTest {
    /**
     * Computes the factorial of a number
     * @param n a non-negative integer
     * @return n! = 1 * 2 * ... * n
     */
    public static int factorial(int n)
    {
        System.out.println("factorial(" + n + "):");
        Throwable t = new Throwable();
        StackTraceElement[] frames = t.getStackTrace();
        for (StackTraceElement f : frames)
            System.out.println(f);
        int r;
        if (n <= 1)
            r = 1;
        else r = n * factorial(n - 1);
        System.out.println("return " + r);
        return r;
    }

    public static void main(String[] args)
    {
        Scanner in = new Scanner(System.in);
        System.out.println("Enter n: ");
        int n = in.nextInt();
        factorial(n);

        Throwable e = new Throwable();
        e.printStackTrace();
    }
}

计算3的阶乘的结果:
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值