Java中的异常处理机制

概述

本篇文章将总结Java中的异常处理机制,通过具体的代码示例来演示try-catch,finally,throw,throws等关键字的用法,旨在简单了解Java中异常处理机制,后续将总结如何在项目中通过Spring实现系统的统一异常处理。异常定义了程序中可能遇到的非致命性错误,而不是编译时的语法错误,例如程序要打开一个不存在的文件,装载一个不存在的类等。

一、try-catch语句块

Java中提供try-catch语句来进行异常的捕获和处理

代码演示:

package com.jwang.exception;

import org.junit.Test;

public class TestException
{

	// 该方法演示不进行异常处理会发生什么:
	@Test
	public void testException1()
	{
		int result1 = devide(3, 1);
		System.out.println("the result1 is " + result1);
		System.out.println("============后面即将发生异常了,但没有捕获处理==============");
		// 下面语句会发成异常,但不处理
		int result2 = devide(3, 0);
		System.out.println("the result2 is " + result2);
		System.out.println("============前面发生异常了,但没有捕获处理==============");
	}

	// 该方法演示异常发生时try-catch是一个怎样的执行顺序:
	@Test
	public void testException2()
	{
		// 监视有可能发生异常的代码段,如果发生异常,会将异常的信息封装到一个对象当中,传递给catch
		try
		{
			// 不会发生异常,所以后面的打印语句正常被执行
			int result1 = devide(3, 1);
			System.out.println("the result1 is " + result1);
			// 发生除数为0的异常,所以后面的打印语句不会被执行,跳转到catch语句
			System.out.println("============后面即将发生异常了,捕获处理了==============");
			int result2 = devide(3, 0);
			System.out.println("the result2 is " + result2);
			System.out.println("============前面发生异常了,但没有捕获处理==============");
		}
		catch (Exception e)
		{
			// 接收来自try语句的异常对象
			System.out.println(e.getMessage() + "异常发生了,现在正再catch中");// 对异常进行处理
		}
		// try代码块之后的语句:
		System.out.println("现在正在执try代码快后的程序。。。。");// 这条语句会被正常执行
	}

	// 编写一个求商的函数
	public int devide(int x, int y) throws ArithmeticException
	{
		int result = x / y;
		return result;
	}
}

第一个方法的运行结果如下所示:

我们看到程序在调用被0除的方法之前运行正常,控制台打印出了预期的结果,接着调用被0除的方法时发生异常,由于异常没有被处理,所以程序直接报错,终止运行。

第二个方法的运行结果如下所示:

       通过上面的运行结果我们发现将可能发生异常的代码放到try语句块中,当try中的代码发生异常时程序会跳转到catch语句块中继续执行,执行完catch中的代码后,程序会接着继续执行catch语句后面的代码,但是try中发生异常的代码以后的代码就不会再执行。可见java中的异常处理是结构化的,不会因为一个异常影响整个程序的执行。

       当try中代码发生异常时,系统会将这个异常的代码行号,类别等信息封装到一个异常对象中,并将这个对象传递给catch代码块。

二、throws关键字

        针对上面的例子,假设devide()方法和testException2方法()分别位于不同的类中,那么在testException2中调用devide方法时怎么知道devide方法可能出现异常呢?他又怎么能够想到用try-catch语句来进行处理呢?问题可以这样解决,只要在定义devide方法时,在devide方法参数列表后用throws关键字声明一下,该函数有可能发生异常及异常的类别。这样调用者在调用时就必须用try-catch语句进行处理了,否则编译无法通过。通过throws关键字可以一直往上一层调用者抛,这样源头的调用这就必须处理。

代码演示:

package com.jwang.exception;

import org.junit.Test;

/**
 * 描述:throws关键字的使用
 * @author jwang
 *
 */
public class TestException2
{
	
