目录
前言:
异常简介:
Error类:
Exception类:
Exception异常:
运行异常:
编译异常:
throw和throws关键字:
throw:
throws:
try-catch关键字:
finally:
为啥叫受查异常?
throw和throws的区别:
总结:
前言:
应该都听说过Java中的异常处理,其实不止Java中有异常处理,我们学过的其他语言中的报错是不是也是异常呢?对,是,但是Java中的异常为什么要学习?因为它可以当程序真的报错而崩溃时,我们进行异常处理可以使其跳过,程序还能正常的往下执行。
像C语言,如果程序报错就会崩溃,而Java可以得知这个异常,可以继续正常运行。
异常简介:
异常就是异于常态,和正常情况不一样,有错误。在Java中,阻止当前方法或作用域的情况,称之为异常。
Java中所有异常类都继承于Throwable类,对,你没听错,这些异常都是类。
Java中,所有异常都有一个公共祖先Throwable(可抛出)。Throwable指定代码中可用传播机制通过Java应用程序传输的任何问题的共性。
Throwable主要包括两个大类,一个是Error类,另一个是Exception类。
注意,异常和错误是两种东西。不是同一概念。
Error类:
其中Error类中包括虚拟机错误和线程死锁,一旦Error出现了,程序就彻底的挂了,被称为程序终结者。
Exception类:
也就是通常所说的“异常”。主要指编码、环境、用户操作输入出现问题,Exception主要包括两大类,非检查异常(RuntimeException)和检查异常(其他的一些异常)。
因为Error类出现,程序就会挂掉,所以讲的意义不大。这一篇我们就来重点了解Exception类这个异常。
Exception异常:
Java中异常是分种类的,异常分为两大类:运行异常和编译异常(下图只是简单分类)。
比如算数异常:
当发生异常时,不会再执行异常后面的代码。
比如空指针异常和数组越界异常:
此时我们来观察运行异常和编译异常的区别,比如我们利用递归来观察:
public static void func() {func();
}
public static void main(String[] args) {func();
}
执行上面的代码会执行,之后报错。
Error指的是Java虚拟机无法解决的严重问题,比如JVM的内部错误、资源耗尽等,比较典型的是:StackOverflowError 和 OutOfMemoryError(上面的死递归就是栈溢出)。
Exception:异常产生后程序员可以通过代码进行处理,是程序继续执行。
运行异常:
异常分为运行时异常和编译时异常,我们刚才说的空指针异常,算术异常,数组下标越界访问异常都是运行异常。运行异常又称非受查异常。这个名字我们一会了解。
注意此时程序至少运行了。
编译异常:
那么接下来我们就来看看编译异常是什么。还记不记得我们之前讲的克隆方法,声明克隆接口表示能被克隆,要通过向下转型,还有要扔出异常才能被克隆。
class Person implements Cloneable{public String name;public Person(String name) {this.name = name;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +'}';}
}public class Test {public static void main(String[] args) throws CloneNotSupportedException {Person person1 = new Person("zhansan");Person person2 = (Person) person1.clone();System.out.println(person2);}
}
是否还记得我们当时一直出现红色标志,我们进行了向下转型,重写了该方法并且使用了Cloneable接口,但是还是不能运行,必须抛出异常才可以运行。其实这里就发生了编译异常。
编译异常在编译期间一定要处理,否则代码不能编译通过。
那么我们有时候写出的会标红,这叫做语法错误。
throw和throws关键字:
throw:
如何让程序抛出异常?Java中抛出异常通常是使用throw和throws关键字来实现的。
throw是将产生的异常抛出,是一个抛出异常的动作(一般是指定的异常)。
int a = 10;
if (a == 10) {throw new NullPointerException("hahaha");
}
通过throw来手动抛出异常。 我们之前的算数等等异常是JVM抛出的,因为我们没有处理。
throw关键字一般抛出我们自定义的异常。
throws:
throws一般使用在方法之后。
告诉方法调用者,调用这个方法可能会抛出这个异常。也就是说,如果在方法中有这个编译时异常(受查异常),那么这个编译时异常一定要进行处理。目前我们的处理方式是在方法定义的时候,通过throws关键字声明异常。最后这个异常是交给JVM处理的。
throws必须跟在方法的参数列表之后,声明异常必须是Exception 或者Exception 的子类。
如果方法内存抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开;如果抛出的异常类型有父子关系,直接声明父类即可。
//public void OpenConfig(String filename) throws IOException, FileNotFoundException{
//FileNotFoundException 继承于 IOException
public void OpenConfig(String filename) throws IOException{}//这两种方式是一样的
此时我们再来看一个代码:
public static void func() throws CloneNotSupportedException{int a = 10;if (a == 10) {throw new CloneNotSupportedException("hahaha");}
}public static void main(String[] args) throws CloneNotSupportedException {func();//因为 func 方法的声明里面有编译异常处理,所以主方法也要写
}
但此时我们确实抛出异常了,但是程序还是崩溃了,那么异常实际上没有被程序员处理,实际上还是交给了JVM处理。
try-catch关键字:
程序员要解决这些异常,就需要使用这些语句:
public static void func() throws CloneNotSupportedException{int a = 10;if (a == 10) {throw new CloneNotSupportedException("hahaha");}
}public static void main(String[] args) throws CloneNotSupportedException {try {func();} catch (CloneNotSupportedException e) {throw new RuntimeException(e);}
}
try语句中,可能会有异常发生,我们写这些语句是,是可以预测会发生那些异常的(身为程序员,我不信你看不出来),那么catch语句中,就是捕获这些可能发生的异常。 如果捕获到了就执行catch当中的内容。
如果我们预测失败,就是catch中没有捕获到发生的异常,还是会交给JVM处理,程序崩溃。
我们可以通过catch捕获多个异常(就是多写几个catch语句):
try {System.out.println(10/0);
} catch (ArithmeticException e) {System.out.println("我来处理 ArithmeticException 了");
} catch (NullPointerException e) {System.out.println("我来处理 NullPointerException 了");
}
这里处理的异常都是在try语句中的。我们也可以通过 | 连接多个异常,充当“或”。
/*try {System.out.println(10/0);
} catch (ArithmeticException e) {System.out.println("我来处理 ArithmeticException 了");
} catch (NullPointerException e) {System.out.println("我来处理 NullPointerException 了");
}*///以下方式等同上面,但是少用
try {System.out.println(10/0);
} catch (ArithmeticException | NullPointerException e) {System.out.println("我来处理 ArithmeticException 了");
}
也就是说,在try里面发生的异常,就不会执行里面的代码了。
虽然可以捕获多种类型异常,但是同一时刻只能抛出一个异常。
我们不能先捕获所有父类异常之后捕获子类异常,否则出错。
但是可以先捕获子类异常,之后捕获父类异常,此时就充当了垫后的角色。
还有就是,那几个运行异常的类型最好记住,如果我们使用了try-catch语句,就一定要知道可能会出现哪些异常。当然,我们知道所有异常都继承Exception类,刚才说可以使用父类来指代。但是最好不要使用父类,否则这样就失去了意义。
finally:
你猜它为啥叫finally?我们先看代码:
public static int func() {try {int[] array = null;System.out.println(array.length);} catch (NullPointerException e) {System.out.println("捕获到了一个空指针异常");} finally {System.out.println("这里执行了finally");}return 10;
}
public static void main(String[] args) {System.out.println(func());
}
可以看到, finally最后无论如何都会被执行。
这里面会有很多抗,也就是说我们必须知道执行顺序,比如以下代码结果:
因为try里面直接返回了,但是finally还是被执行了。那么既然如此,我们就不能乱返回了: 因为可能不止一条语句执行,所以返回值不能有多个。
执行顺序:try中的语句正常,就先执行finally,之后执行try;try中的语句有异常,就先执行catch里面捕获到的异常语句,之后执行finally。
所以try里面没有异常,要先执行finally:
public static int func1() {try {return 10;} finally {return 100;}
}
public static void main(String[] args) {System.out.println(func1());
}
这个结果为100,此时直接执行finally,并不在执行try里面的代码。
我们先来执行一个异常代码:
此时func2里面没有处理这个异常,main方法中处理了这个异常,那么这个func2方法就最好声明一下,让使用者知道会有异常并处理。
public static void func2() throws ArrayIndexOutOfBoundsException{//声明会有这个异常让使用者处理int[] arr = {1,2,3};System.out.println(arr[100]);
}
public static void main(String[] args) {//此时因为是 main 方法调用,所以要在 main 方法中处理//使用 try-catchtry {func2();} catch (ArrayIndexOutOfBoundsException e) {} finally {}}
所以调用的方法中也没有处理,最终就会交给JVM来处理。
为啥叫受查异常?
为了更好的理解,我们来举一个例子,我们来自定义异常,因为异常本来就是类,所以我们利用继承来自己定义异常。
我们可以看到,像ArrayIndexOutOfBoundsException这个异常是继承于IndexOutOfBoundsException的,所以我们自己写异常时,就需要用到继承于Exception中的类来写。
此时我们定义一个LogIn类,并在里面对比用户名和密码,如果对照错误,则抛出自己定义的异常。
public class LogIn {private String userName = "admin";private String password = "123455";private static void loginInfo (String userName, String password) {if (!userName.equals(userName)) {//此时用户名输入错误,就需要报异常//此时抛出自定义异常throw new UserNameException("用户名有问题");}if (!password.equals((password))) {//此时密码错误throw new PasswordException("密码有问题");}System.out.println("登陆成功");}public static void main(String[] args) {loginInfo("admin","123456");}
}//extends Exception 编译时异常
//extends RuntinmeException 运行时异常class UserNameException extends Exception {//自定义异常一定要继承于异常//此时为编译时异常public UserNameException (String message) {super(message);}
}class PasswordException extends Exception {public PasswordException (String message) {super(message);}
}
此时报红是因为这是一个受查异常,所以需要通过try-catch包起来。但是我们之前讲过,可以通过调用它的函数try-catch,有异常的函数只需要通过声明可能有异常即可。
public static void main(String[] args) {//此时通过throws已经声明了异常//调用方法里面需要通过 try-catch 来解决try {loginInfo("admin","123456");} catch (UserNameException e) {} catch (PasswordException e) {} finally {}}
private static void loginInfo (String userName, String password) throws UserNameException,PasswordException{if (!userName.equals(userName)) {//此时用户名输入错误,就需要报异常//此时抛出自定义异常throw new UserNameException("用户名有问题");}if (!password.equals((password))) {//此时密码错误throw new PasswordException("密码有问题");}System.out.println("登陆成功");
}
因为这是受查异常,因为我们继承了编译异常的类,所以必须解决。所以运行异常(非受查异常)就不用解决了。
注:因为空指针异常,算数异常不是编译异常,所以你在主方法后面不加上throws也可以通过,由JVM来帮你处理。
我们见过一个最典型的受查异常(编译异常)就是克隆方法了,我们当时主方法调用克隆方法,这个主方法就必须使用throws关键字,否则无法完成编译。
throw和throws的区别:
1、throws出现在方法函数头;而throw出现在函数体。
2、throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。
3、两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。
总结:
多去刷题,多去使用,就会领悟。