理解什么是异常
异常就是我们平时编译程序或者运行程序时出现的错误,那么我们为什么要处理异常呢?因为出现错误让我们无法让代码跑起来或者达不到我们编程的最终目的!
在程序清单1中,如果不处理异常,那么这个异常就会交给 JVM 处理,程序会立马终止。也就是说,如果某一行出现了异常,那么从这一行开始往下,所有程序都会终止。例如:下面程序中的 " world " 就没有打印出来。
程序清单1:
public class Test1 { public static void main(String[] args) { int[] arr = {1,2,3,4,5}; System.out.println("hello"); System.out.println(arr[10]); System.out.println("world"); } }
输出结果:
上面代码中抛出的异常表示数组越界,而 ArrayIndexOutOfBoundsException 其实在 Java 的源代码中就是一个类。
一、Java 的异常体系
顶层类 Throwable 派生出两个重要的子类,Error 和 Exception
① 其中 Error 指的是 Java 运行时内部错误和资源耗尽错误。应用程序不抛出此类异常。这种内部错误一旦出现,除了告知用户并使程序终止之外。再无能无力。这种情况很少出现。
② Exception 是我们程序员所使用的异常类的父类。
其中 Exception 有一个子类称为 RuntimeException,这里面又派生出很多我们常见的异常类,例如:【 NullPointerException , IndexOutOfBoundsException 】
③ Java 语言规范将派生于 Error 类或 RuntimeException 类的所有异常称为非受查异常(运行时异常),所有的其他异常称为受查异常(编译时异常)。
④ 如果一段代码可能抛出受查异常,那么必须显式进行处理。通俗的来说,因为此时受查异常代表的就是编译时异常,所以可想而知,当我们编译都通过不了的时候,自然是要处理问题的。
显示进行处理,有两种方式:
- 使用 try & catch 包裹起来
或者 - throws 抛出,即在方法上加上异常声明
IDEA 编译器为我们提供了快捷键 【Alt + Enter】,两种方式可以任选其一。
⑤ 如果是非受查异常,即运行时异常。
我们也可以采取刚刚上面提到的显示进行处理的两种方式,当然,如果我们不采取以上两种方式,也不会影响我们编译成功,因为我们如果不进行异常处理,此时非受查异常会交给我们的 JVM 处理。
二、捕获异常的基本语法
try{ 有可能出现异常的语句 }catch (异常类型 异常对象) { 处理对应 try 代码块中出现的所有异常 } finally { 异常的出口 }
- try 代码块中放的是可能出现异常的代码
- catch 代码块中放的是出现异常后的处理行为
- finally 代码块中的代码用于处理善后工作,会在最后执行
- 其中 catch 和 finally 都可以根据情况选择加或者不加
三、异常的处理流程
① 程序先执行 try 中的代码
② 如果 try 中的代码出现异常,就会结束 try 中的代码,看和 catch 中的异常类型是否匹配,如果找到匹配的异常类型,就会执行 catch 中的代码,如果没有找到匹配的异常类型,就会将异常向上传递到上层调用者。
③ 如果上层调用者也处理不了异常,就继续向上传递,一直到 main 方法,此时如果 main 方法 也没有合适的代码处理异常,就会交给 JVM 来进行处理,此时程序就会异常终止,那么出现错误行的代码及其以下行代码都被不会执行。
⑥ 无论是否找到匹配的异常类型,finally 中的代码都会被执行(在该方法结束之前执行)
程序清单2:
public class Test2 { public static void main(String[] args) { int[] arr = {1,2,3,4,5}; try{ System.out.println(arr[10]); }catch (ArrayIndexOutOfBoundsException e){ e.printStackTrace(); System.out.println("我捕捉到了一个异常"); } System.out.println("hello world"); } }
输出结果:
程序清单3:(错误的代码)
public class Test3 { public static void main(String[] args) { int[] arr = {1,2,3,4,5}; try{ System.out.println(arr[10]); }catch (IndexOutOfBoundsException e){ e.printStackTrace(); System.out.println("我捕捉到了一个异常"); } catch (ArrayIndexOutOfBoundsException e){ e.printStackTrace(); System.out.println("我捕捉到了一个异常"); } System.out.println("hello world"); } }
ArrayIndexOutOfBoundsException 类 继承 IndexOutOfBoundsException类,那么子类应写在父类的前面。
程序清单4:
class Person implements Cloneable{ public int age = 10; @Override public String toString() { return "Person{" + "age=" + age + '}'; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } public class Test4 { public static void main(String[] args) { Person person1 = new Person(); try{ Person person2 = (Person) person1.clone(); System.out.println(person2); }catch (CloneNotSupportedException e){ e.printStackTrace(); } } }
输出结果:
在我们学习接口的时候,有一个克隆接口,我们可以选择将异常交给JVM处理,当然我们也可以用 try & catch 处理异常。
程序清单5:
public class Test5 { public static void main(String[] args) { System.out.println(func()); } public static int func() { try { return 10; } catch (ArithmeticException e){ e.printStackTrace(); } finally{ return 20; } } }
输出结果:
由于 finally 不管在抛出异常或未抛出异常的情况下,其花括号内的数据都会被系统执行,那么上述代码会输出 20.所以我们应避免在finally 中使用 return 语句。
四、抛出异常
除了 Java 内置的类会抛出一些异常之外,程序员也可以手动抛出某个异常。使用 throw 关键字完成这个操作。
我们在处理异常的时候,通常希望知道这段代码中究竟会出现哪些可能的异常。
我们可以使用 throws 关键字,把可能抛出的异常显式的标注在方法定义的位置。从而提醒调用者要注意捕获这些异常。
程序清单6:
public class Test6 { public static int divide(int x, int y) throws ArithmeticException { if (y == 0) { throw new ArithmeticException("抛出除 0 异常"); } return x / y; } public static void main(String[] args) { System.out.println(divide(10, 0)); } }
输出结果:
五、自定义异常
在说明自定义异常之前,在程序清单7中,我们先来模拟一下简易的QQ登录操作,在登录时的逻辑我们要输入用户名和密码。
程序清单7:
public class Test7 { public static String name = "Jack"; public static String password = "123456"; public static void login(String name, String password){ if(!Test7.name.equals(name)){ System.out.println("QQ用户名输入错误"); } if(!Test7.password.equals(password)){ System.out.println("QQ密码输入错误"); } System.out.println("登录成功"); } public static void main(String[] args) { login("liming","654321"); } }
输出结果:
程序清单8是对程序清单7中的模拟异常。
程序清单8:
class NameException extends RuntimeException{ public NameException(String message){ super(message); } } class PasswordException extends RuntimeException{ public PasswordException(String message){ super(message); } } public class Test8 { public static String name = "Jack"; public static String password = "123456"; public static void login(String name, String password)throws NameException, PasswordException{ if(!Test8.name.equals(name)){ throw new NameException("QQ用户名输入错误"); } if(!Test8.password.equals(password)){ throw new PasswordException("QQ密码输入错误"); } System.out.println("登录成功"); } public static void main(String[] args) { try{ login("Jim","654321"); }catch (NameException e){ e.printStackTrace(); System.out.println("QQ用户名输入错误"); }catch (PasswordException e){ e.printStackTrace(); System.out.println("QQ密码输入错误"); } } }
输出结果:
可以发现当我们用户名输入错误的时候,系统就已经抛出异常了,那么此时还未执行到判断密码异常。