程序出错老看不懂,今天专门去总结了一下程序异常的情况

Java异常处理

程序运行时发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。异常发生时,是任程序自生自灭,立刻退出终止。

Scanner sc=new Scanner(System.in);
String ss=sc.nextLine();
int kk=Integer.parseInt(ss);
System.out.println("用户输入数据为:"+kk);
//用户输入abc
/*
则执行结果为:
Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at test.A.main(A.java:9)
*/

意外产生和处理过程概述

运行时有许多因素引起出错,硬件失败,除法溢出,数组下标越界。

出错的位置都在方法Method里

出错后方法生成一个Exception对象,并把它交给JVM。这个对象里包括许多信息:错误类型,错误位置。JVM负责处理Exception对象

这种生成Exception对象并交给系统的过程叫抛出意外throwing an exception

一个方法抛出意外后,JVM就试图在“调用栈”里找能处理这个类型Exception对象的方法。找到就执行,找不到程序中止

出错的处理方式

以前正常流程代码和问题处理代码相结合。现在将正常流程代码和问题处理代码分离,以提高阅读性。

public static int bbb(int k) {
	if (k > 100) {
	//输出正常处理
	return 0;
	} else if (k > 10) {
	return -3;
	} else if (k > 0) {
	return -2;
	} else {
	return -1;
	}
}

可以根据返回值判断程序的执行情况,如果>=0表示正常执行完毕,如果<0,则返回不同的负值表示不同的运行情况。调用方法时可以根据不同的返回值进行不同的处理

java中引入异常对象的目的在于给调用方更加详细的出错说明,以达到异常处理和正常代码分离,并且按照不同的异常进行处理的目的

其实异常就是Java通过面向对象的思想将程序中的问题封装成了对象,用异常类对其进行描述。1、不同的问题用不同的类进行具体的描述。2、问题很多,意味着描述的类也很多,将其共性进行向上抽取就形成了异常体系

异常

Java异常是Java提供的用于处理程序中错误的一种机制

  • 所谓错误是指在程序运行的过程中发生的一些异常事件。如除0溢出、数组下标越界、所需要读取的文件不存在
  • 设计良好的程序应该在异常发生时提供处理这些错误,使得程序不会因为异常的发生而阻断或者产生不可预见的结果
  • Java程序的执行过程中如果出现异常事件,可以生成一个异常类对象,该异常对象封装了异常事件的信息并经被提交给Java运行时系统,这个过程称为抛出异常throw
  • 当Java运行时系统接收到异常对象时,会寻找能处理这一异常的代码并把当前异常对象交给其处理,这个过程称为捕获异常

Java异常处理的关键字有throws、throw、try、catch、nally共5个关键字

异常用途

异常就是在程序运行时由代码所产生的不正常状态。换句话说,异常就是一个运行时不支持异常处理的计算机语言中,错误必须被人工进行检查和处理,这显然麻烦而低效

Java语言提供了异常处理机制,为方法的异常终止和出错处理提供了清楚的接口

  • 1、用来在发生运行异常时告诉程序如何控制自身的运行,以防止错误的进一步恶化
  • 2、每个方法必须对他可能抛出的异常进行预先声明,在定义方法时,必须声明这个方法可能会抛出哪一种或几种异常

异常的分类

JavaSE中定义了很多异常类,这些类可以对应各种各样可能出现的异常事件。

int k=0;
System.out.println(100/k);
System.out.println("end...");
允许则会发现出现报错ArithmeticException,同时显示出错行号,但是看不到end...的输出

在这里插入图片描述
Throwable类是Java异常类型的顶层父类,一个对象只有是Throwable 类的(直接或者间接)实例,他才是一个异常对象,才能被异常处理机制识别

在这里插入图片描述

Java异常可以分为3大类

所有异常对象的父类为Throwable—Object

1、Error及其子类:错误,一般指的是虚拟机的错误,是由Java虚拟机生成并抛出,程序不能进行处理所以也不加处理,例如OutOfMemoryError内存溢出、调用栈溢出StackOverFlowError

public static void pp(){
System.out.println("this is pp....");
pp();
}

在控制台上显示Exception in thread main java.lang.StackOverowError

2、RuntimeException及其子类:运行时异常(非受检型异常),是由于编程bug所导致,希望越早发现越好,所以不进行处理,直接中断报错即可,编程人员针对报错信息修改程序bug来解决问题。

5种常见的异常类型

常见的第一种运行时异常:ArithmeticException算术异常,就是执行数学计算时产生的非正常情况,如除以0
在这里插入图片描述

常见的 第二种运行时异常:NullPointerException空指针异常(试图访问null对象的引用)
在这里插入图片描述

常见的 第三种运行时异常:IndexOutOfBoundsException下标出界
在这里插入图片描述
解决方法是通过下标访问时应该判定下标的取值是否合法
在这里插入图片描述

