异常简介

1.异常定义

编译期间的错误编译器往往或提醒我们,但运行期间的错误不易预先侦知。


2.异常分类

异常是在程序运行过程中不期而至的各种状况,可以把他理解成是一种事件,其会干扰程序正常的运行流程;

在Java中是使用 Throwable以及它的相关子类 来去对各种异常类型进行描述的

Error: Error是程序中无法处理的错误,表示运行应用程序中较严重的问题。大多数的错误与代码编写者执行的操作是没有关系的,而是代码运行的时候,Java虚拟机出现的一系列的问题。

常见的有虚拟机错误,内存溢出错误等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的情况;对于设计合理的应用程序来说,即使确实发生了错误,本质上也无法通过异常处理去解决它所引起的各种状况。

所以对于Error及其子类所产生的异常我们无能为力,通常也不需要去关心,自然我们也不希望程序中出现Error错误。

Exception: 是程序本身可以处理的异常,而我们所用的异常处理通常就是针对Exception的;

非检查异常:指编译器不要求强制处理的异常,其包含RuntimeException及其相关子类:

例如下面就是非检查异常的四个例子,Java编译器在编译阶段是不会去检查【非检查异常】的,即在编写代码时可以选择去捕获处理【非检查异常】,也可以选择放任不管

检查异常:编译器要求必须在代码中处理的异常;在Java中,除了RuntimeException及其子类之外的所有Exception均是检查异常:

常见的检查异常如:文件操作异常和数据操作异常等,这些异常在程序编码阶段就要求必须进行异常处理( 尚存模糊点 ),否则编译时无法正常通过。

异常的处理机制简介

Java异常处理机制:

异常得先抛出来,然后才能够被捕获。

抛出异常:当一个方法中出现错误引发的异常的时候,方法会去创建【异常对象】,并且交付给运行时系统来进行处理;这个异常对象中通常包含异常的类型和异常出现时的程序状态等信息;

当运行时系统捕获到这个异常时,就会进入到捕获异常环节;

捕获异常:运行时系统会逐步地去寻找合适的处理器,如果找到了与抛出的异常匹配的处理器,就会执行相关的异常处理逻辑;如果始终没有找到合适的处理器,那么运行时系统将会终止,而这是也就意味着Java程序也就停止了。


Java规定对于Checked Exception即检查异常(如文件操作异常和数据操作异常),必须进行异常捕获,或者声明抛出的,不能无视它; ** _ _否则会报错(根据经验应该是的,待特地去验证)__** 对于Unchecked Exception即非检查异常,系统允许忽略这些异常,即可以不对这些异常进行处理;


异常处理中常用的五个关键字:

try-catch-finally简介

1.如果对可能产生异常的程序没有进行异常处理,程序运行时就可能会产生错误,造成程序中断,尤其是在参杂人为因素的时候,更加无法保证可能出现问题的程序能不出错

下面一个异常例子:可以发现,如果对可能产生异常的程序没有进行异常处理,程序运行时就可能会产生错误,造成程序中断。


2.对可能出错的程序进行异常处理是十分必要的,try-catch-finally简介

try块:用于捕获异常;可能出现异常的代码放在try块中;

catch块:用于处理try捕获到的异常;

finally块:无论是否发生异常,其都会执行;(经过实测发现,当try块中抛出了异常,而且没有catch块能够捕获这个类型的异常的时候,finally块依旧能够执行)

1.一个try-catch-finally例子

(1)当try块中的代码出现了错误,其会抛出一个异常对象,该异常对象被catch语句块捕获处理;如果try块没有抛出异常对象,则catch块是不会执行的

(2)当异常被catch块捕获后,程序便不会输出红色的错误信息了,但我们常常通过e.printStaceTrace()方法去主动打印错误信息;主动打印的错误信息有助于我们查找错误位置和原因。

public class TryDemoOne {
 
	public static void main(String[] args) {
		// 要求:定义两个整数,输出两数的商
		int one = 12;
		int two = 2;
		System.out.println(one/two);  // 对于这种值确定的 ,没有问题
 
		Scanner sc = new Scanner(System.in);
 
		try{
			one = sc.nextInt();
			two = sc.nextInt();
			System.out.println(one/two);
		}catch(Exception e){ // try块中产生的异常会被抛出,抛出的异常会被catch块中的Exception对象捕获,
			System.out.println("程序出错了");
			// printStackTrace()方法可以打印出错误的堆栈信息,包括错误的描述,错误类型,错误出现位置
			e.printStackTrace();
		}finally{
			System.out.println("finally语句块");
		}
 
		System.out.println("最后的");
    }
}

​ ​

可以发现,主动打印的错误信息可能和输错结果交错在一起,其实错误信息的出现位置是随机的,并没有一个特定的位置。

这篇博客浏览一下就行,是一个整体的展示。

常见的异常类型及原因分析

1.ArithmeticException,算数异常

常见的情况就是除数为0;

2.NumberFormatException,数字格式化异常

数字 格式化, 数字 转换

3.ArrayIndexOutOfBoundsException,数组下标越界异常

4.NullPointerException,空指针异常

5.ClassCastException,类型转换异常

