异常机制


异常机制

在某些情况下,由于编写的代码不适当,会发生编译异常或运行异常,有时甚至会发生错误,出现异常时,程序都会在异常处终止运行

Exception:叫做异常,代表程序可能出现的问题

类型

每一个异常都是一个类,并且都继承自 Exception 类(Exception类继承自 Throwable 类)

异常分为:运行时异常、编译时异常

运行时异常

如数组越界异常,空指针异常,算术异常

特点

编译阶段无法感知代码是否会出现问题,只有在运行的时候才知道会不会出错(正常情况下是不会出错的),这样的异常称为运行时异常,异常也是由类定义的,所有的运行时异常都继承自RuntimeException。

编译时异常

编译时异常明确指出可能会出现的异常,在编译阶段就需要进行处理(捕获异常)必须要考虑到出现异常的情况,如果不进行处理,将无法通过编译!默认继承自Exception类的异常都是编译时异常。

如使用Clone方法时,需要先使类实现 Clonable 接口,对Clone方法进行重写,才能使用clone方法,否则会抛出 CloneNotSupportException 异常

//Object类中的clone方法定义
protected native Object clone() throws CloneNotSupportedException;

错误

错误(Error)不属于异常(Exception),但二者都继承于Throwable

Error:代表系统级别错误(属于严重问题)
系统一旦出现问题,sun公司会把这些错误封装成Error对象,Error是给sun公司自己用的,不是给程序员用的,我们开发人员不用管它

作用

  1. 异常时用来查询bug的关键参考信息
  2. 异常可以作为方法内部的一种特殊返回值,以便通知调用者底层的执行情况(throw)

处理方式

分为三种:JVM默认的处理方式、自己处理、抛出异常

JVM默认的处理方式

  1. 把异常的名称,异常原因及异常出现的位置等信息输出在了控制台
  2. 程序停止执行,异常下面的代码不会再执行了

自己处理(捕获异常)

  1. 格式:
try{
		//可能出现异常的代码;
} catch(//异常类名+变量名){
		//异常的处理代码
}
  1. 好处:可以让程序继续往下执行,不会停止
  2. 细节:
  • catch中的代码只有在 try中出现了对应的异常才会执行
  • 如果 try 中可能会遇到多个问题,要写多个 catch 与之对应(但这些异常中如果存在父子关系的话,那么父类一定要写在下面)
    在JDK 7 之后,我们可以在 catch 中同时捕获多个异常,中间用 | 进行隔开,表示如果出现了A异常或者B异常的话,采取同一种处理方案
  • 如果 try 中遇到的问题没有被捕获,最终还是会交给虚拟机进行处理
  • 如果 try 中遇到了问题,那么 try 下面的其他代码就不会执行了,直接跳转到对应的 catch 中

抛出处理

需要用到 throws 和 throw 两个关键字

  • throws:
  1. 写在方法定义处,表示声明一个异常,告诉调用者,使用本方法可能会有哪些异常
public void 方法名() throws 异常类名1,异常类名2{
	...
}
  1. 编译时异常:必须要写
    运行时异常:可以不写
  • throw
    写在方法内,结束方法。手动抛出异常对象,交给调用者,方法中下面的代码不再执行了
public void 方法名(){
		throw new NullPointerException();
}

方法

使用的是定义在 Throwable 中的成员方法

String getMessage() 返回此throwable的详细消息字符串
String toString() 返回此可抛出的简短描述
void printStackTrace() 把异常的错误信息输出在控制台

常用的方法是第三个 printStackTrace ,其底层是利用 System.err.println 进行输出,把异常的错误信息以红色字体输出在控制台,并且不会停止程序运行

自定义异常

步骤

  1. 定义异常类
  2. 写继承关系
  3. 空参构造
  4. 带参构造

定义

如果想自定义一个编译时异常,可以新建一个类并继承 Exception 类

如果想自定义一个运行时异常,可以新建一个异常,此时idea会将其自动继承 RunTimeException 类

比如,新建一个运行时异常:

public class StudyException extends RunTimeException{
	public StudyException(String message){
		super(message);   //调用父类的带参构造方法
	}
}

使用

在定义一个方法时,在异常分支下,可以选择抛出异常

但是抛出运行时异常和编译时异常的情况略有不同

  • 运行时异常

直接在异常分支下加 throw new +异常() ,小括号内可写抛出异常的原因

public static void main(String[] args){
	int res = div(4,0);
	sout(res);
}
public static int div(int a, int b){
	if(b==0) throw new DivException("除数不能为0");
	return a / b;
}

class DivException extends RunTimeException{
	public DivException(String message){
		super(message);
	}
}
  • 编译时异常

除了在异常分支下加 throw new +异常()外,还需要在方法后面加上 throw 异常

即如果在方法中抛出了一个非运行时异常,那么必须告知函数的调用方我们会抛出某个异常,函数调用方必须要对抛出的这个异常进行对应的处理才可以

public static void main(String[] args)throw Exception{
	int res = div(4,0);
	sout(res);
}
public static int div(int a, int b) throw Exception{
	if(b==0) throw new DivException("除数不能为0");
	return a / b;
}

class DivException extends Exception{
	public DivException(String message){
		super(message);
	}
}

在方法的定义以及使用该方法的方法后面都需要加 throw ,如果方法中有多个异常,需要用逗号隔开,写上所有的异常

  • 特殊情况

在重写方法时,如果父类中的方法表明了会抛出某个异常,只要重写的内容中不会抛出对应的异常我们可以直接省去:

@Override
protected Object clone() {
    return new Object();  //此时子类中没有使用clone方法,所以不用写throw
}

意义

就是为了让控制台的报错信息更加地见名知意

出现异常时的打印信息

当出现异常时:

程序会终止,并且会打印栈追踪信息,这里简单介绍一下,实际上方法之间的调用是有层级关系的,而当异常发生时,方法调用的每一层都会在栈追踪信息中打印出来,比如这里有两个at,实际上就是在告诉我们程序运行到哪个位置时出现的异常,位于最上面的就是发生异常的最核心位置,我们代码的第15行。

并且这里会打印出当前抛出的异常类型和我们刚刚自定义异常信息。


Author: havenochoice
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source havenochoice !
评论
  TOC