常见的 第四种运行时异常:ClassCastException试图把一个对象转换成非法类型
在这里插入图片描述

常见的第五种运行时异常:NumberFormatException数据格式异常,一般出现在数据类型转换中
在这里插入图片描述
进行数据类型转换之前可以进行格式验证
在这里插入图片描述
这里进行数据的合法性验证后再进行数据类型转换比较繁琐,所以最终的解决方案是使用try/catch语法结果,针对出现问题后进行处理
在这里插入图片描述

3、Exception及其子类中除了RuntimeException及其子类之外的其它异常:受检型异常(非运行时异常),这类异常属于明知道可能出现,但是没有办法杜绝的异常。这类异常一般采用try/catch或者throws声明抛出的方式进行异常处理,当程序出现了非正常情况,尽量保证程序正常结果,而不是立即中断

  • 受检型异常:明知道存在这种异常的可能,但是没有办法杜绝,所以必须进行编码异常处理
  • 非受检型异常:这种异常不需要进行处理,发现的越早越好

命令行上的数据录入

异常的捕获和处理

语法规则:

try{
	try代码段中包含可能产生异常的代码,有人称为陷阱代码,在执行过程中如果出现了异常,则异常之后的
java语句不会执行。转而执行catch部分的代码
} catch(SomeException e){ 可以写多个
	try后可以跟一个多个catch代码段,针对不同异常执行不同的处理逻辑。当异常发生时,程序会中止当前的
流程,根据获取异常的类型去执行响应的代码段。注意异常类型判定时是从上向下逐个判断的。
} finally{
finally代码是无论是否发生异常都会执行的代码
}

在这里插入图片描述
注意:

  • try块中的局部变量和catch块中的局部变量(包括异常变量),以及nally中的局部变量,他们之间不可共享使用
  • Java采用的是终结式异常处理机制,java中异常处理的任务就是将执行控制流从异常发生的地方转移到能够处理这种异常的地方去。也就是说:当一个函数的某条语句发生异常时,这条语句的后面的语句不会再执行,它失去了焦点。执行流跳转到最近的匹配的异常处理catch代码块去执行,异常被处理完后,执行流会接着在“处理了这个异常的catch代码块”后面接着执行。
  • try{}后面必须跟一个nally或者catch,其中有个特殊写法可以省略后面的nally和catch

Java异常处理

try–用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。 catch–用于捕获异常。catch用来捕获try语句块中发生的异常。 nally–nally语句块总是会被执行。它主要用于回收在try块里打开的资源(如数据库连接、网络连接和磁盘文件)。只有nally块,执行完成之后,才会执行try或者catch块中的return或者throw语句,如果nally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。 throw-- 人为编程实现抛出异常。

在这里插入图片描述

try语句

try语句指定一段代码,该段代码就是一次捕获并处理异常的范围

在执行过程中,该段代码可能会产生并抛出一种或几种类型的异常对象,它后面的catch语句要分别对这些异常做响应的处理

如果没有异常产生,所有的catch代码段都能略过不执行

在这里插入图片描述

catch语句

在catch语句块中是对异常进行处理的代码,每个try语句块可以伴随一个或多个catch语句,用于处理可能产生的不同类型的异常对象,同时允许异常的继续抛出

catch只匹配成功一次即可,注意不是最佳匹配,例如交换Exception和NumberFormatException的位置,这里就会语法报错,报错的提示信息为Unreachable catch block for NumberFormatException. It is already handled by the catch block for Exception,含义是catch(NumberFormatException e)是不可达语句,NumberFormatException是Exception的子类。

在这里插入图片描述

Integer kk=null;
try{
	String str="123.456";
	kk=Integer.parseInt(str);
	System.out.println("数据转换结束");
} catch(NumberFormatException e){
	System.out.println("数据格式错误!");
//继续抛出异常,不再是直接消费处理掉了,导致的结果是try之后的代码并没有得到执行
	throw new Exception(e.getMessage());
	//throw e;在用在方法上throws声明抛出,因为NumberFormatException是运行时异常
	//throw new Exception(""); 需要在方法上声明抛出,因为Exception是受检型异常
} catch(Exception e){
	System.out.println("出现了错误:"+e.getMessage());
}
	System.out.println("转换结果为"+kk);

在catch中声明的异常对象封装了异常发生的信息,在catch语句块中可以使用这个对象的一些方法获取这些信息

  • getMessage():String用于获取有关异常事件的信息,一般是异常信息的描述,例如【For input string:“123.456”】
  • toString():String,输出格式【java.lang.NumberFormatException: For input string: “123.456”】
  • printStackTrace():void用来跟踪异常事件发生时执行堆栈的内容,注意:这里的输出默认采用错误流 System.err进行输出,如果还使用了System.out进行输出,则不能保证显示顺序
    在这里插入图片描述
    注意:使用printStackTrace输出调用栈使用的是System.err输出报错信息,不是编程使用的System.out,所以输出顺序有可能和预计不一致