	/**
	 * 该方法中调用了通过throws关键字抛出异常的方法,通过try-catch进行异常处理
	 */
	@Test
	public void testException1()
	{
		// 监视有可能发生异常的代码段,如果发生异常,会将异常的信息封装到一个对象当中,传递给catch
		try
		{
			// 虽然不会发生异常,但是调用的方法可能抛出异常,所以要放在try中
			int result1 = devide(3, 1);
			System.out.println("the result1 is " + result1);
			// 发生除数为0的异常,所以后面的打印语句不会被执行,跳转到catch语句
			System.out.println("============后面即将发生异常了,捕获处理了==============");
			int result2 = devide(3, 0);
			System.out.println("the result2 is " + result2);
			System.out.println("============前面发生异常了,已经被捕获处理==============");
		} 
		catch (Exception e)
		{
			// 接收来自try语句的异常对象
			System.out.println(e.getMessage() + "异常发生了,现在正再catch中");// 对异常进行处理
		}
		// try代码块之后的语句:
		System.out.println("现在正在执try代码快后的程序。。。。");// 这条语句会被正常执行
	}

	/**
	 * 编写一个求商的函数,并通过throws关键字抛出异常
	 * @param x
	 * @param y
	 * @return
	 * @throws ArithmeticException
	 */
	public int result(int x, int y) throws ArithmeticException
	{
		int result = x / y;
		return result;
	}

	/**
	 * 该方法调用通过throws关键字抛出异常的方法,不处理继续抛出
	 * @param x
	 * @param y
	 * @return
	 * @throws ArithmeticException
	 */
	public int devide(int x, int y) throws ArithmeticException
	{
		return result(x, y);
	}

}

运行的结果如下所示:

注意:

(1)某个方法中用throws声明有可能发生异常时,调用方法的语段中必须对异常进行处理,即使没有异常。

(2)如果一个方法中的语段执行时可能发生某种异常,但是并不能确定该如何处理,则此方法应该声明抛出异常,表明该方法将不对这些异常进行处理,而是由该方法的调用者来负责处理。也就是程序中异常没有用try-catch捕获处理,我们可以在程序代码所在的函数(方法)声明后用throws声明该函数要抛出异常,将该异常抛出到该函数的调用函数中,一直到mian方法,jvm肯定是要处理的,这样编译就能通过了。虽然编译能通过,但是异常一旦发生,没有处理,程序就会非正常终止。所以在正常的开发中我们必须对可能发生的异常进行捕获处理。

三、系统异常和自定义异常

系统异常类:
1、throwable有两个直接子类,一个是Error类,通常为内部错误,正常情况下并不期望用户程序捕获他们。另一个是Exception类,绝大部分用户程序应当捕获的异常类的根类。一些常用的异常类都直接或间接派生自Exception类,因此我们可以认为绝大多数的异常都属于Exception。

2、ArithmeticException(表示在算数运算中发生异常)NullPointerException(空指针异常)等都是Exception类的子类。

3、异常类中有两个常用的方法:
(1)String getMessage()在Exception类中定义的方法,被继承到所有的异常类中,用于获得与异常相关的描述信息。
(2)void printStackTrace()在Exception类中定义的方法,用于显示异常的堆栈信息,不但有异常的原因,还涉及产生异常的代码行。

自定义异常类与throw关键字:
1、除了系统提供的异常,我们也可以自己定义自己的异常类,自定义的异常类必须继承Exception类。

2、在一个方法内部使用throw抛出异常对象,如果该方法的内部没有用try-catch语句对这个抛出的异常进行处理,则此方法因该声明抛出异常而由该方法的调用者负责处理。

程序示例:

package com.jwang.exception;

/**
 * @author jwang 
 * 自定义一个异常类,必须继承Exception类
 */
@SuppressWarnings("serial")
public class MyFirstException extends Exception
{

	// 异常类中定义一个变量
	int devisor;

	// 自定义异常类的一个构造函数,供throw抛出该类的异常对象时调用
	public MyFirstException(String msg, int devisor)
	{
		// 调用父类的带一个参数的构造方法
		super(msg);
		this.devisor = devisor;
	}

	// 自定义异常类中的一个方法,返回除数的值
	public int getdevisor()
	{
		return devisor;
	}

}
package com.jwang.exception;

