什么是异常?
Java异常体系
异常的用法
异常的处理流程
抛出异常
自定义异常
什么是异常?
我们在之前的学习中已经接触过异常,例如:
数组下标访问越界时:
public class Test2 {
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
System.out.println(array[5]);
}
}
//结果:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
将0作为除数时:
public class Test2 {
public static void main(String[] args) {
int i = 0;
int j = 1;
System.out.println(j/i);
}
}
//结果:
Exception in thread "main" java.lang.ArithmeticException: / by zero
上述结果都发生了异常,那么,到底什么才算异常呢?
异常指的是在程序运行时出现错误并通知调用者的一种机制。
那么,有哪些错误属于异常呢?我们可以通过java异常体系来了解。
Java异常体系
上图是Java异常的体系结构,Throwable类是Java中所有异常类的父类,它派生出了Exception类和Error类
Error类指的是Java运行时的内部错误和资源耗尽错误,应用程序不抛出此类异常,这种内部错误一旦出现,就只能告知用户并终止程序,但这种内部错误很少出现。
Exception类是程序员使用的异常类的父类,Exception类派生出了一个RuntimeException类,这里面抛出了我们常见的异常类,如NullPointerException,IndexOutOfBoundsException等。
Java中所有继承于Error或RuntimeException类的异常称为非受查异常,非首查异常又叫运行时异常,其他异常称为受查异常,即图片中浅蓝色部分为非受查异常,其余异常均为受查异常。
受查异常可以理解为编译时发生的异常,当出现受查异常时,编译器会划红线,此时无法编译通过。
而非受查异常是在运行时才发生的异常,这种异常在程序编译时无法被检测出来,例如数组下标越界异常等。
异常的用法
我们对异常有两种处理方式,一种是让JVM自动处理:
public class Test2 {
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
System.out.println(array[5]);
}
}
//Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5
当JVM自动处理异常时,程序会直接结束,并不会执行后面的步骤,但有时我们希望程序出现异常后能正常执行,这时就需要我们使用try—catch语句来处理异常:
public class Test1 {
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
int i = 100;
System.out.println(func(array,i));
}
public static int func(int[] array,int i){
try { //try{ }中存放可能发生异常的语句
System.out.println(array[i]);
return array[i];
}
//catch用来捕获异常
catch (ArrayIndexOutOfBoundsException e){ //( )内存放可能发生异常的类型
//固定用法,用于打印出现异常的数据栈
e.printStackTrace();
System.out.println("程序发生异常了");
return -1;
}
}
//结果:
java.lang.ArrayIndexOutOfBoundsException: 100
at Test.Test1.func(Test1.java:20)
at Test.Test1.main(Test1.java:16)
程序发生异常了
-1
我们可以看出,当我们使用try—catch语句处理异常时,当程序发生异常后并没有直接结束,而是继续执行了后面的语句。
当我们使用try—catch语句时,try{ }中存放可能发生异常的语句,catch后面的( )内存放可能产生异常的类型,例如上述代码中,try语句中可能会产生数组下标越界异常,catch后的( )存放数组下标异常类,catch方法内部存放发生异常时要执行的操作,e.printStackTrace()用于打印出现的异常。
但是, catch()中填写了错误的异常类型时, 就无法捕获到正确的异常, 这时程序同样会直接结束:
public class Test1 {
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
int i = 100;
System.out.println(func(array,i));
}
public static int func(int[] array,int i){
try { //try{ }中存放可能发生异常的语句
System.out.println(array[i]);
return array[i];
}
//catch用来捕获异常
catch (NullPointerException e){ //将这里更改为空指针异常
e.printStackTrace();
System.out.println("程序发生异常了");
return -1;
}
}
//结果:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
at Test.Test1.func(Test1.java:20)
at Test.Test1.main(Test1.java:16)
可以发现,我们使用了try-catch语句来捕获异常, 但catch并没有捕获到对应的异常,这次异常是由编译器捕获的,这是因为catch( )中的异常类型与try{ }中产生的异常类型不匹配。
我们可以同时进行多次catch():
public class Test1 {
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
int i = 100;
System.out.println(func(array,i));
}
public static int func(int[] array,int i){
try {
System.out.println(array[i]);
return array[i];
}
catch (NullPointerException e){
e.printStackTrace();
System.out.println("程序发生空指针异常");
return -1;
}
catch (ArrayIndexOutOfBoundsException e){
e.printStackTrace();
System.out.println("程序发生数组下标越界异常");
return -1;
}
}
}
//结果:
java.lang.ArrayIndexOutOfBoundsException: 100
at Test.Test1.func(Test1.java:20)
at Test.Test1.main(Test1.java:16)
程序发生数组下标越界异常
-1
可以看到,这次catch( )成功捕获了异常,第一次catch( )没有捕获到异常, 这时程序会继续执行第二次catch( ),直到所有catch( )全部完成, 如果所有的catch全部执行完之后依然没有捕获到对应的异常, 这时程序也会立即终止.
如果本方法中没有合适的处理异常的方式, 就会沿着调用栈向上传递:
public class Test1 {
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
int i = 100;
try {
System.out.println(func(array, i));
}
catch(ArrayIndexOutOfBoundsException e){
e.printStackTrace();
System.out.println("程序发生数组下标越界异常");
}
}
public static int func(int[] array,int i){
System.out.println(array[i]);
return array[i];
}
}
//结果:
java.lang.ArrayIndexOutOfBoundsException: 100
at Test.Test1.func(Test1.java:25)
at Test.Test1.main(Test1.java:17)
程序发生数组下标越界异常
我们通常用finally来进行最后的工作, 如释放资源等:
public class Test1 {
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
int i = 100;
System.out.println(func(array, i));
}
public static int func(int[] array,int i){
try {
System.out.println(array[i]);
return array[i];
}
catch(ArrayIndexOutOfBoundsException e){
e.printStackTrace();
System.out.println("程序发生数组下标越界异常");
return -1;
}
finally {
System.out.println("finally");
}
}
}
在上述例子中,如果没有finally,catch结束后会直接return -1,但有了finally后,catch会先执行finally,再返回。
值得注意的是,我们不应该将返回值放在finally中,如果finally中有返回值,那么就一定会返回finally中的return,而不会返回try和catch中的返回值。原因就是在执行try和catch的返回值之前一定会执行finally,但finally中存在返回值,那么就会直接返回finally中的return。
异常的处理流程
- 程序先执行 try 中的代码
- 如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配.
- 如果找到匹配的异常类型, 就会执行 catch 中的代码
- 如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者.
- 无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行).
- 如果上层调用者也没有对应的catch, 就继续向上传递.
- 一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止.
抛出异常
除了Java内置的类会抛出异常之外,我们也可以通过throw关键字手动抛出某个异常:
public class Test {
public static void main(String[] args) {
int i = 0;
int j = 1;
if(i==0){
throw new IndexOutOfBoundsException("你的除数是0");
}
}
}
//结果:
Exception in thread "main" java.lang.IndexOutOfBoundsException: 你的除数是0
at Test.Test.main(Test.java:15)
在这个代码中, 我们可以根据实际情况来抛出需要的异常. 在构造异常对象同时可以指定一些描述性信息.
自定义异常
Java 中虽然已经内置了丰富的异常类, 但是我们实际场景中可能还有一些情况需要我们对异常类进行扩展, 创建符合我们实际情况的异常.
例如, 我们实现一个用户登陆功能:
class UserNameError extends Exception{
public UserNameError(String message) {
super(message);
}
}
class PasswdError extends Exception{
public PasswdError(String message) {
super(message);
}
}
public class Test2 {
static String userName = "1999";
static String passwd = "2000";
public static void main(String[] args) {
Test2 test2 = new Test2();
try {
test2.login("123","2000");
}
catch (UserNameError userNameError){
userNameError.printStackTrace();
}
catch (PasswdError passwdError){
passwdError.printStackTrace();
}
}
public void login(String username,String passwd)throws UserNameError,PasswdError{
if(!username.equals(Test2.passwd)){
throw new UserNameError("用户名错误");
}
if(!passwd.equals(Test2.passwd)){
throw new PasswdError("密码错误");
}
System.out.println("登录成功!");
}
}
自定义异常通常会继承自 Exception 或者 RuntimeException
继承自 Exception 的异常默认是受查异常
继承自 RuntimeException 的异常默认是非受查异常
The end