当前位置:首页 > Java技术 > Java面向对象系列[v1.0.0][异常处理机制]

Java面向对象系列[v1.0.0][异常处理机制]

2022年09月16日 21:22:45Java技术6

异常和测试岗位的理念紧密相关,所以关于异常应该好好说一说,实际上异常机制是判断一门编程语言是否成熟的标准,主流的编程语言中都提供了健全的异常处理机制,请看清楚这里说的是处理机制,它可以使程序中的异常处理代码和正常业务代码分离,保证程序代码更加优雅的展现,而其本质是大大提高了程序的健壮性,反映到客户层面更多的感知就是稳定性

对于计算机语言来说情况相当复杂,没人能保证得了程序永远不会出错,就算程序没有错误,谁又能保证客户是按你的预期来输入的?就算客户是非常聪明而且配合的,谁能保证运行该程序会永久稳定?谁能保证运行程序的物理资源永久配合?谁能保证网络条件永远合适?。。。太多无法保证的东西,人是否要覆盖住?

程序员喜欢的永远是解决问题以及开发带来的创造快感,都不愿意当个堵漏洞的工人,而这才是漏洞的真正概念,而这些也是一个程序员是否成熟的标准

以Java为例,它的异常处理主要依赖于try、catch、finally、throw和throws五个关键字:

  • try代码块中放置可能引发异常的代码,因此程序员判断代码是否会有异常情况,是否需要try代码块来处理就成了关键,try只是工具,关键在人;
  • catch代码块对应异常类型以及该类型的处理方式;
  • 多个catch代码块后可以跟一个finally代码块它与try代码块相呼应,主要用于回收try代码块里打开的物理资源,而异常处理机制会保证finally代码块总会被执行;
  • throws关键字主要是用于方法签名,声明该方法可能抛出的异常,而throw用于抛出一个实际的异常

Java的异常分为两种,Checked异常和Runtime异常,Checked异常和Runtime异常,Checked都是可以在编译阶段被处理的异常,因此它强制程序处理所有的Checked异常Runtime异常则无需处理,程序员处理异常是一个繁琐的事情,因此程序的健壮性在人而非try代码块

使用try…catch捕获异常

try
{
     
	// 业务实现代码
	...
}
catch (Exception e)
{
     
	// 处理异常的代码块
}

如果程序可以顺利完成,那就一切正常,如果try块里的业务逻辑代码出现异常,系统会自动生成一个异常对象,该异常对象会被提交给Java runtime环境,而这个过程就被称为抛出异常,当抛出异常发生的时候,Java会寻找能够处理该异常对象的catch块,如果找到合适的catch块,则把该异常对象交给该catch块处理,这个过程被称为捕获异常,如果找不到捕获异常的catch块,则Java runtime环境终止,Java程序也会退出
无论程序是否出现在try代码块中,只要执行该代码块出现了异常,系统总会自动生成异常对象,如果不处理则程序直接退出

import java.io.*;

public class Gobang
{
     
