阿里云【名师课堂】Java面向对象开发89 ~ 96:【第04个代码模型】异常的捕获与处理


异常是导致程序中断执行的一种指令流,就是程序运行时可能出现一些错误,比如试图打开一个不存在的文件等。异常处理将会改变程序的控制流程,让程序有机会对错误进行处理。

89:观察异常带来的问题

范例:首先观察一个没有异常的简单程序

public class TestDemo {
	public static void main(String args[]) {
		System.out.println("【1】数学计算开始前:" + 4 + "  " + 7) ;
		System.out.println("【2】数学计算:" + 4 + "*" + 7) ;
		System.out.println("【3】数学计算结束后:" + (4 * 7)) ;
		System.out.println("【4】done") ;
	}
}

在这里插入图片描述
范例:观察产生异常的程序

public class TestDemo {
	public static void main(String args[]) {
		System.out.println("【1】数学计算开始前:" + 17 + "  " + 0) ;
		System.out.println("【2】数学计算:" + 17 + "/" + 0) ;
		System.out.println("【3】数学计算结束后:" + (17 / 0)) ;
		System.out.println("【4】done") ;
	}
}

在这里插入图片描述
根据输出结果可以看出,异常产生的语句(第5句)之前的代码都正常执行,异常产生之后程序将直接结束运行,不管后面什么代码。
为了保证程序出现异常之后还能继续向下运行,就需要进行异常处理。

90:异常处理格式

异常处理格式如下:

try {
	有可能出现异常的语句 ;
} [catch (异常类 对象) {
	异常的处理语句 ;
} catch (异常类 对象) {
	异常的处理语句 ;
}] // []中为可选内容
finally {
	异常的统一出口 ;
}

对于以上三个关键字:trycatchfinally,可能出现的组合为:

  • try···catch
  • try···finally
  • try···catch···finally

try···catch

范例:对异常进行处理

public class TestDemo {
	public static void main(String args[]) {
		System.out.println("【1】数学计算开始前:" + 17 + "  " + 0) ;
		System.out.println("【2】数学计算:" + 17 + "/" + 0) ;
		try {
			System.out.println("【3】数学计算结束后:" + (17 / 0)) ;
		} catch (ArithmeticException e) {  // java.lang包自动导入,不用写上
			System.out.println("【CATCH】异常已经被处理") ;
		}
		System.out.println("【4】done") ;
	}
}

在这里插入图片描述
发现出现异常之后,由于程序存在有异常的处理机制,所以可以正常执行完毕。
但是此时存在一个问题:虽然异常被处理了,但是还是不知道程序究竟出现了什么样的异常,所以为了明确取得异常的信息

  • 可以直接输出异常类对象(任何对象直接打印都会调用toString方法),缺点是不知道异常出现在何处(行数);
    • System.out.println(异常对象名) ;
    • 比如:System.out.println("【CATCH】异常已经被处理" + e) ;
      在这里插入图片描述
  • 推荐: 或者调用所有异常类中都提供的printStackTrace()方法进行完整异常信息的输出(异常类型和行数)。
    • 异常对象名.printStackTrace() ;
    • 比如:e.printStackTrace() ;
      在这里插入图片描述

try···catch···finally

在执行try···catch语句之后执行finally语句。也就是说,无论在try部分是否有异常发生,finally子语句都会执行。
范例:观察try···catch···finally操作

public class TestDemo {
	public static void main(String args[]) {
		System.out.println("【1】数学计算开始前:" + 17 + "  " + 0) ;
		System.out.println("【2】数学计算:" + 17 + "/" + 0) ;
		try {
			System.out.println("【3】数学计算结束后:" + (17 / 0)) ;
		} catch (ArithmeticException e) {
			e.printStackTrace() ;
		} finally {
			System.out.println("【FINALLY】不管是否产生异常都执行此语句") ;
		}
		System.out.println("【4】done") ;
	}
}

在这里插入图片描述
修改上面程序的数学运算为17/1之后:
在这里插入图片描述
所以,finally类就是本节开头说到的程序的出口。


下面改进程序,要求初始化两个参数传入程序来进行计算。
范例:接收参数进行除法计算

