什么是异常
在 Java 里,异常是指程序运行期间出现的不正常状况,它会中断程序的正常执行流程。
异常的分类
Java 中的异常是对象,这些对象都继承自 Throwable类。Throwable类有两个主要的子类:Error 和 Exception。
Error类表示严重的系统级错误。
Exception类代表程序中可以被捕获和处理的异常情况。Exception类又分为两类:受查异常(编译时异常)和运行时异常。
异常的处理
对于异常的处理有5个关键字:try
catch
finally
throw
throws
。
throw关键字
用于在方法内部抛出异常。
throw new XXXXException("产生异常的原因");
代码展示:
public static void func(int a) throws ArithmeticException{if(a == 0) {throw new ArithmeticException("除数为零,算数异常");}int b = 40 / a;System.out.println(b);int[] array = null;if(array == null) {throw new NullPointerException("空指针异常");}System.out.println(array[1]);
}
public static void main(String[] args) {int a = 0;func(a);
}
代码解释:
1、为什么要抛出异常?是为了将错误信息告知程序员。
2、throw用于抛出异常,必须在方法内部使用。
3、用throw只是单纯抛出问题,并没有解决异常。对于抛出的是RunTimeException 或者 RunTimeException 的子类是,则可以不用处理,直接交给JVM处理。
4、异常一旦抛出后面的代码将不再执行。像上述代码就不会输出b的值,也不会爆出空指针异常。
当我们将a == 2时,输出b的值,爆出空指针异常。**也就是说同一时刻只能抛出一个异常!**不会同时抛出多个异常。
5、对于抛出编译时异常,需要用throws在方法声明中声明可能抛出的异常。
throws关键字
用于在方法声明中声明可能抛出的异常。
修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2...{
}
代码展示:
1、对于刚才的代码,我们也可以声明可能发生的异常。
public static void func(int a) throws ArithmeticException,NullPointerException{if(a == 0) {throw new ArithmeticException("除数为零,算数异常");}int b = 40 / a;System.out.println(b);int[] array = null;if(array == null) {throw new NullPointerException("空指针异常");}System.out.println(array[1]);
}
public static void main(String[] args) {int a = 2;func(a);
}
2、在学习Cloneable接口时,有出现throws关键字。
class Student implements Cloneable{@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}
public class Test2 {public static void main(String[] args) throws CloneNotSupportedException{Student student = new Student();Student student1 = (Student) student.clone();}
}
代码解释:
1、throws
告诉调用者这个方法可能存会抛出异常,调用者需要处理这个异常。使用throws
实际上是将处理代码的责任转移给了调用改方法的代码。
像上述代码:重写的clone()方法声明了CloneNotSupportedException异常。当main方法调用了clone()方法,需要去处理这个异常。但是,main方法也声明了CloneNotSupportedException异常,此时将由JVM去处理。
2、throws必须跟在方法的参数列表之后。
3、如果抛出的多个异常存在父子关系,可以声明父类。
try和catch关键字
try是用于包含可能会抛出异常的代码块。
catch是用于捕获并处理try块中抛出的异常。
try {可能会抛出异常的代码块
} catch (异常1 e) {throw new RuntimeException(e);
} catch (异常2 e) {throw new RuntimeException(e);
}
代码展示:
代码案例1:
public static void func(int a) {int b = 40 / a;System.out.println(b);int[] array = null;System.out.println(array[1]);
}
public static void main(String[] args) {int a = 0;try {func(a);System.out.println("try内部的代码被执行了");} catch (ArithmeticException e) {e.printStackTrace();System.out.println("捕捉到算数异常");} catch (NullPointerException e) {e.printStackTrace();System.out.println("捕捉到空指针异常");}System.out.println("try-catch后面的代码被执行了");
}
输出:
代码案例2:
public static void func(int a) {int b = 40 / a;System.out.println(b);int[] array = null;System.out.println(array[1]);
}
public static void main(String[] args) {int a = 0;try {func(a);System.out.println("try内部的代码被执行了");} catch (IndexOutOfBoundsException e) {e.printStackTrace();System.out.println("捕捉到下标越界异常");} catch (NullPointerException e) {e.printStackTrace();System.out.println("捕捉到空指针异常");}System.out.println("try-catch后面的代码被执行了");
}
输出:
代码案例3:
class Student implements Cloneable {public int age;public String name;@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}
public class Test {public static void main(String[] args) throws CloneNotSupportedException{Student student = new Student();Student student1 = (Student) student.clone();}
}
public class Test {public static void main(String[] args) {try {test1();} catch (CloneNotSupportedException e) {e.printStackTrace();}}public static void test1() throws CloneNotSupportedException{throw new CloneNotSupportedException();}
}
public class Test {public static void main(String[] args) {test1();}public static void test1() {try {throw new CloneNotSupportedException();} catch (CloneNotSupportedException e) {e.printStackTrace();}}
}
代码解释:
1、 try内不是一定要有异常的。
2、 在代码案例1中:我们发现func(a);
后面的代码没有被执行。说明在捕获到了异常后,try包裹的后面的代码就不会被执行(这是为了考虑到安全性)。会执行捕获到的catch中的代码(案例中也就是e.printStackTrace();System.out.println("捕捉到算数异常");
)。当捕获到异常后,try-catch后面的代码仍会被执行。
3、 在代码案例1中:我们发现当存在多个异常时,只会捕捉一个异常,这个异常是,try中最先发生的异常在catch中匹配成功的第一个异常(注意:并不是第一catch的异常)。(当然,前提是抛出的异常类型和catch中的异常匹配的时候)
4、 在代码案例2中:我们发现当抛出的异常类型和catch中的异常不匹配时,抛出的异常不会被处理,此时就会交给JVM处理,JVM处理就会终止程序,输出异常信息,将不会再执行后面语句了。
5、 在代码案例3中:对于编译时异常我们可以将不断抛出,最终交给JVM处理;
就是第一个代码,我们再学习接口的时候就有见过。由于重写的clone方法声明了CloneNotSupportedException异常,此时我们需要去对这个异常进行处理,当时我们选择的是对异常进行抛出给调用者(main方法),main方法继续抛出给JVM处理。
也可以自己使用try-catch进行处理。
第二个和第三个代码都使用了try-catch来处理异常,只是在处理异常的地方不一样,本质上是一样的。
6、 由于异常之间是存在继承关系的,在根据第3点知识,我们可以知道catch中的第一个异常不能是Exception!因为Exception是所有异常的父类。catch(Exception e){}
虽然不能放在第一个,但是可以放在最后一个,进行一个兜底的作用。防止有些异常没有被我们捕捉到。也是就是如果异常之间具有父子关系,一定是子类异常在前catch,父类异常在后catch,否则语法错误!
7、 当要捕捉的异常较多的时候,可以catch合并起来写。
catch(ArithmeticException | NullPointerException e) {}
但不推荐这么写,会造成分不清具体发生的是什么异常。当然也不允许只写个Exception。
finally关键字
finally关键字通常和try-catch语句搭配使用。
作用:
finally块常用于确保资源(如文件、网络连接、数据库连接等)被正确关闭,避免资源泄漏。
代码案例:
代码案例1:
public class Test2 {public static void main(String[] args) {try {int result = 10 / 0; // 这里会抛出ArithmeticException异常System.out.println("结果: " + result);} catch (ArithmeticException e) {e.printStackTrace();System.out.println("捕获到算数异常");} finally {System.out.println("finally块中的代码一定会被执行");}}
}
代码案例2:
import java.util.InputMismatchException;
import java.util.Scanner;public class TestFinally {public static int getNum() {Scanner scanner = null;try {scanner = new Scanner(System.in);int data = scanner.nextInt();return data;} catch (InputMismatchException e) {e.printStackTrace();} finally {System.out.println("finally内代码被执行了");}System.out.println("finally后面代码被执行了");if(scanner != null) {scanner.close();}return 0;}public static void main(String[] args) {int data = getNum();System.out.println(data);}
}
在 Java 里,InputMismatchException是RuntimeException的子类。当程序借助Scanner类从输入源(像键盘、文件等)读取数据,而输入的数据类型和程序所期望的数据类型不相符时,就会抛出此异常。
当输入数字时:
当输入的不是数字时:
代码案例3:
public class Test3 {public static void main(String[] args) {int test = test();System.out.println(test);}public static int test() {int a = 0;try {int b = 40/a;return 1;} catch (ArithmeticException e) {return 0;} finally {return -1;}}
}
输出:-1
代码解释:
1、 在代码案例1中:无论try块里的代码是否抛出异常,finally块中的代码都会被执行。
2、 在代码案例2中:
当输入数字时,并没有产生异常,所以遇到了try中的return,返回输入的数字。但代码并没有走关闭Scanner的代码,导致资源泄露。(但finally中的代码一定会被执行。)
当输入非数字时,产生异常,此时异常被捕获,try中的return语句就不会被执行。走完catch和finally后,会进入if语句,返回0。
3、 在代码案例3中:当try和finally中都有return语句,此时一定会执行finally中的return语句。(一定要记住:finally中的代码是一定会被执行的!)
4、 对于快速使用try-catch-finally的快捷键:选中代码块,ctrl + Alt + t
异常处理流程总结
1、 对于运行时异常,可以使用try-catch-finally;也可以使用if语句进行判断,其中使用throw手动抛出异常。对于使用throws来说,只是起到了一个声明存在可能发生的异常,可以用可以不用。
2、 对于编译时异常,可以使用try-catch-finally;也可以使用throws交给调用者处理,最终会交给JVM处理。
1、程序执行try中的代码
2、当try中没有发生异常,程序就会正常执行;当发生异常,就会看和catch中的异常是否匹配。
3、匹配成功,执行当中catch的语句;否则交给调用者处理,当调用者始终没有真正处理,就会交给JVM处理,此时程序就会终止。
4、finally中的代码始终被执行!
自定义异常
代码案例:
public class LogIn {private String username = "admin";private String password = "123";public LogIn(String username, String passeword) {this.username = username;this.password = passeword;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public LogIn() {}public void login() throws PasswordException {if (!this.username.equals("admin")) {throw new UsernameException("用户名错误或不存在");}if (!this.password.equals("123")) {throw new PasswordException("密码错误");}System.out.println("登录成功!");}
}
public class UsernameException extends RuntimeException{public UsernameException(String message) {super(message);}public UsernameException() {super();}
}
public class PasswordException extends Exception{public PasswordException() {super();}public PasswordException(String message) {super(message);}
}
import java.util.Scanner;public class CustomExceptionTest {public static void main(String[] args) {Scanner scanner = null;int count = 3;scanner = new Scanner(System.in);do {LogIn logIn = new LogIn();System.out.println("请输入用户名:");logIn.setUsername(scanner.nextLine());System.out.println("请输入密码:");logIn.setPassword(scanner.nextLine());try {logIn.login();} catch (UsernameException e) {e.printStackTrace();} catch (PasswordException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();} finally {if(scanner != null && count == 0) {scanner.close();}count--;if(count != 0) {System.out.println("你还有" + count + "次机会!");}else if(count == 0) {System.out.println("请1分钟后在试");}}} while (count != 0);}
}
代码解释:
1、对于自定义异常,需要继承异常类。一般是Exception或者RuntimeException。
2、继承Exception默认被定义为受查异常。继承RuntimeException默认被定义为运行时异常。
3、对于自定义异常可以重写构造方法。