常见的3种输出异常的用法

  • System.out.println(e);//java.lang.ArithmeticException: / by zero
  • System.out.println(e.getMessage()); // / by zero
  • e.printStackTrace(); 调用栈
  • JDK1.7引入多异常捕获

大部分情况下可能会出现多种不同异常但是处理方法一致时,注意这多个异常类型之间没有继承关系,如果有继承关系则应该写成try{}catch(父类型e){}即可

try{
	int k=0;
	system.out.println(10/k);
} catch(ArithmeticException e){
	System.out.println(e);
}catch(NullPointerException e){
	System.out.println(e);
}

两次捕捉异常的处理是一样的,就可以将两个异常卸载一个catch中,其中多个异常类型之间使用|分割

try{
	int k=0;
	system.out.println(10/k);
} catch(ArithmeticException | NullPointerException e){//含义和上面的一致
	System.out.println(e);
}

try{} catch(IndexOutOfBoundException | NumberFormatException | ArithmeticException e){ }。这里需要注意的是捕获一种异常时,异常对象没有final的含义,但是多种异常时隐含了final含义,所以不能修改对象引用

try{
	int k=0;
	system.out.println(10/k);
} catch(ArithmeticException e){
	System.out.println(e);
	e=new ArithmeticException("就是不告诉你"); //注意按照Java变量的规则,这里的类型不能变,所以可以new当前类型,也可以子类型
	throw e;
}catch(NullPointerException e){
	System.out.println(e);
}

但是使用多异常时则不能修改e对象

try{
	int k=0;
	System.out.println(10/k);
}catch(ArithmeticException | NullPinterException e){ //要求异常型之间不能有继承关系,如果有继承关系应该只写大异常类型即可
	System.out.println(e);
	e=new ArithmeticException("就是不告诉你");
	throw e;
}

最后需要记得catch可以写多个,但是有顺序,需要时先小后大,如果先大后小,则小代码不可及

正确写法:

try{
	int k=0;
	System.out.println(10/k);
}catch(ArithmeticException | NullPointerException e){
	System.out.println("小异常:"+e);
}catch(Exception e){
	System.out.println("大异常:"+e);
}

错误写法: 因为匹配规则是顺序匹配,不是最佳匹配

try{
	int k=0;
	System.out.println(10/k);
} catch(Exception e){
	System.out.println("大异常:"+e);
} catch(ArithmeticException | NullPointerException e){ //报错
	System.out.println("小异常:"+e);
}

编码练习:

要求输入一个合法的字符串类型的整数,进行数据类型转换,如果数据不合法则中断程序执行,并进行提示。

	Scanner sc=new Scanner(System.in);
	System.out.println("请输入一个整数");
	String ss=sc.nextLine();
try {
	int kk=Integer.parseInt(ss.trim());
	System.out.println("输入数据正确,具体值为:"+kk);
} catch(NumberFormatException e){
	System.out.println("输入的数据格式错误!");
} catch (Exception e) {
	System.out.println("其它错误:"+e.getMessage());
}

可以使用定义多个catch语句捕获不同的异常,采用不同的异常处理方法。但是一般不建议使用catch作为程序处理分支

catch可以消费掉异常,这个异常不会再提交运行时环境处理

大异常在前小异常在后会出现程序不可达的问题,所以多个catch的时候注意书写顺序

finally语句

finally语句为异常处理提供一个统一的出口,使得在控制流程转到程序的其他部分以前,能够对程序的状态作统一的管理

无论try所指定的程序块是否抛出异常,finally所指定的代码都要执行。

try可以没有catch而直接使用finally,当然在方法上声明抛出

Scanner sc=new Scanner(System.in);
System.out.println("请输入一个整数");
String ss=sc.nextLine();
try {
	int kk=Integer.parseInt(ss.trim());
	System.out.println("输入数据正确,具体值为:"+kk);
} catch(NumberFormatException e){
	System.out.println("输入的数据格式错误!");
} catch (Exception e) {
	System.out.println("其它错误:"+e.getMessage());
} finally{ //不管陷阱代码中是否出现异常,是否执行了catch语句,finally语句块一定要执行
	System.out.println("finally....");
}
	System.out.println("end pp....");//这个语句有可能不执行,但是finally是无论如何都会被执行。典型案例去掉所有的catch语句,当出现异常时finally还是执行了的,但是这个语句没有执行

通常在nally语句中可以进行资源的清除工作,例如关闭打开的文件,删除临时文件等。注意Java的垃圾回收机制只能回收再堆内存中对象所占用的内存,而不会回收任何物理资源(如数据库连接、网络连接和磁盘文件等)