	// 定义一个二维数组来充当棋盘
	private String[][] board;
	// 定义棋盘的大小
	private static int BOARD_SIZE = 15;
	public void initBoard()
	{
     
		// 初始化棋盘数组
		board = new String[BOARD_SIZE][BOARD_SIZE];
		// 把每个元素赋为"╋",用于在控制台画出棋盘
		for (var i = 0; i < BOARD_SIZE; i++)
		{
     
			for (var j = 0; j < BOARD_SIZE; j++)
			{
     
				board[i][j] = "╋";
			}
		}
	}
	// 在控制台输出棋盘的方法
	public void printBoard()
	{
     
		// 打印每个数组元素
		for (var i = 0; i < BOARD_SIZE; i++)
		{
     
			for (var j = 0; j < BOARD_SIZE; j++)
			{
     
				// 打印数组元素后不换行
				System.out.print(board[i][j]);
			}
			// 每打印完一行数组元素后输出一个换行符
			System.out.print("\n");
		}
	}
	public static void main(String[] args) throws Exception
	{
     
		var gb = new Gobang();
		gb.initBoard();
		gb.printBoard();
		// 这是用于获取键盘输入的方法
		var br = new BufferedReader(
			new InputStreamReader(System.in));
		String inputStr = null;
		// br.readLine():每当在键盘上输入一行内容按回车,
		// 用户刚刚输入的内容将被br读取到。
		while ((inputStr = br.readLine()) != null)
		{
     
			try
			{
     
				// 将用户输入的字符串以逗号作为分隔符,分解成2个字符串
				String[] posStrArr = inputStr.split(",");
				// 将2个字符串转换成用户下棋的坐标
				var xPos = Integer.parseInt(posStrArr[0]);
				var yPos = Integer.parseInt(posStrArr[1]);
				// 把对应的数组元素赋为""。
				if (!gb.board[xPos - 1][yPos - 1].equals("╋"))
				{
     
					System.out.println("您输入的坐标点已有棋子了,"
						+ "请重新输入");
					continue;
				}
				gb.board[xPos - 1][yPos - 1] = "";
			}
			catch (Exception e)
			{
     
				System.out.println("您输入的坐标不合法,请重新输入,"
					+ "下棋坐标应以x,y的格式");
				continue;
			}

			gb.printBoard();
			System.out.println("请输入您下棋的坐标,应以x,y的格式:");
		}
	}
}

程序中catch代码块处理了异常后,使用continue忽略本次循环剩下的代码,开始执行下一次循环,这就保证了足够的兼容性,用户可以随意输入,程序不会因为用户输入不合法而突然退出

查找catch代码块

当Java runtime环境接收到异常对象的时候,会依次判断该异常对象是否是catch块里的异常类或者其子类的实例,如果是Java runtime将调用该catch块来处理该异常,否则再次拿该异常对象与下一个catch块里的异常类进行比较,如下图所示
Java面向对象系列[v1.0.0][异常处理机制] _ JavaClub全栈架构师技术笔记

Java异常类的继承体系

如图所示,Java把所有的非正常情况非为两种即:异常(Exception)和错误(Error),他们都继承Throwable父类
Error:一般是指与虚拟机相关的问题,如系统崩溃、虚拟机错误、动态链接失败等,这种错误无法恢复或不可能捕获,将导致程序中断,一般情况下应用程序无法处理这些错误,因此应用程序不应该试图使用catch块来捕获Error对象,在定义该方法时,无须在其throws子句中声明该方法可能抛出Error及其任何子类
Java面向对象系列[v1.0.0][异常处理机制] _ JavaClub全栈架构师技术笔记

public class DivTest
{
     
	public static void main(String[] args)
	{
     
		try
		{
     
			var a = Integer.parseInt(args[0]);
			var b = Integer.parseInt(args[1]);
			var c = a / b;
			System.out.println("您输入的两个数相除的结果是:" + c );
		}
		catch (IndexOutOfBoundsException ie)
		{
     
			System.out.println("数组越界:运行程序时输入的参数个数不够");
		}
		catch (NumberFormatException ne)
		{
     
			System.out.println("数字格式异常:程序只能接受整数参数");
		}
		catch (ArithmeticException ae)
		{
     
			System.out.println("算术异常");
		}
		catch (Exception e)
		{
     
			System.out.println("未知异常");
		}
	}
}

上面程序针对IndexOutOfBoundsException、NumberFormatException、ArithmeticException类型的异常,提供了专门的异常处理逻辑。
Java运行时的异常处理逻辑可能有如下几种情形:

  • 如果运行该程序时输入的参数不够,将会发生数组越界异常,Java运行时将调用IndexOutOfBoundsException对应的catch块处理该异常
  • 如果运行该程序时输入的参数不是数字,而是字母,将发生数字格式异常, Java运行时将会调用NumberFormatException对应的catch块处理该异常
  • 如果运行该程序时输入的第二个参数是0,将会发生除0异常,Java运行时将会调用ArithmeticException对应的catch块处理该异常
  • 如果程序运行时出现其他异常,该异常对象总是Exception类或其子类的实例,Java运行时将调用Exception对象的catch块处理该异常
import java.util.*;

public class NullTest
{
     
