JAVA学习*异常
什么是异常
在 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{
@Override
protected 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;
@Override
protected 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、对于自定义异常可以重写构造方法。