public class TestDemo {
	public static void main(String args[]) {
		System.out.println("【1】数学计算开始前:") ;
		try {
			int x = Integer.parseInt(args[0]) ;  // 初始化参数
			int y = Integer.parseInt(args[1]) ;
			System.out.println("【2】数学计算:" + x + "/" + y) ;
			System.out.println("【3】数学计算结束后:" + (x / y)) ;
		} catch (ArithmeticException e) {
			e.printStackTrace() ;
		} finally {
			System.out.println("【FINALLY】不管是否产生异常都执行此语句") ;
		}
		System.out.println("【4】done") ;
	}
}

正确的执行:
在这里插入图片描述
错误的执行:

  • 输入参数数量不对(小于需要的参数)
    在这里插入图片描述
  • 输入的参数不是数字
    在这里插入图片描述

可以发现,上面两种异常情况都没有得到处理(catch语句没有执行),因为catch语句在这里只能捕获、处理被除数为0的异常(ArithmeticException)。

怎么解决?
简单粗暴(并不推荐):使用多个catch语句涵盖所有可能异常的情况。

		catch (ArithmeticException e) {
			e.printStackTrace() ;
		} catch (NumberFormatException e) {
			e.printStackTrace() ;
		} catch (ArrayIndexOutOfBoundsException e) {
			e.printStackTrace() ;
		}

在这里插入图片描述
这样虽然可以实现要求的功能,但是并没有实际意义。以后会讲解正确的方法。

91:throws关键字

在进行方法调用时,如果要告诉调用者本方法可能会产生哪些异常,可以使用throws关键字进行声明这些异常。

  • 使用throws关键字定义方法时一定要有try···catch块语句中调用能发生异常的方法,因为catch可以捕获异常对象,而定义的throws方法可能会产生异常,所以必须按照有异常产生的情况来处理。

范例:使用throws定义方法

class MyMath {
	// 此处明确告诉用户该方法上会有异常
	public static int div(int x, int y) throws Exception {
		return x / y ;
	}
}

public class TestDemo {
	public static void main(String args[]) {
		try {
			System.out.println("结果:" + MyMath.div(8, 2)) ;
		} catch (Exception e) {
			e.printStackTrace() ;
		}
	}
}

正常执行:
在这里插入图片描述
出现异常:
在这里插入图片描述


主方法本身也属于一个方法,所以主方法上也可以使用throws定义方法,这时如果产生了一场,就交给JVM进行处理

  • 这时不需要try···catch语句。
class MyMath {
	// 此处明确告诉用户该方法上会有异常
	public static int div(int x, int y) {
		return x / y ;
	}
}

public class TestDemo {
	public static void main(String args[]) throws Exception {
			System.out.println("结果:" + MyMath.div(8, 0)) ;
	}
}

在这里插入图片描述

92:throw关键字

throw是直接编写在语句之中的,表示人为进行异常的抛出。

  • 比如,对于数学运算:8 / 0,产生的异常ArithmeticException是由JVM负责进行异常类对象实例化catch (ArithmeticException e)。而现在如果不希望异常类对象由系统产生,而是希望由用户控制异常类实例化对象的产生,就可以使用throw关键字。

范例:使用throw产生异常类对象

public class TestDemo {
	public static void main(String args[]) {
		try {
			throw new Exception("抛,都给我抛") ;  //实例化异常类对象
		} catch (Exception e) {
			e.printStackTrace() ;
		}
	}
}

在这里插入图片描述
一般情况下,throw的使用要与条件判断语句结合。


throws和throw的区别

  • throws用于方法声明时,主要明确告诉用户本方法可能产生的异常,同时该方法可能不处理此异常。
  • throw用于方法内部,主要表示进行人为的抛出

93:异常处理模型(重点)

本节通过一个案例讲解异常处理标准格式。上面已经讲解了try、catch、finally、throw、throws概念,本节将它们串联起来。


现在要求编写一个方法——除法操作,要求如下:

  1. 在进行除法操作之前要先打印一行语句;
  2. 如果在除法运算的过程中出现有错误,则应该将异常返回给调用处;
  3. 不管最终是否有错误产生,都要求打印一行计算结束的信息。