	public static void main(String[] args)
	{
     
		Date d = null;
		try
		{
     
			System.out.println(d.after(new Date()));
		}
		catch (NullPointerException ne)
		{
     
			System.out.println("空指针异常");
		}
		catch (Exception e)
		{
     
			System.out.println("未知异常");
		}
	}
}

当试图调用一个null对象的实例方法或实例变量的时,就会引发NullPointerException异常,Java运行时会调用NullPointerException对应的catch块来处理该异常,如果遇到其他异常则调用最后的catch块来处理异常

注意:Exception类的catch块必须方法最后,因为所有的异常对象都是Exception或其子类的实例,如果Exception类的catch块在前边,那么它后边的catch块将永远不会获得执行

实际上进行异常捕获的时候不仅应该把Exception类对应的catch块放在最后,而且所有父类异常的catch块都应该排在子类异常catch块后面,也就是先处理小异常再处理大异常,否则将出现编译错误

try
{
     
	statements...
}
catch (RuntimeException e)
{
     
	System.out.println("运行时异常");
}
catch (NullPointerException ne)
{
     
	System.out.println("空指针异常");
}

因为RuntimeException已经包括了NullPointerException异常,所以catch (NullPointerException ne)处的catch块永远不会获得执行的机会

多异常捕获

在Java7之后,一个catch块可以捕获多种类型的异常,只需要在多种异常类型之间使用竖线(|)隔开,并且异常变量有隐式的final修饰,因此程序不能对异常变量重新赋值

public class MultiExceptionTest
{
     
	public static void main(String[] args)
	{
     
		try
		{
     
			var a = Integer.parseInt(args[0]);
			var b = Integer.parseInt(args[1]);
			var c = a / b;
			System.out.println("您输入的两个数相除的结果是:" + c );
		}
		catch (IndexOutOfBoundsException|NumberFormatException
			|ArithmeticException ie)
		{
     
			System.out.println("程序发生了数组越界、数字格式异常、算术异常之一");
			// 捕捉多异常时,异常变量默认有final修饰,
			// 所以下面代码有错:
			ie = new ArithmeticException("test");  
		}
		catch (Exception e)
		{
     
			System.out.println("未知异常");
			// 捕捉一个类型的异常时,异常变量没有final修饰
			// 所以下面代码完全正确。
			e = new RuntimeException("test");    
		}
	}
}

访问异常类信息

如果程序需要在catch块中访问异常对象的相关信息,可以通过访问catch块的后异常形参来获得,当Java运行时决定调用某个catch块来处理该异常对象时,会将异常对象赋给catch块后的异常参数,程序即可通过该参数来获得异常的相关信息
所有的异常对象都包含了如下几个常用方法:

  • getMessage():返回该异常的详细描述字符串
  • printStackTrace():将该异常的跟踪栈信息输出到标准错误输出
  • printStackTrace(PrintStream s): 将该异常的跟踪栈信息输出到指定输出流
  • getStackTrace():返回该异常的跟踪栈信息
import java.io.*;

public class AccessExceptionMsg
{
     
	public static void main(String[] args)
	{
     
		try
		{
     
			var fis = new FileInputStream("a.txt");
		}
		catch (IOException ioe)
		{
     
			System.out.println(ioe.getMessage());
			ioe.printStackTrace();
		}
	}
}

finally块的作用

Java的垃圾回收机制不会回收任何物理资源,垃圾回收机制只能回收堆内存中对象所占用的内存,而程序在try代码块里打开的物理资源如数据库连接、网络资源、磁盘文件等,这些资源都必须显示的回收,而这些显示的回收应该在finally块中做,因为无论try和catch执行的什么,finally代码块总会被执行

try
{
     
	// 业务代码
	...
}
catch (SubException e)
{
     
	// 异常处理代码块
}
catch (SubException e)
{
     
	// 异常处理代码块
}
...
finally
{
     
	// 资源回收代码块
	...
}
  • 异常处理机制中必须有try代码块,catch和finally都是可选的,但catch和finally必须有其一,也可以同时有
  • 可以有多个catch块,捕获父类异常的catch块必须位于捕获子类异常的后面
  • 不能只有try代码块,既没有catch也没有finally
  • 多个catch代码块必须位于try代码块之后,finally块必须位于所有的catch块之后
