异常
异常指的就是程序编译或者运行的各种错误状况,如:找不到文件,把0做除数等.
Java中的错误事件分为两种:Error
(错误)和Exception
(异常),它们都继承自Throwable
.
Java异常继承体系结构图如下:
1. 异常的分类
1.1 Error
Error通常是指虚拟机无法解决的严重问题,如 OutOfMemoryError
(内存溢出错误)
public static void main(String[] args) {
byte[] b1 = new byte[1024 * 1024 * 1024];
byte[] b2 = new byte[1024 * 1024 * 1024];
byte[] b3 = new byte[1024 * 1024 * 1024];
byte[] b4 = new byte[1024 * 1024 * 1024];
}
如上所示,申请空间过大造成系统内存溢出,产生了OutOfMemoryError
Error是严重的错误,会造成系统的崩溃
1.2 Exception
Exception 指的是程序本身可以进行处理或者捕获的异常.
Exception可以分为两种:编译时异常(又称检查异常)和 RuntimeException
1.2.1 RuntimeException
运行时异常是程序运行时出现的异常,RuntimeException
是所有运行时异常的父类,如NullPointerException
(空指针异常)和IndexOutOfBoundsException
(下标越界异常)等.
这种异常属于非检查性异常,程序可以选择捕获,也可以不做处理,一般由程序本身的逻辑错误引起
1.2.2 编译异常(Checked Exception)
是除RuntimeException
以外的异常,这种异常必须显式地抛出或捕获,否则将不能编译通过.
如:IOException
,SQLException
等
1.2.3异常的常用方法
Throwable
中提供了很多关于异常的方法
1.public String getMessage()
//返回关于发生的异常的详细信息。这个消息在Throwable 类的构造函数中初始化了。
2.public Throwable getCause()
//返回一个 Throwable 对象代表异常原因。
3.public String toString()
//返回此 Throwable 的简短描述。
4.public void printStackTrace()
//在控制台用标准错误流打印异常信息在程序中出错的位置及原因。
public class Exception04 {
public static void main(String[] args) {
test01(0);
}
public static void test01(int num) {
try {
int a = 10 / num;
} catch (ArithmeticException e) {
System.out.println("e.getMessage(): "+e.getMessage());
System.out.println("e.getCause(): "+e.getCause());
System.out.println("e.toString(): "+e.toString());
System.out.print("e.printStackTrace(): ");
e.printStackTrace();
}
}
}
2.异常的捕获
我们发现,如果发生异常时,如果不对异常进行任何处理,那么程序就会崩溃,而且出现异常后方的代码并不会被执行,这显然不是我们所希望的,这个时候就可以使用try catch去捕获异常
public static void main(String[] args) {
System.out.println("程序开始执行");
int a = 10 / 0;
System.out.println("程序结束执行");//不会被执行
}
2.1 try catch
try catch关键字的含义:
- try:用于监听try代码块中可能产生异常的代码,当产生异常时,异常就会被抛出
- catch:用于捕获try代码块中产生的异常
try catch的用法如下:
try{
//可能出现异常的代码
}catch(异常种类 对象名){
//当try中代码产生异常时执行的操作
}
使用try catch后,可以捕获异常,产生异常后依然不影响系统继续运行,但是try中产生异常的代码后续内容将不会被执行
public static void main(String[] args) {
System.out.println("程序开始执行");
try{
int a = 10 / 0;
System.out.println("程序正在执行");
}catch(Exception e){
System.out.println("异常被捕获了");
}
System.out.println("程序结束执行");
}
//程序开始执行
//异常被捕获了
//程序结束执行
当我们希望能捕获多个异常时,可以使用多重catch
try{
//可能出现异常的代码
}catch(异常种类1 对象名){
//当try中代码产生异常1时执行的操作
}catch(异常种类2 对象名){
//当try中代码产生异常1时执行的操作
}...
catch(异常种类n 对象名){
//当try中代码产生异常n时执行的操作
}
public static void main(String[] args) {
System.out.println("程序开始执行");
try{
String str = null;
str.length();
}catch(ArithmeticException e){
System.out.println("异常1被捕获了");
}catch (NullPointerException e){
System.out.println("异常2被捕获了");
}catch (Exception e){
System.out.println("异常3被捕获了");
}
System.out.println("程序结束执行");
}
//程序开始执行
//异常2被捕获了
//程序结束执行
注意:
在使用多层catch时,要注意优先将具体的异常类写在上面,即保证具体异常优先被捕获.
要保证在上层的异常是下层异常的子类或不具有继承关系.
public static void main(String[] args) {
System.out.println("程序开始执行");
try {
String str = null;
int length = str.length();
}catch(RuntimeException e){
System.out.println("异常1被捕获了");
}
} catch (NullPointerException e) {
System.out.println("异常2被捕获了");
}
}
如上述代码,NullPointerException
是RuntimeException
的子类,所以下面的catch始终不会捕获到异常,不能编译通过
2.2 try catch finally
当我们希望程序是否产生异常都要执行一些操作时,就可以使用try catch finally,不论如何,finally代码块始终会被执行
public static void main(String[] args) {
try{
System.out.println("程序开始执行");
int a = 10 / 0;
}catch(Exception e){
System.out.println("异常被捕获了");
}finally {
System.out.println("程序结束执行");
}
}
//程序开始执行
//异常被捕获了
//程序结束执行
finally中通常用于关闭连接,释放资源,释放锁的操作.如InputStream
、OutputStream
、Scanner
等的资源都需要我们调用close()进行手动关闭,此时就可以使用finally
//读取文本文件的内容
Scanner scanner = null;
try {
scanner = new Scanner(new File("D://read.txt"));
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (scanner != null) {
scanner.close();
}
}
如果我们不想捕获异常,只希望在产生异常后执行某些操作的话,也可以使用try finally
try finally语法和try catch finally类似,只是去掉了catch块
public static void main(String[] args) {
String str = null;
try {
str.length();
} finally {
System.out.println("我出现了空指针");
}
}
2.3 try with resource
在jdk7后,引入了try with resource
,可以更优雅地释放资源
public static void main(String[] args) {
BufferedReader br = null;
String line;
try {
System.out.println("Entering try block");
br = new BufferedReader(new FileReader("test.txt"));
while ((line = br.readLine()) != null) {
System.out.println("Line =>"+line);
}
} catch (IOException e) {
System.out.println("IOException in try block =>" + e.getMessage());
} finally {
System.out.println("Entering finally block");
try {
if (br != null) {
br.close();
}
} catch (IOException e) {
System.out.println("IOException in finally block =>"+e.getMessage());
}
}
}
如上述代码,多层嵌套,显得代码很繁琐,而使用try with resource
可以优雅地解决,格式如下:
try(使用的资源){
//执行的操作
}catch(异常种类 e){
//产生异常执行的操作
}
上述代码可以用try with resource
改写为
public static void main(String[] args) {
String line;
try(BufferedReader br = new BufferedReader(new FileReader("test.txt"));) {
System.out.println("Entering try block");
while ((line = br.readLine()) != null) {
System.out.println("Line =>"+line);
}
} catch (IOException e) {
System.out.println("IOException in try block =>" + e.getMessage());
}
try with resource
是一种语法糖,使用try with resource
打开资源,可以确保在语句结束执行后每个资源都被关闭,而不需要我们进行手动关闭.
try with resource
还可以同时开启多个资源,书写时用;
进行分隔
public static void main(String[] args) {
String line;
try(BufferedReader br1 = new BufferedReader(new FileReader("test1.txt"));
BufferedReader br2 = new BufferedReader(new FileReader("test2.txt"))) {
System.out.println("Entering try block");
while ((line = br1.readLine()) != null) {
System.out.println("Line1 =>"+line);
}
while ((line = br2.readLine()) != null) {
System.out.println("Line1 =>"+line);
}
} catch (IOException e) {
System.out.println("IOException in try block =>" + e.getMessage());
}
}
2.4 try catch finally的注意事项
2.4.1 执行流程
没有发生异常,先执行try代码块中的所有代码,然后执行finally代码块的所有代码,catch代码块会被跳过
发生异常,先执行try代码块中发生异常之前的代码,剩下的语句不再执行,然后执行catch代码块中的代码,最后执行finally代码块的代码
2.4.2 注意
-
finally中的代码不一定所有情况都会执行,如发生以下情况,将不会执行
在前面的代码中使用 System.exit() 退出程序
finally代码块中产生了异常
-
不要在finally中使用rerurn,因为finally肯定会被执行,在finally中使用return会使try或catch中的return失效
3. 异常的声明和抛出
3.1 异常的声明(throws)
Java中,执行的语句必然属于某个方法,如果某个语句可能产生异常,我们可以选择不进行捕获,但是必须在方法上显式地声明异常,交由调用方法的调用者去进行处理,如果方法的调用者不去捕获异常,则要继续向上抛出
public void method() throws IOException,SQLException{
}
在方法中声明一个异常,需要在方法名后使用关键字throw
,后面跟上我们需要声明的异常,如存在多个异常,中间用逗号进行分隔.
通常,对于知道如何处理的异常我们应该去捕获,对于不知道如何处理的异常,应该让它继续传递下去.
public static void test(BufferedReader br) throws IOException {
String line;
while ((line = br.readLine()) != null) {
System.out.println("Line =>"+line);
}
在用throws去向上抛出异常时,要遵循以下规则:
- 如果是运行时异常,Error以及它们的子类,可以选择不使用throws声明要抛出的异常,此时编译仍然可以正常通过,但是发生异常时将会在运行时被抛出.
- 如果是
checked Exception
(编译异常),则必须要显式地向上抛出或者用try catch语句进行捕获 - 在进行方法重写时,如果父类中的方法声明了一个异常,则子类重写的方法也必须声明异常,声明的异常可以是这个异常,也可以是这个异常的子类
public class Exception01 {
public void test() throws RuntimeException{
}
}
public class Exception02 extends Exception01{
@Override
public void test() throws NullPointerException {
}
}
3.2异常的抛出(throw)
如果代码可能出现某些错误,可以创建一个合适的异常类实例并抛出它,这就是抛出异常.
public static int divide(int num1, int num2) {
if (num2 == 0) {
throw new ArithmeticException("传入的被除数不能为0");
}
return num1 / num2;
}
通常情况下我们都不需要手动抛出异常,在Java中的方法要么已经声明了异常,要么就是对异常进行了处理.所以通常对于异常我们只需要捕获或者向上抛出
4.自定义异常
我们可以将项目中的异常封装成自己的一个异常类.通常自定义的异常类应该包含两个构造方法,一个无参构造,一个带有详细描述信息的构造
public class MyException extends Exception{
public MyException() {
}
public MyException(String message) {
super(message);
}
//...
}
提供有参构造可以让Throwable
的 toString 方法打印这些详细信息,让我们在调试时更清晰地了解异常的详细信息
public class ExceptionDemo {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int age = 0;
System.out.println("欢迎光临荔枝网吧,请输入年龄");
try {
age = scanner.nextInt();
if (age >= 0 && age < 18) {
System.out.println("未成年不能上网");
}
if (age >= 18 && age <= 100) {
System.out.println("已成年,可以上网");
}
if (age > 100 || age < 0) {
throw new MyException("您输入的年龄不在范围");
}
} catch (InputMismatchException e) {
System.out.println("您输入的年龄不是数字");
} catch (MyException e) {
System.out.println(e.getMessage());
}
}
}
如这个判断用户年龄是否到达上网年龄的Demo,如果用户输入100以上或负数,会抛出我们自定义的异常,被catch捕获,因为继承了Exception,所以继承了父类所有的属性和方法
5.练习:
1.思考下面方法的返回值是多少
public static int test() {
int i = 0;
String[] names = new String[3];
try {
if (names[0].equals("张三")) {
names[0] = "李四";
} else {
names[3] = "zeh";
return 2;
}
} catch (NullPointerException e) {
return ++i;
} catch (ArrayIndexOutOfBoundsException e) {
return ++i;
} finally {
return ++i;
}
}
2.调用下面方法会输出什么,方法的返回值是多少
public static int test() {
int i = 0;
String[] names = new String[3];
try {
if (names[0].equals("张三")) {
names[0] = "李四";
} else {
names[3] = "zeh";
return 2;
}
} catch (NullPointerException e) {
return ++i;
} catch (ArrayIndexOutOfBoundsException e) {
return ++i;
} finally {
++i;
System.out.println("i="+i);
}
}
答案:
-
names[0].equals(“张三”)会抛出NullPointerException,所以后续代码不会执行,异常被捕获,执行第一个catch中的代码,遇到return语句,因为finally中的return会使catch中的return失效,所以只会执行++i操作,而不会返回值,最后执行finally中的return ++i,最后返回2.
-
names[0].equals(“张三”)会抛出NullPointerException,所以后续代码不会执行,异常被捕获,执行第一个catch中的代码,遇到return语句,但是finally中的代码肯定会被执行,所以会先将catch中的++i用一个temp进行暂存,执行finally中++i输出i=2,然后返回1.