目录:
一:异常的基本介绍
二:异常的分类
三:异常的抛出
四:异常的捕获
1.throws
2.关键字try-catch组合使用处理异常
五:finally的使用
六:自定义的异常类
一:异常的基本介绍:
1.定义:程序执行过程中遇到的不正常的行为,即阻碍程序的执行的行为称作异常。异常可以通过代码解决。
2.本质:异常本身是一个类。我们所说的发生异常,实际上是一个异常类被执行了。
异常体系图:
从上图可知:
Throwable:是异常体系中的顶层类,派生出Error和Exception两个重要的子类。
Error:指的是Java虚拟机无法解决的严重问题。比如:JVM的内部错误,资源耗尽(内存溢出和栈溢出)。
Exception(异常):②
3.异常关键字:异常类的使用涉及五个关键字:throw, throws, try, catch, finally
二:异常的分类
1.编译时异常:也叫受查异常(Checked Exception),在程序编译期间发生的异常。若这个异常不解决,那么程序无法运行。比如:
当你对一个类进行拷贝的时候,如图,类的对象Person调用clone方法时,调用者 (指的是包含含有异常的代码的方法,在main方法中调用,此时main方法是调用者) 需要对代码中含有的异常进行解决,如果没有解决,那么程序无法运行,这就是在编译过程中发生的异常(即编译时异常)。
2.运行时异常:也叫非受查异常(Unchecked Exception),在程序运行期间发生的异常。这个异常能通过编译,但在程序运行的时候就会报错,从而中止程序运行。比如:
①:ArithmeticException (算术异常):
注:正常来说0是不能作分母当被除数的,但此时编译器没有进行报错,当运行程序时就会报错,报错了一个异常ArithmeticException并打印到控制台上,这是算术异常,是运行时异常的子类之一。
②:ArrayIndexOutOfBoundsException (数组越界访问异常):
注:这里定义了一个内存空间为3的数组,下标选择范围为0~2,此时访问数组的3下标,越界访问了数组的空间,但在进行编译时编译器没有报错,却在运行时报了一个异常ArrayIndexOutOfBounds的错误出来,这是数组越界访问异常,是运行时异常的子类之一。
③:NullPointerException (空指针异常):
注:这里定义了一个引用类型为空指针的数组,说明这个数组未引用任何值,所以是无法使用这个引用来访问编译器的库方法,但此时数组访问了系统的求数组长度的库方法,而系统并未报错,却在运行时报了NullPointerException异常错误,这是空指针异常,是运行时异常的子类之一。
总结:编译时异常必须要先解决掉问题,程序才能执行。运行时异常可以通过编译,但会在运行过程中出问题,需要运行中解决或者在编译时解决都可。
三:异常的抛出-throw
throw关键字的运用是手动抛出一个异常。
语法:throw new xxxException("异常产生的原因")
运用场景:当我们需要检测程序中的可能会出现的错误时,在可能发生错误的地方抛出一个异常,告知调用者。比如:参数检测
我需要利用这个testException方法来检测我的数组中有没有10这个数字,若有则报异常。
第一个数据是正确的,没有异常发生。
第二个数据里面含有10这个数字,触发了异常,一旦触发了异常,且异常没有被解决,那么程序会立马中止并报错。
这里的n == 10 是告知调用者异常的原因是数组数据中含有10这个数字。
注:利用throw关键字在代码可能会发生错误的地方抛出一个异常,能有效的检查代码,同时将错误信息返还给调用者。
四:异常的捕获
异常的捕获就是处理代码中出现的异常。有两个可以解决异常的方法。用thorws关键字解决或try-catch解决
1.throws
1.1 语法:修饰符 方法名 (参数列表) throws 异常类型1,异常类型2… {
}
1.2 该关键字是声明并抛出代码中可能存在的异常,通过该方法解决的异常最后都是抛给JVM进行处理。(throws只是暂时的能解决编译时异常,无法解决运行时异常,所以一般是在出现编译时异常时使用)比如:
此时main方法这个调用者中的代码出现了一个编译时异常:CloneNotSupportedException
如果我们用throws抛出了这个异常,在main方法中存在的异常能得以暂时解决,即:这个CloneNotSupportedException是被main方法交给了上一层的调用者解决,最终被JVM进行处理。在main方法中没有对其实际处理掉。
1.3 补充:1.若代码中存在多个编译时异常,可以同时声明多个异常。
2.若抛出的异常具有父子关系,则直接声明父类即可。
3.声明的异常必须是Exception或Exception的子类。
4.将光标放在需要抛出的异常方法上,Alt+ Enter 快速处理
2.关键字 try-catch 组合使用处理异常
2.1 语法:
try {
//可能存在异常的代码
}catch (要捕获的异常类型 e) {
//try中抛出异常,若被catch捕获的异常类型对上,或是其子类,就会被捕获到e中去(e可以是其它的字母)
//然后对异常进行处理,处理完后跳出try-catch结构,继续执行后序代码
}catch (异常类型 e) {
//对异常进行处理
}
2.2 throws没有对异常进行真正的处理,try-catch是在代码运行时就会处理掉其中的异常
比如:此时的异常ArithmeticException时在main方法中就被处理了,没有交给上一层调用者进行处理,并且能执行后序代码。
2.3 可以存在多个catch接收异常类型,但不会抛出多个异常进行同时处理
比如:
public static void main(String[] args) {
try {
int[] array = new int[2];
System.out.println(array[3]);
System.out.println(10 / 0);
}catch (ArithmeticException e) {
e.printStackTrace();//这个能将你解决的异常打印到控制台上
}catch (ArrayIndexOutOfBoundsException e){
e.printStackTrace();
}
System.out.println("程序正常运行!");
}
结果:
数组越界访问异常先发生,异常发生之后,它之后的代码就不会再执行了,会直接跳转到catch中去捕获并处理异常
public static void main(String[] args) {
try {
System.out.println(10 / 0);
int[] array = new int[2];
System.out.println(array[3]);
}catch (ArithmeticException e) {
e.printStackTrace();//这个能将你解决的异常打印到控制台上
}catch (ArrayIndexOutOfBoundsException e){
e.printStackTrace();
}
System.out.println("程序正常运行!");
}
结果:
这里是算术异常先发生,发生之后,处于它之后的代码不会执行,就不会触发数组越界访问异常
总结:尽管代码中可能会同时含有多个异常,但编译器只会处理优先碰到的异常,其余的存在异常的代码就不会执行。
补充:e.printStackTrace 只会显示运行时异常和自己手动抛出的异常,不会显示编译器本身带有的编译时异常。
2.4 若catch捕获的异常类型存在父子关系,那么子类在前,父类在后。
比如:
此时下图中的异常被ArithmeticException 捕获到
此时下图中的ArrayIndexOutOfBoundsException 被父类RuntimeException 捕获到
如上图:子类异常只有一个算术异常:ArithmeticException,其它的异常则有父类兜底。
五: finally关键字的使用
1.语法:①形成try-catch-fianlly连用 ②形成try-finally连用
2.特点:只要存在finally语句,那么这个语句中的代码一定会实现
此时代码中存在数组越界访问异常没有处理,finally中的语句执行了
此时代码中的数组越界访问异常处理了,finally中的代码执行了
总结:不管try-catch中的异常有没有处理,finally语句一定会执行
3.finally语句的作用:因为finally语句中的代码一定会被执行,所以会用来进行对一些资源清理的扫尾工作。
4.finally的特例:当try-catch中出现有return时,finally语句依旧会被执行
public static void main(String[] args) { try { //System.out.println(10 / 0); int[] array = new int[2]; System.out.println(array[3]); }catch (ArrayIndexOutOfBoundsException e) { System.out.println("捕获到了异常!"); return; } finally { System.out.println("finally中的代码一定会执行!"); } }
代码运行结果:
总结:finally语句执行的时机是在方法返回之前,即return之前。(如果try或catch中有return语句,那么finally会在return执行之前先执行)
5.关于finally的一道面试题
try{
return 10;
}finally{
return 20;
}
A:10 B:20 C:30 D:编译失败
答案是B。因为在try中的return执行时,会先执行finally语句,此时里面的return语句执行,返回20,程序结束。即当try和finally中都存在return语句时,会执行finally中的return。
六:自定义的异常类
为什么要自定义一些异常类呢?因为编译器给予我们的异常类有时是不符合我们的需求的,这就需要自定义异常类来符合我们的实际情况。
1.异常类的结构:
创建一个异常类首先了解它的结构:
//以算术异常结构为例
public class Arithmetic extends RuntimeException {
public ArithmeticException () {
super();
}
public ArithmeticException(String s) {
super(s);
}
}
//第一步:用public class修饰你要自定义的异常,用extends来继承父类异常
//注:继承RuntimeException 说明你要自定义的异常是运行时异常
// 继承Exception 说明你要自定义的异常是编译时异常
//第二步:构造两个构造方法,一个没有参数,一个拥有String类型的参数。
2. 现在来实现一个用户登录异常
2.1 先构造用户账号异常和用户密码异常
//用户账号异常
public class UserException extends RuntimeException {
public UserException() {
super();
}
public UserException(String s) {
super(s);
}
}
//用户密码异常
public class PasswordException extends RuntimeException {
public PasswordException() {
super();
}
public PasswordException(String s) {
super(s);
}
}
2.2 构造测试自定义异常类的代码
public static void main(String[] args) {
String userName = "zhangsan";
String userPassword = "123456";
System.out.println("请输入用户名:");
Scanner scanner = new Scanner(System.in);
String user = scanner.nextLine();
System.out.println("请输入密码:");
String password = scanner.nextLine();
if (!user.equals(userName)) {
throw new UserException("用户名错误!");
// 这个正常是可以用System.out.println("用户名错误!") 来代替的
//不过此时是以异常的形式展现出来,有利于调用者清楚明白代码错误发生的地方
}
if (!password.equals(userPassword)) {
throw new PassWordException("用户密码错误!");
}
System.out.println("登录成功!");
}
测试结果:
这就是自定义异常类的作用,自定义异常类可以维护符合我们实际情况的异常结构。