import java.io.*;

public class FinallyTest
{
     
	public static void main(String[] args)
	{
     
		FileInputStream fis = null;
		try
		{
     
			fis = new FileInputStream("a.txt");
		}
		catch (IOException ioe)
		{
     
			System.out.println(ioe.getMessage());
			// return语句强制方法返回
			return;       
			// 使用exit来退出虚拟机
			System.exit(1);    
		}
		finally
		{
     
			// 关闭磁盘文件,回收资源
			if (fis != null)
			{
     
				try
				{
     
					fis.close();
				}
				catch (IOException ioe)
				{
     
					ioe.printStackTrace();
				}
			}
			System.out.println("执行finally块里的资源回收!");
		}
	}
}
  • 即便有return强制方法返回,但仍旧会执行finally代码块里的代码
  • System.exit(1)强制退出JVM,这种退出的话finally就没机会执行了
  • 如果在finally块中使用了return或throw语句,将会导致try代码块、catch代码块中的return、throw语句失效
public class FinallyFlowTest
{
     
	public static void main(String[] args)
		throws Exception
	{
     
		boolean a = test();
		System.out.println(a);
	}
	public static boolean test()
	{
     
		try
		{
     
			// 因为finally块中包含了return语句
			// 所以下面的return语句失去作用
			return true;
		}
		finally
		{
     
			return false;
		}
	}
}

当系统遇到try和catch里的return或者throw语句的时候,都会立即终止执行当前方法,当方法执行并未结束,且return和throw语句也未执行,然后程序去寻找finally代码块,如果没有finally代码块程序立即执行return或throw语句方法终止,如果有finally代码块,系统立即执行finally代码块,当finally代码块执行完毕后系统才会跳回去执行try代码块、catch代码块里的return或throw语句,如果finally里也使用了return或throw等导致方法终止的语句,finally代码块就终止了系统也不会跳回去执行try代码块、catch代码块里的任何代码了

自动关闭资源的try语句

当程序使用finally代码块关闭资源时,显得非常臃肿,Java7之后允许try关键字后跟一对圆括号用于声明、初始化一个或多个资源,然后try语句在该语句结束时自动关闭这些资源,从而降低了代码的臃肿
需要说明的是,要保证try语句可以正常关闭资源,这些资源实现类必须实现AutoCloseable或Closeable接口,实现这两个接口就必须实现close()方法

  • Closeable是AutoCloseable的子接口,可以被自动关闭的资源类要么实现AutoCloseable接口,要么实现Closeable接口
  • Closeable接口里的close()方法声明抛出了IOException,因此它的实现类在实现close()方法时只能声明抛出IOException或其子类
  • AutoCloseable接口里的close()方法声明抛出了Exception,因此它的实现类在实现close()方法时可以声明抛出任何异常
import java.io.*;

public class AutoCloseTest
{
     
	public static void main(String[] args)
		throws IOException
	{
     
		try (
			// 声明、初始化两个可关闭的资源
			// try语句会自动关闭这两个资源。
			var br = new BufferedReader(
				new FileReader("AutoCloseTest.java"));
			var ps = new PrintStream(new
				FileOutputStream("a.txt")))
		{
     
			// 使用两个资源
			System.out.println(br.readLine());
			ps.println("庄生晓梦迷蝴蝶");
		}
	}
}

自动关闭资源的try语句相当于包含了隐式的finally代码块,因此这个代码既没有catch也没有finally,Java7之后几乎所有的资源类,包括文件IO的各种类、JDBC的Connection、Statement等接口进行了改写,改写后资源类都实现了AutoCloseable或Closeable接口

Java9之后不要求在try后的圆括号内声明并创建资源,只需要自动关闭的资源有final修饰或者是有效的final,在Java9之后改写上面的代码

import java.io.*;

public class AutoCloseTest2
{
     