特殊情况导致finally执行出现问题

  • 在前边的代码中使用System.exit(int)退出应用

System.exit(int status)这个方法是用来结束当前正在运行中的java虚拟机。

status是非零参数,那么表示是非正常退出。
System.exit(0)是正常退出程序,而System.exit(1)或者说非0表示非正常退出程序。

try{
	int k=10;
	System.out.println(10/k);
	System.exit(0); //执行这里表示无条件立即终止执行
} finally{
	System.out.println("finally....");
}
  • 程序所在的线程死亡或者cpu关闭
  • 如果在finally代码块中的操作又产生异常,则该finally代码块不能完全执行结束
Scanner sc=new Scanner(System.in);
System.out.println("请输入一个整数");
String ss=sc.nextLine();
try {
	int kk=Integer.parseInt(ss.trim());
	System.out.println("输入数据正确,具体值为:"+kk);
	System.exit(0);
} finally{
	int kk=0;
	System.out.println(10/kk); //这里出现异常,提供提示为	ArithmeticException,而不是上面出现的NumberFormatException
	System.out.println("finally....");
}

注意点:finally块和return

public static int pp(){
try{
	int i=1;
	System.out.println(10/i);
	System.out.println("try....");
	return 0; //由于有finally语句,所以这里的return并不会立即返回,而是等待finally执行后才能返回
} catch(Exception e){
	System.out.println("catch...");
return 1;
} finally {
	System.out.println("finally...");
	return 2; //这里的return会替换前面的return结果
}
}
public static void main(String[] args){
	int res=pp();
	System.out.println("Main:"+res);
}

返回值不管是否异常都是finally中的return 2;

注意 :finally、final和finalize的区别

JDK1.7+引入的自动关闭的写法

JDK1.7引入了自动关闭资源的try语句块,在后续模块中演示

try(FileInputStream fis=new FileInputStream(“a.txt”)){}在方法上声明抛出异常即可,没有finally语句块

这里引入了Closeable和AutoCloseable接口,可以自动关闭资源,

try(FileInputStream fis=new FileInputStream("c:/A.java")){
	int content=fis.read();
while(content!=-1){
	System.out.print((char)content);
	content=fis.read();
}
}这里可以没有catchfinally

异常的捕获和处理

Java的异常处理机制使得异常事件能够沿着被调用的顺序向前寻找,只要找到符合该异常种类的异常处理程序,就会进行异常处理逻辑的执行,并且被消费掉,不会继续查找。

容易出现的一个编码问题:隐藏异常

在这里插入图片描述

异常处理允许嵌套,但是一般不建议嵌套层数过多

最佳异常相关编程实践

1、不要在fianlly中使用return。

public static int pp() {
	int res = 0;
try {
	int i = 0;
	res = (10 / i);
	System.out.println("try....");
} catch (Exception e) {
	res = -1;
} finally {
	res = 100;
}
	return res;
}

2、不要在finally中抛出异常。
3、减轻finally的任务,不要在finally中做一些其它的事情,finally块仅仅用来释放资源是最合适的。
4、将尽量将所有的return写在函数的最后面,而不是try … catch … finally中

自定义异常

1、通过继承java.lang.Exception类【受检型异常】或者RuntimeException【非受检型异常】声明自定义异常类【也允许继承Throwable或者Exception的子类】。例如因为业务导致可能出现的问题账户余额不足

在这里插入图片描述
需要编写的程序:

  • 一个无参构造函数

其余的方法可以使用IDE工具自动生成

  • 一个带有String参数的构造函数,并传递给父类的构造函数。
  • 一个带有String参数和Throwable参数,并都传递给父类构造函数
  • 一个带有Throwable 参数的构造函数,并传递给父类的构造函数。

2、在方法适当的位置生成自定义异常的实例,并使用throw语句抛出

public void zhuan(Account source, Account target, long mount)  throws AccountException {//告知调用方,这个方法可能会出现这个问题,要求调用方法进行处理
// 因为定义AccountException的父类为Exception,所以这个异常属于受检型异常,所以必须进行处理,
try/catch或者throws
if (source.getBalance() < mount)
throw new AccountException();
... ...
}

3、在方法的声明部分用throws语句声明该方法可能会抛出的异常或者使用try/catch结构直接进行处理或者自定义异常为运行时异常

  • 使用运行时异常是可以简化调用方编程,但是也可能导致调用方根本不知道这里可能会出现异常

4、注意:方法重写时需要抛出比原来方法一致或者更少的异常

异常处理建议规则

  • 1、不要过度使用异常,不要把异常当做不同的逻辑分支对待
  • 2、不要使用过于庞大的try块
  • 3、避免使用catch all语句 try{}catch(Throwable t){}
  • 4、坚决不要忽略捕获的异,坚决不允许catch块中内容为空
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值