class MyMath {
	// 此处明确告诉用户该方法上会有异常
	public static int div(int x, int y) throws Exception {
		int result = 0 ;
		System.out.println("【开始】") ;  // 2、打印【开始】
		try {  // 3、进行除法运算
			result = x / y ;  // 不使用try···catch的话,此处如果有异常,后面不执行
		} catch (Exception e) {  // 其实这里的catch语句可以省略
			throw e ;  // 4、如果有异常,抛出到本方法的throws Exception
		} finally {
			System.out.println("【结束】") ;  // 5、不管有没有异常,都执行finally,打印结束
		}
		return result ;
		
	}
}

public class TestDemo {
	public static void main(String args[]) {
		try {  // 对应MyMath类中的throws
			System.out.println(MyMath.div(8, 0)) ;  // 1、调用div方法,在div方法中的输出完成后进行输出
		} catch (Exception e) {  // 6、捕获到div方法传过来的异常,输出异常信息
			e.printStackTrace() ;
		}
	}
}

在这里插入图片描述

94:RuntimeException

讲解之前先看一段简单程序:

public class TestDemo {
	public static void main(String args[]) {
		String str = "100" ;
		int num = Integer.parseInt(str) ;
		System.out.println(num * 2) ;
	}
}

在这里插入图片描述
观察Integer类中关于parseInt()方法的定义

  • public static int parseInt(String s) throws NumberFormatException

可以看到parseInt方法已经明确抛出了异常,但是在进行调用的时候可以发现即使没有进行异常处理,也可以正常执行,为什么?

  • RuntimeException

观察NumberFormatException的继承结构:

Class NumberFormatException
	java.lang.Object 
		java.lang.Throwable 
			java.lang.Exception 
				java.lang.RuntimeException 
					java.lang.IllegalArgumentException 
						java.lang.NumberFormatException 

异常是普遍发生的,如果所有出现异常的地方都要求进行强制性的异常处理,代码将会很复杂。所以在异常类设计的时候考虑到一些异常可能是一些简单的问题,所以将这些异常统一称为RuntimeException

  • 也就是说使用RuntimeException定义的异常类可以不需要强制性进行异常处理。当然想使用try···catch处理也没人拦着。

ExceptionRuntimeException的区别?

  • ExceptionRuntimeException的父类,使用Exception定义的异常要求必须使用异常处理;
  • RuntimeException可以用用户选择性的来进行异常处理。

列举几个常见的RuntimeException

ArithmeticException 算数运算异常
ClassCastException 类型间转换不兼容异常
NullPointerException 空指针异常

95:断言

断言(assert)指的是当程序执行某些语句之后其数据的内容一定是约定的内容,否则停止执行。
范例:观察断言程序

public class TestDemo {
	public static void main(String args[]) {
		int num = 100 ;
		// 中间可能会有很多步骤,预计num的内容应该变为300
		assert num == 300 : "错误:num内容不是300" ; 
		System.out.println(num) ;
	}
}

如果要让断言起作用,要在JVM解释时使用-ea选项:java -ea 文件名
在这里插入图片描述
断言主要作用为调试阶段debug,但是实际上基本用不到。

96:自定义异常类

在Java中,实际上对于可能出现的公共的程序问题都会提供有相应的异常信息,但是很多时候这些异常信息往往不足以满足需求。

  • 例如:现在要求进行加法处理,如果发现两个内容相加为100,就抛出一个AddException异常。这种情况Java并没有提供相应的异常信息,因此需要我们自定义异常类/

想要自定义异常类,可以继承两种父类:ExceptionRuntimeException
范例:实现自定义异常类

class AddException extends Exception {
	public AddException(String msg) {
		super(msg) ;
	}
}

public class TestDemo {
	public static void main(String args[]) throws Exception {
		if ((50 + 50) == 100) {
			throw new AddException("错误的相加操作") ;
		}
	}
}

在这里插入图片描述

posted @ 2020-06-28 15:55  溺水的情书  阅读(184)  评论(0编辑  收藏  举报