目录
? ? ? ? 应该都听说过Java中的异常处理,其实不止Java中有异常处理,我们学过的其他语言中的报错是不是也是异常呢?对,是,但是Java中的异常为什么要学习?因为它可以当程序真的报错而崩溃时,我们进行异常处理可以使其跳过,程序还能正常的往下执行。
? ? ? ? 像C语言,如果程序报错就会崩溃,而Java可以得知这个异常,可以继续正常运行。
? ? ? ? 异常就是异于常态,和正常情况不一样,有错误。在Java中,阻止当前方法或作用域的情况,称之为异常。
? ? ? ? Java中所有异常类都继承于Throwable类,对,你没听错,这些异常都是类。
????????Java中,所有异常都有一个公共祖先Throwable(可抛出)。Throwable指定代码中可用传播机制通过Java应用程序传输的任何问题的共性。
????????Throwable主要包括两个大类,一个是Error类,另一个是Exception类。
????????注意,异常和错误是两种东西。不是同一概念。
????????其中Error类中包括虚拟机错误和线程死锁,一旦Error出现了,程序就彻底的挂了,被称为程序终结者。
????????也就是通常所说的“异常”。主要指编码、环境、用户操作输入出现问题,Exception主要包括两大类,非检查异常(RuntimeException)和检查异常(其他的一些异常)。?
? ? ? ? 因为Error类出现,程序就会挂掉,所以讲的意义不大。这一篇我们就来重点了解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;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public 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接口,但是还是不能运行,必须抛出异常才可以运行。其实这里就发生了编译异常。
?????????编译异常在编译期间一定要处理,否则代码不能编译通过。
????????那么我们有时候写出的会标红,这叫做语法错误。
?????????如何让程序抛出异常?Java中抛出异常通常是使用throw和throws关键字来实现的。
? ? ? ? throw是将产生的异常抛出,是一个抛出异常的动作(一般是指定的异常)。
int a = 10;
if (a == 10) {
throw new NullPointerException("hahaha");
}
? ? ? ? 通过throw来手动抛出异常。 我们之前的算数等等异常是JVM抛出的,因为我们没有处理。
????????throw关键字一般抛出我们自定义的异常。?
? ? ? ? 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处理。
? ? ? ? 程序员要解决这些异常,就需要使用这些语句:
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?我们先看代码:
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-catch
try {
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关键字,否则无法完成编译。
????????1、throws出现在方法函数头;而throw出现在函数体。
? ? ? ? 2、throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。
????????3、两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。
? ? ? ? 多去刷题,多去使用,就会领悟。