	public static void main(String[] args)
		throws IOException
	{
     
		// 有final修饰的资源
		final var br = new BufferedReader(
			new FileReader("AutoCloseTest.java"));
		// 没有显式使用final修饰,但只要不对该变量重新赋值,按该变量就是有效的final
		var ps = new PrintStream(new
			FileOutputStream("a.txt"));
		// 只要将两个资源放在try后的圆括号内即可
		try (br; ps)
		{
     
			// 使用两个资源
			System.out.println(br.readLine());
			ps.println("庄生晓梦迷蝴蝶");
		}
	}
}

作者:Davieyang.D.Y
来源链接:https://blog.csdn.net/dawei_yang000000/article/details/106038440

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。


本文链接:https://www.javaclub.cn/java/42149.html

标签: Exception
分享给朋友:

“Java面向对象系列[v1.0.0][异常处理机制]” 的相关文章

Java空指针异常解决java.lang.NullPointerException解决心得

Java空指针异常解决java.lang.NullPointerException解决心得

今天做课设的时候运行程序报出以下错误 java.lang.NullPointerException 首先要理解的是此错误并不会在 程序中报错,只会在运行的时候报错。 是由于某个参数(集合,数组等数据)可能出现一个null值而导致后面的程序不能运行时...

浅谈java异常[Exception]

一. 异常的定义 在《java编程思想》中这样定义 异常:阻止当前方法或作用域继续执行的问题。虽然java中有异常处理机制,但是要明确一点,决不应该用"正常"的态度来看待异常。绝对一点说异常就是某种意义上的错误,就是问题,它可能会导致程序失败。之所以java要...

[biomaRt] Query ERROR: caught BioMart::Exception::Usage: Attributes from multiple attribute pages are not allowed

[biomaRt] Query ERROR: caught BioMart::Exception::Usage: Attributes from multiple attribute pages are not allowed

正文 Query ERROR: caught BioMart::Exception::Usage: Attributes from multiple attribute pages are not allowed 就如报错所说, 来源于多个attribute pages...

解决ADT不显示javadoc An exception occurred while getting the javadoc

这几天发现ADT在网速特别差的情况下,鼠标悬停不会显示javadoc,而自己写的注释则没有问题. 在网络畅通的时候又会显示javadoc. 所以我猜测ADT会在本地查找文档失败时候尝试联网查找. 一般来说,如果你之前鼠标悬停会显示javadoc,最近突然不显示了...

ERROR org.hibernate.search.exception.impl.LogErrorHandler

2019-06-12 10:17:42 [Hibernate Search: entityloader-1] ERROR org.hibernate.search.exception.impl.LogErrorHandler - HSEARCH000058: HSEARCH0...

如何显示Magento 错误信息!Magento install error – Exception printing

编辑器加载中... 如何显示Magento 错误信息!Magento install error – Exception printing is disabled Here is a known error which can occur when installing Magento...

after : """>【异常】Maxwell异常 Exception in thread "main" net.sf.jsqlparser.parser.TokenMgrError: Lexical error at line 1, column 596. Encountered: after : ""

1 详细异常 Exception in thread "main" net.sf.jsqlparser.parser.TokenMgrError: Lexical error at line 1, column 596. Encountered: <EOF>...

An exception occurred applying plugin request [id: ‘com.android.application‘]

An exception occurred applying plugin request [id: ‘com.android.application‘]

记一次编译异常 An exception occurred applying plugin request [id: 'com.android.application'] > Failed to apply plugin 'com.android.internal.a...

浅谈 Android  Exception & Throwable 类

浅谈 Android Exception & Throwable 类

一、Throwable 类 概述:java将所有的错误封装为一个对象,其根本父类为Throwable, Throwable有两个子类:Error和Exception。 Error: 这种异常被设计成不被捕获,因为这种异常产生于JVM自身。 Runti...

UNEXPECTED TOP-LEVEL EXCEPTION: com.android.dex.DexException: Multiple dex files define

UNEXPECTED TOP-LEVEL EXCEPTION: com.android.dex.DexException: Multiple dex files define

问题描述: 最近在调试一个项目A,A项目需要依赖libraryB,C,D,在调试的时候遇到如下Error: Error:Execution failed for task ':BlogClient:transformClassesWithDexForDebug...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。