如向下转型时的异常

6.ArrayStoreException,数组存储异常?

通过实测发现:

目前可以这样理解,数组声明称申明类型的,其不能存贮父类,但可以存储本类和子类类型的对象。 ** 存疑**

7.InputMismatchException,输入格式错误异常

8.FileNotFoundException,文件未找到异常

使用try…catch…finally实现异常处理

1.多重catch结构

(1)目的:针对不同类型的异常,有不同的处理方式;

(2)不能一种异常写两遍,即不能写重了

(3)一般我们不能保证列举写出所有可能出现的异常的情况,所以在最后,习惯用Exception这个异常的父类兜底;且兜底的这个Exception必须在最后

(4)当try块抛出一个异常对象的时候,这些catch块有点依次比对的感觉,直到匹配到正确的异常类型

1.try…catch块中定义的变量不能在外面用

2.

1.System.exit(num),终止程序执行

(1)只有当触发catch(ArithmeticException e)这个catch块时,就会执行System.exit(1),该方法的作用对无条件终止程序运行,然后程序26行以后的程序都不会被执行了,自然finally也不会执行了

(2)里面的数字添几都行,只要不是0就行,看个人喜好;(只要不是数字0就代表是一种异常终止状态;当填写一个非0数据时,该非0的数字通过exit方法传递到Java虚拟机,异常终止程序执行);

注:感觉System.exit()这个东西得慎用,,,待后续实际应用中了解;

return关键字可以用来完成对于方法返回值的带回,return关键字可以终止方法的执行,把相应的数据带回到调用处。

那么当return关键字用在try-catch-finally结构的时候,return关键字是否能够和System.exit()一样终止try-catch-finally块的执行,直接把数据带回到被调用处呐?

(1)catch块中也得加上return语句;


(2)finally块中不建议加return语句;

因为finally语句块有一定会执行的特性,这会干扰方法的返回值。

try、catch、finally、return 执行顺序超详解析(针对面试题)


使用throw和throws实现异常处理

汽车可能出现各种故障,但汽车本身并没有能力来处理这些故障,这些故障需要开车的人来处理。

当一个方法中可能会出现异常,但是这个方法自身并不想或者没有能力去处理这些异常的时候,那么可以在该方法的声明处通过throws关键字来声明抛出这个异常,谁接到了这个异常就由谁来处理这个异常(即,谁调用了这个方法谁就会来处理这个异常)

1.throws简单语法规则

通过前几篇博客可知,可以通过try-catch- finally来处理异常;但当一个方法不愿意处理异常的时候,就可以使用throws关键字声明异常,这样这个方法就可以不去处理可能出现的异常,相当于是“甩锅”给下一位。


2.throws在使用时需要注意的点:

2.1)基本流程


2.2)throws抛出多个异常

test()方法调用处也要添加对应数量及类型的catch块


2.3)直接throws Exception这个异常的父类

那么,当throws Exception的时候,方法调用处是不是也可以catch(ArithmeticException e),即针对不同的细分异常类型做处理呐?

是可以的,但需要注意,如果throws Exception了,方法调用处也必须得写上catch(Exception e)

经过实测发现可以throws ArithmeticException,Exception:


2.4) 通过方法注释,让在调用方法时提示应该catch那些异常

1.为什么要使用throw主动抛出异常对象

根据已有内容可知,在程序中应该尽量避免异常,程序中出现了异常我们要进行抛出和捕获,这是一件很麻烦的事情。

但throw的作用是主动抛出异常对象:这是自己给自己找麻烦?

其实在程序运行的过程中,合理使用异常对象可以用来规避可能出现的风险;也能够利用异常对象完成一些程序的逻辑,以完成一些特定业务需求的特定逻辑。(如酒店入住时,如果年龄小于18或大于80必须要有亲友陪同的情况)

2.throw示例

throw:可以用来处理特定的业务逻辑产生的一些需求。( 这个性质的使用技巧需要在实际开发中逐渐积累经验归纳用法

疑问:自己抛出什么类型的异常,其捕获异常的时候,在该类型异常对应的catch块中,加入特定的处理逻辑,以完成对实际应用中不同逻辑情况的处理?这种应用主动抛出异常的情况,理论上没问题,实际开发中习惯这么干吗?

(1)第一种策略,自己抛出的自己去处理

(2)第二种策略,自己抛出的异常,自己不处理接着向上抛

(注:上图写的,Exception和Throwable两种,其包含的点是:throws向上抛的异常,不能是throw抛出异常的子类)

方法调用处,给了错误提示,要么对这个异常进行捕获,要么继续向上抛(这儿特殊,是main方法,如果main方法还不捕获异常继续向上抛就是抛给虚拟机了,将由系统进行异常处理)


3.throw和throws的区别

自定义异常

1.自定义异常的概念

Java异常十一这篇博客中,介绍了酒店入住时年龄小于18或大于80的情况下抛出异常。如果在一个项目中,这种类型的异常会频繁的出现,或者我们希望借由特定的类型来去针对这种特定业务进行描述的话,那么可以通过自定义异常类来实现这个需求。


2.自定义异常流程

第三步,使用自定义的异常类:

第三步,就体现了抛出什么类型的异常,就在该类型异常对应的catch块中添加对应的处理逻辑

public class TryDemoFour {
 
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		try {
			testAge();
		} catch (HotelAgeException e) {
			// TODO Auto-generated catch block
			System.out.println(e.getMessage());
			System.out.println("不允许入住");
			// e.printStackTrace();
		}catch (Exception e){
			e.printStackTrace();
		}
 
	}
 
	public static void testAge() throws HotelAgeException{
		System.out.println("请输入年龄");
		Scanner sc = new Scanner(System.in);
		int age = sc.nextInt();
		if(age <18 || age > 80){
			//throw new Exception("小于18岁和大于80岁的人需要亲友陪同。");
			throw new HotelAgeException();
		}else{
			System.out.println("欢迎入住。 ");
		}
	}
 
}

