Java面向对象系列[v1.0.0][抛出异常]

程序出错的时候,系统会抛出异常,这属于被动抛出,而Java也允许采用一种主动抛出异常的方式或者说自行抛出异常,使用throw语句可以完成

使用throw抛出异常

throw抛出的不是异常类,而是一个异常实例,并且每次只能抛出一个异常实例,语法格式为
throw ExceptionInstance;无论是系统自动抛出的异常还是开发者手动抛出的异常,Java运行时环境对异常的处理没有任何差别

  • 如果throw语句抛出的异常是Checked异常,则该throw语句要么处于try代码块里,显示捕获该异常,要么放在一个带throws声明抛出的方法中,也就是说把异常交给该方法的调用者处理
  • 如果throw语句抛出的异常时Runtime异常,则该语句无须放在try块里,也无须放在带throws声明抛出的方法中,程序既可以显示的使用try…catch来捕获并处理该异常,也可以完全不理会该异常,把该异常交给该方法调用者处理
public class ThrowTest
{
	public static void main(String[] args)
	{
		try
		{
			// 调用声明抛出Checked异常的方法,要么显式捕获该异常
			// 要么在main方法中再次声明抛出
			throwChecked(-3);
		}
		catch (Exception e)
		{
			System.out.println(e.getMessage());
		}
		// 调用声明抛出Runtime异常的方法既可以显式捕获该异常,
		// 也可不理会该异常
		throwRuntime(3);
	}
	public static void throwChecked(int a) throws Exception
	{
		if (a > 0)
		{
			// 自行抛出Exception异常
			// 该代码必须处于try块里,或处于带throws声明的方法中
			throw new Exception("a的值大于0,不符合要求");
		}
	}
	public static void throwRuntime(int a)
	{
		if (a > 0)
		{
			// 自行抛出RuntimeException异常,既可以显式捕获该异常
			// 也可完全不理会该异常,把该异常交给该方法调用者处理
			throw new RuntimeException("a的值大于0,不符合要求");
		}
	}
}

自定义异常

用户自定义异常都应该继承Exception基类,如果希望自定义Runtime异常,则应该集成RuntimeException基类,定义异常类时通常需要提供两个构造器,一个是无参数的构造器,另一个是带一个字符串参数的构造器,这个字符串将作为该异常对象的描述信息(也就是异常对象的getMessage()方法的返回值)

public class AuctionException extends Exception
{
	// 无参数的构造器
	public AuctionException(){}       
	// 带一个字符串参数的构造器
	public AuctionException(String msg)    
	{	
		// 通过super来调用父类的构造器,从而将此字符串参数传给异常对象的message属性
		// 该message属性就是该异常对象的详细描述信息
		super(msg);
	}
}

catch和throw同时使用

异常的处理方式大致有两种

  • 在出现异常的方法内捕获并处理异常,该方法的调用者将不能再次捕获该异常
  • 该方法签名中声明抛出该异常,将该异常完全交给方法调用者处理
    然而在实际应用中往往没有这么简单,单靠某个方法无法完全处理该异常,必须由几个方法协作才可完全处理该异常,换句话说,在异常出现的当前方法中,程序只对异常进行部分处理,还有些处理需要在该方法的调用者中才能完成,所以应该再次抛出异常,让该方法的调用者也能捕获到异常
    这种通过过个方法协作处理同一个异常的方式,可以通过在catch代码块中结合throw语句来完成
public class AuctionTest
{
	private double initPrice = 30.0;
	// 因为该方法中显式抛出了AuctionException异常,
	// 所以此处需要声明抛出AuctionException异常
	public void bid(String bidPrice)
		throws AuctionException
	{
		var d = 0.0;
		try
		{
			d = Double.parseDouble(bidPrice);
		}
		catch (Exception e)
		{
			// 此处完成本方法中可以对异常执行的修复处理,
			// 此处仅仅是在控制台打印异常跟踪栈信息。
			e.printStackTrace();
			// 再次抛出自定义异常
			throw new AuctionException("竞拍价必须是数值,"
				+ "不能包含其他字符!");
		}
		if (initPrice > d)
		{
			throw new AuctionException("竞拍价比起拍价低,"
				+ "不允许竞拍!");
		}
		initPrice = d;
	}
	public static void main(String[] args)
	{
		var at = new AuctionTest();
		try
		{
			at.bid("df");
		}
		catch (AuctionException ae)
		{
			// 再次捕捉到bid方法中的异常。并对该异常进行处理
			System.err.println(ae.getMessage());
		}
	}
}