import org.junit.Test;

/**
 * @author jwang 自定义异常应用
 */
public class TestException3
{

	@Test
	public void testException()
	{
		try
		{
			//int result = devide(3,0);当这一句的devide方法被调用的时候,会自动调用下面与此异常相匹配的catch异常处理语段,即调用第二个catch语段
			int result = devide(3, -1);// 当这一句的devide方法被调用的时候,会调用devidebyminusexception异常处理语段
			// int result = devide(3,1);//当这一句的devide方法被调用的时候,会自动调用第三个catch语段进行处理
			System.out.println("the result is " + result);
		}
		catch (MyFirstException e)// 当除数为负数的时候自动调用这个catch语段进行处理
		{
			System.out.println("program is running into " + "devidebyminusexception");
			System.out.println(e.getMessage());// 调用父类的方法,输出异常信息
			System.out.println("the devisor is " + e.getdevisor());// 调用自定义异常类中独有的方法,输出除数
		} 
		catch (ArithmeticException e)// 当除数为0的时候会自动调用这个catch语段进行处理
		{
			System.out.println("program is running into " + "ArithmeticException");
			System.out.println(e.getMessage());
		} 
		catch (Exception e)// 当异常与其他异常都不匹配的时候就会调用这个catch处理异常,要是有多个异常处理语段的时候,这个通常放在末尾。
		{
			System.out.println("program is running into " + "other unknow exception");
			System.out.println(e.getMessage());
		}
		finally // 无论是否发生异常都会被执行
		{
			System.out.println("program is running into " + "finally");
		}

		System.out.println("program is running here ,that is normal");

	}

	/**
	 * 这个方法接收两个参数并抛出两个可能的异常
	 * @param x
	 * @param y
	 * @return
	 * @throws ArithmeticException
	 * @throws MyFirstException
	 */
	public int devide(int x, int y) throws ArithmeticException, MyFirstException
	{
		if (y < 0)
		{
			// 当除数小于0时,在方法的内部抛出一个自定义类型的异常对象
			throw new MyFirstException("被除数为负", y);
		}
		int result = x / y;
		return result;
	}
}

运行结果:

分析:
1、我们可以看到上面的程序中,devide方法声明时抛出了两个异常,java中一个方法可以声明抛出多个异常。

2、上面的程序使用一个try后面跟着多个catch语句来捕捉异常,每一个catch可以处理一个不同种类的异常。如果我们调用devide(3,0),将会发生ArithmeticException 异常,程序就将跳转到catch (ArithmeticException e)代码块来执行。如果我们调用devide(3,-1),将会发生devidebyminusexception类型的异常,程序将跳转至catch (devidebyminusexception e)代码块来执行。如果devide方法中发生了除了上面两种异常之外的异常时,程序将跳转到catch(Exception e)代码块进行处理,这个代码块能够处理所有类型的异常。所以编写代码时应该放在最后。否则其他处理异常的代码不会执行。

四、finally关键字(代码见上面程序示例)

1、finally块必须和try-catch块一起使用,不能单独使用。每一个try语句必须有一个或多个catch语句相对应,try代码块与catch代码块,以及finally代码块之间不能有其他的语句,必须连着。

2、无论try-catch中发生什么,finally块都会被执行。即使try代码块和catch代码块中使用了return语句退出当前方法,或者使用了break语句跳出了某个循环,相应的finally块都要被执行。当发生异常时,程序可能会意外的中断,有些被占用的资源就得不到清理。finally块可以确保执行清理工作,或者进行资源的释放工作。

3.Finally块不能被执行的唯一情况是:在被保护代码块中执行了System.exit(0).

五、注意:

1、一个方法被覆盖时,覆盖他的方法必须抛出相同的异常或者异常的子类。

2、如果父类抛出了多个异常,那么重写(覆盖)方法必须抛出那些异常的一个子集,也就是说,不能抛出新的异常。


 

猜你喜欢

转载自blog.csdn.net/m0_38045882/article/details/83216726