3.自定义异常的常见问题

文字太多,浏览下就行


由2可知,自定义的异常往往是由预先设计好的业务逻辑来触发的


(注:在实际开发中归纳常见的用法)


4的意思是,自定义异常的时候,在使用的时候,throw自动抛出这个异常是必要的。(下面图很多,但内容少)

异常链

1.异常链

一个异常链的简单示例:

public class TryDemFive {
 
	public static void main(String[] args) {
 
		try {
			testThree();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
 
	public static void testOne() throws HotelAgeException{
		// testOne()方法抛出了HotelAgeException异常
		throw new HotelAgeException();
	}
 
	/*
	 * testTwo()方法捕获了HotelAgeException异常,新抛出了一个Exception异常1
	 */
	public static void testTwo() throws Exception{
		try {
			testOne();
		} catch (HotelAgeException e) {
			throw new Exception("新抛出的异常1");
		}
	}
 
	/*
	 * testThree()方法捕获了Exception1异常,新抛出了一个Exception异常2
	 */
	public static void testThree() throws Exception{
		try {
			testTwo();
		} catch (Exception e) {
			throw new Exception("新抛出的异常2");
		}
	}
 
}

上面运行结果:出现了异常信息丢失的情况

……………………………………………………

问题解决方法如下:

Java中提供了保留异常的机制:

……………………………………………………

使用Throwable(String message,Throwable cause)的策略解决后的示例:

public class TryDemFive {
 
	public static void main(String[] args) {
 
		try {
			testThree();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
 
	public static void testOne() throws HotelAgeException{
		// testOne()方法抛出了HotelAgeException异常
		throw new HotelAgeException();
	}
 
	/*
	 * testTwo()方法捕获了HotelAgeException异常,新抛出了一个Exception异常1
	 */
	public static void testTwo() throws Exception{
		try {
			testOne();
		} catch (HotelAgeException e) {
			throw new Exception("新抛出的异常1",e);
		}
	}
 
	/*
	 * testThree()方法捕获了Exception1异常,新抛出了一个Exception异常2
	 */
	public static void testThree() throws Exception{
		try {
			testTwo();
		} catch (Exception e) {
			throw new Exception("新抛出的异常2",e);
		}
	}
 
}

结果:

……………………………………………………

使用initCause(Throwable cause)的策略解决后的示例:

注意使用initCause()方法时,其成熟的写法。

public class TryDemFive {
 
	public static void main(String[] args) {
 
		try {
			testThree();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
 
	public static void testOne() throws HotelAgeException{
		// testOne()方法抛出了HotelAgeException异常
		throw new HotelAgeException();
	}
 
	/*
	 * testTwo()方法捕获了HotelAgeException异常,新抛出了一个Exception异常1
	 */
	public static void testTwo() throws Exception{
		try {
			testOne();
		} catch (HotelAgeException e) {
			throw new Exception("新抛出的异常1",e);
		}
	}
 
	/*
	 * testThree()方法捕获了Exception1异常,新抛出了一个Exception异常2
	 */
	public static void testThree() throws Exception{
		try {
			testTwo();
		} catch (Exception e) {
			Exception e1 = new Exception("新抛出的异常2");
			e1.initCause(e);
			throw e1;
			//throw new Exception("新抛出的异常2",e);
		}
	}
 
}

结果:

注:在实际应用中,方法调用存在链条,自然可能出现的异常也会存在链条,而保留所有的异常信息而不丢失是很重要的。


2.异常链常见问题

1.这的意思是,对于异常链来说,一个方法抛出异常后,应该在下一个环节就把上个环节抛过来的异常捕获了,不要都挤压到最后一个环节再统一捕获。但这个过程又可能会丢失异常信息,所以需要异常链技术,在保证不丢失异常信息的情况下,很好的完成异常的捕获。



3.的意思时候,越靠近最终的方法调用处的异常,越先显示异常信息。下面的图没东西,看看就行


注:(1)估计在实际开发中,异常链的每一个环节,到底要抛出什么类型的异常,是个需要经验积累和常见处理方式归纳的过程。

一个带异常处理的Scanner实例

1.一个带异常处理的Scanner实例

注:输入类型错误时,使用sc.next()去接受这次输入;然后上述程序最后需要sc.close()关闭这个流。

2.for(表达式q;;表达式3)的意思