增强的throw语句

对于如下代码:

try
{
	new FileOutputStream("a.txt")
}
catch (Exception ex)
{
	ex.printStackTrace()'
	throw ex;
}

如下代码所示,在try代码块中只调用了FileOutputStream构造器,这个构造器声明只是抛出了FileNotFoundException异常,在Java7之前只需要声明抛出Exception即可,而Java7之后会进行更仔细的检查,当检查到只可能抛出FileNotFoundException异常的时候,在方法签名中只需要声明抛出FileNotFoundException异常即可

import java.io.*;

public class ThrowTest2
{
	public static void main(String[] args)
		// Java 6认为代码throw ex可能抛出Exception,
		// 所以此处必须声明抛出Exception
		// Java 7会检查代码throw ex可能抛出异常的实际类型,
		// 因此此处只需声明抛出FileNotFoundException即可。
		throws FileNotFoundException
	{
		try
		{
			new FileOutputStream("a.txt");
		}
		catch (Exception ex)
		{
			ex.printStackTrace();
			throw ex;        
		}
	}
}

异常链和异常转译

真正的企业级应用,有严格的分层,层与层之间有非常清晰的划分,上层功能的实现严格依赖于下层的API,也不会跨层访问
在这里插入图片描述
当业务逻辑层访问持久层出现SQLException异常时,程序不应该把底层的SQLException异常传到用户界面,程序应该先捕获原始异常,然后抛出一个新的业务异常,新的业务异常应该包含对用户有用的的提示信息,这种处理方式叫做异常转译

public void calSal() throws SalException
{
	try
	{
		// 业务逻辑
		...
	}
	catch(SQLException sqle)
	{
		// 把原始异常记录下来,留给管理员
		...
		// 下面异常中的message就是对用户的提示
		throw new SalException("访问底层数据库出现异常")
	}
	catch (Exception e)
	{
		// 把原始异常记录下来,留给管理员
		...
		// 下面异常中的message就是对用户的提示
		throw new SalException("系统出现未知异常")
	}
}

这种把原始异常信息隐藏起来,仅向上提供必要的异常提示信息的处理方式,可以保证底层异常不会扩散到表现层,可以避免向上暴露太多的实现细节,这完全符合面向对象的封装原则
这种把捕获一个异常然后接着抛出另一个异常,并把原始异常信息保存下来是一种典型的链式处理,也被称为“异常链”
在JDK1.4以前,程序员必须自己编写代码来保持原始异常信息,从JDK1.4以后,所有Throwable的子类在构造器中都可以接收一个cause对象作为参数,这个cause就用来表示原始异常,这样可以把原始异常传递给新的异常,使得即使在当前位置创建并抛出新的异常,你也能通过这个异常链追踪到一场最初发生的位置,例如希望通过上面的SalException去追踪到最原始的异常信息,则可以将该方法改写为如下形式

public void calSal() throws SalException
{
	try
	{
		// 业务逻辑
		...
	}
	catch(SQLException sqle)
	{
		// 把原始异常记录下来,留给管理员
		...
		// 下面异常中的sqle就是原始异常
		throw new SalException(sqle);
	}
	catch(Exception e)
	{
		// 把原始异常记录下来,给管理员
		...
		// 下面异常中的e就是原始异常
		throw new SalException(e);
	}
}

上面程序中创建SalException对象时,传入了一个Exception对象,而不是传入了一个String对象,这就需要SalException类有相应的构造器,在jdk1.4之后,Throwable基类已有了一个可以接收Exception参数的方法,所以可以采用如下代码来定义SalException类

public class SalException extends Exception
{
	public SalException(){}
	public SalException(String msg)
	{
		super(msg);
	}
	// 创建一个可以接受Throwable参数的构造器
	public SalException(Throwable t)
	{
		super(t);
	}
}

创建了这个SalException业务异常类后,就可以用它来封装原始异常,从而实现对异常的链式处理

猜你喜欢

转载自blog.csdn.net/dawei_yang000000/article/details/106677960