Java语言基础(SE)-第五节 异常(Exception)
异常简介
- 开发中的错误: 在开发 Java 程序的过程中,会遇到各种各样的错误
- 语法错误:会导致编译失败,程序无法正常运行
- 逻辑错误:比如需要执行加法操作时,不小心写成了减法操作
- 运行时错误:在程序运行过程中产生的意外,会导致程序终止运行
- Java 异常强制用户考虑程序的强健性和安全性。异常处理不应用来控制程序的正常流程,其主要作用是捕获程序在运行时发生的异常并进行相应处理。
异常的种类
-
所有的异常最终都继承自 java.lang.Throwable
Object Throwable Exception Error RuntimeException(运行时异常) (非运行时异常)... ...
- Throwable 类是所有异常和错误的超类,下面有 Error 和 Exception 两个子类分别表示错误和异常。其中异常类 Exception 又分为运行时异常和非运行时异常,这两种异常有很大的区别,也称为非检查异常(Unchecked Exception)和检查异常(Checked Exception)。
- Exception 类用于用户程序可能出现的异常情况,它也是用来创建自定义异常类型类的类。
- Error 定义了在通常环境下不希望被程序捕获的异常。Error 类型的异常用于 Java 运行时由系统显示与运行时系统本身有关的错误。通常是灾难性的致命错误,不是程序可以控制的。堆栈溢出是这种错误的一例。
- 检查型异常(Checked Exception)
- 这类异常一般难以避免,编译器会进行检查:如果开发者没有处理这类异常,编译器将会报错
- 指 Error、RuntimeException 以外的异常,类型上都属于 Exception 类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如 IOException、ClassNotFoundException 等以及用户自定义的 Exception 异常,一般情况下不自定义检查异常。表 1 列出了一些常见的异常类型及它们的作用。
- 非检查型异常(Unchecked Exception)
- 这类异常一般可以避免,编译器不会进行检查:如果开发者没有处理这类异常,编译器将不会报错
- 都是 RuntimeException 类及其子类异常,如 NullPointerException、IndexOutOfBoundsException 等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般由程序逻辑错误引起,程序应该从逻辑角度尽可能避免这类异常的发生。
- 哪些异常是非检查型异常?:Error、RuntimeException以及其子类
-
举例:
******检查型异常********** //java.io.FileNotFoundException 文件不存在 FileOutputStream fos = new FileOutputStream("F://xxx.txt"); //反射 //java.lang.ClassNotFoundException,不存在这个类 Class cls = Class.forName("Dog"); //java.lang.InstantiationException,没有无参的构造函数 //java.lang.IllegalAccessException,没有权限访问构造函数 Dog dog = (Dog) cls.newInstance(); ******非检查型异常********** //Error //java.lang.OutOfMemoryError,内存不够用 for (int i = 0; i < 200; i++) { long[] a = new long[1000000000]; } //RuntimeException //java.lang.NullPointerException,使用了空指针 StringBuilder s = null; s.append("abc"); //java.lang.NumberFormatException数字的格式不对 Integer i = new Integer("abc"); int[] array = {11,22,33}; //java.lang.ArrayIndexOutOfBoundsException,数组索引越界 array[4] = 44; Object obj = "123.4"; //java.lang.ClassCastException:类型不匹配 Double d = (Double)obj;
- 总结: 检查就是编译器对否会自动检查,这个方法是否会抛出异常、以及抛出异常的种类是否需要开发者必须用try-cach或者throws来处理。检查型异常类型必须处理,非检查型类异常可以不处理
-
常见的异常类型及它们的作用。
Exception 异常层次结构的根类 RuntimeException 运行时异常,多数 java.lang 异常的根类 ArithmeticException 算术谱误异常,如以零做除数 ArraylndexOutOfBoundException 数组大小小于或大于实际的数组大小 NullPointerException 尝试访问 null 对象成员,空指针异常 ClassNotFoundException 不能加载所需的类 NumberFormatException 数字转化格式异常,比如字符串到 float 型数字的转换无效 IOException I/O 异常的根类 FileNotF oundException 找不到文件 EOFException 文件结束 InterruptedException 线程中断 IllegalArgumentException 方法接收到非法参数 ClassCastException 类型转换异常 SQLException 操作数据库异常
异常处理
- 程序产生了异常,一般称之为:抛出了异常
- 如果没有主动去处理它,会导致程序终止运行
- 异常产生后默认的处理过程解析(自动处理的过程):
- 默认会在出现异常的代码那里自动创建一个异常对象:ArithmeticException
- 异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM虚拟机
- 虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据
- 直接从当前执行的异常点结束当前程序
- 后续代码没有机会执行,因为程序已经死亡
- 异常处理有2种方法
- try-catch: 捕获异常
- throws: 将异常往上抛
try-catch
-
示例代码
try { 代码1 代码2 (可能会抛出异常) 代码3 } catch (异常A e) { //当抛出【异常A】类型的异常时,会进入这个代码块 }catch (异常B e) { //当没有抛出【异常A】类型 //但抛出【异常B】类型的异常时,会进入这个代码块 }catch (异常C e) { //当没有抛出【异常A】、【异常B】类型 //但抛出【异常C】类型的异常时,会进入这个代码块 } 代码4
- 如果【代码2】没有抛出异常
- 【代码1、3】都会被执行
- 所有的 catch 都不会被执行
- 【代码4】会被执行
- 如果【代码2】抛出异常
- 【代码1】会被执行、【代码3】不会被执行
- 会选择匹配的 catch 来执行代码
- 【代码4】会被执行
- 父类型的异常必须写在子类型的后面
- 【异常A】不可以是【异常B、C】的父类型
- 【异常B】不可以是【异常C】的父类型
- 如果【代码2】没有抛出异常
-
异常对象的常用方法
try { Integer i = new Integer("abc"); } catch (NumberFormatException e) { //异常描述 System.out.println(e.getMessage()); //异常名称 异常描述 System.out.println(e); //打印堆栈信息 e.printStackTrace(); }
-
一个 catch 捕获多种类型的异常
- 从 Java 7 开始,单个 catch 可以捕获多种类型的异常
- 如果并列的几个异常类型之间存在父子关系,保留父类型即可
- 这里的变量 e 是隐式 final 的,就是e不能再次赋值了
try { } catch (异常A | 异常B | 异常C e) { //当抛出【异常A】或【异常B】或【异常C】类型的异常时,会进入这个代码块 }
finally
- try 或 catch 正常执行完毕后,一定会执行 finally 中的代码
- finally 可以和 try-catch 搭配使用,也可以只和 try 搭配使用
- 经常会在 finally 中编写一些关闭、释放资源的代码(比如关闭文件)
try { } catch (异常e) { }finally { } try { }finally { }
-
举例:
PrintWriter out = null; try { out = new PrintWriter("F:/zh.txt"); //向文件中写入内容 out.print("My name is ZH"); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { if (out != null) { //关闭文件 out.close(); } }
- finally 细节
- 如果在执行 try 或 catch 时,JVM 退出或者当前线程被中断、杀死
- finally 可能不会执行
- 如果 try 或 catch 中使用了 return、break、 continue 等提前结束语句
- finally 会在 return、break、continue 之前执行
-
下面代码的打印结果是什么?
for (int i = 1; i <= 3; i++) { try { System.out.println(i + "_try_1"); if (i== 2) continue; System.out.println(i + "_try_2"); } finally { System.out.println(i + "_finally"); } }
-
上面是continue,则打印结果
1_try_1 1_try_2 1_finally 2_try_1 2_finally 3_try_1 3_try_2 3_finally
-
如果是break
1_try_1 1_try_2 1_finally 2_try_1 2_finally
-
-
代码举例:
static int get() { try { new Integer("abc"); System.out.println(1); return 2; } catch (Exception e) { System.out.println(3); //return之前要先去调用finally!!! return 4; } finally { System.out.println(5); } } public static void main(String[] args) { System.out.println(get()); } //打印结果: 3 5 4
- 如果在执行 try 或 catch 时,JVM 退出或者当前线程被中断、杀死
throws
- throws 的作用:将异常抛给上层方法
- 如果 throws 后面的异常类型存在父子关系,保留父类型即可
-
如果异常最终抛给了 JVM,那么整个 Java 程序将终止运行
//案例x public static void main(String[] args) throws ClassNotFoundException { System.out.println(1); //没有try-catch,往JVM抛,JVM终止程序 method1(); //不会执行 System.out.println(2); } static void method1() throws ClassNotFoundException { //没有try-catch,往上层抛 method2(); } static void method2() throws ClassNotFoundException { //没有try-catch,往上层抛 method3(); } static void method3() throws ClassNotFoundException { //没有try-catch,往上层抛 //java.lang.ClassNotFoundException,不存在这个类,编译时异常 Class cls = Class.forName("Dog"); }
-
-
可以一部分异常使用 try-catch 处理,另一部分异常使用 throws 处理
static void methodx() throws FileNotFoundException { PrintWriter out = new PrintWriter("F:/zh.txt"); try { Class cls = Class.forName("Dog"); } catch (Exception e) { e.printStackTrace(); } }
- 总结: throws是不想处理的异常,则抛到上一层处理。try-catch处理当前异常
- throws 的细节
- 当父类的成员方法没有 throws 异常
- 子类的重写方法也不能 throws 异常
- 当父类的成员方法有 throws 异常
- 子类的重写方法可以
- 不 throws 异常
- throws 跟父类一样的异常
- throws 父类异常的子类型
- 子类的重写方法可以
public class Person { public void test1() {}; public void test2() throws IOException {}; public void test3() throws IOException {}; public void test4() throws IOException {}; } public class Student extends Person { @Override public void test1() {}; @Override public void test2() {}; @Override public void test3() throws IOException {}; @Override public void test4() throws FileNotFoundException {}; }
- 当父类的成员方法没有 throws 异常
throw
-
使用 throw 可以抛出一个新建的异常
public class Person { public Person(String name) throws Exception { if (name == null || name.length() == 0) { throw new Exception("name must not be empty"); } } } public static void main(String[] args) { try { //这里就必须处理异常了 Person person = new Person("zh"); } catch (Exception e) { e.printStackTrace(); } }
总结:非检查型(运行时)异常、检查型(编译时)异常处理机制
- 检查型(编译时)异常处理方式:必须处理
- 方式一:在编译出现异常的地方,直接通过try-catch进行捕获异常然后处理。
- 方式二:在最外层方法实现try-catch捕获处理异常,在每层子方法后手工加上
throws 异常类型
来向外抛出异常(比如案例x
)
- 非检查型(运行时)异常:可以不用处理,默认会层层抛出 是自动往外层方法抛异常(默认
throws RuntimeException
)不需要手工抛出(案例x
中所有的throws都不需要添加),处理规范直接在最外层方法通过try-catch捕获处理异常。也可以直接在异常处使用try-catch。 - 区别:
- 编译异常和运行时异常都可以用try-catch、throws来处理,但是使用throws时,编译时异常需要手工添加,运行时不需要手工添加。
- 即检查型异常编译器强制要求通过try-catch或throws去处理,非检查型异常编译器不强制要求
- 异常只要最终传递到了JVM中,都会导致程序终止运行
自定义异常:
- 开发中自定义的异常类型,基本都是以下 2 种做法
- 继承自 Exception—检查型异常,必须处理
- 使用起来代码会稍微复杂
- 希望开发者重视这个异常、认真处理这个异常
- 开发者要么try-catch,要么throws
- 继承自 RuntimeException—非检查型异常,可以不处理
- 使用起来代码会更加简洁
- 不严格要求开发者去处理这个异常
- 开发者可以不用处理异常
- 继承自 Exception—检查型异常,必须处理
-
代码举例:
public class EmptyNameException extends RuntimeException { public EmptyNameException() { super("name must not be empty"); } } public class WrongAgeException extends RuntimeException { private int age; public WrongAgeException(int age) { super("wrong age:" + age + ", age must be >0"); } } public class Person { private String name; private int age; public Person(String name,int age) { if(name == null || name.length() ==0) { throw new EmptyNameException(); } if(age<0) { throw new WrongAgeException(age); } this.name = name; this.age = age; } } //非检查型异常,可以不做处理,但是会透传到 // com.zh.WrongAgeException: wrong age:-10, age must be >0 Person person = new Person("ZH", -10); //不会执行 System.out.println(1);
使用异常的好处
- 将错误处理代码与普通代码区分开
- 能将错误信息传播到调用堆栈中
- 能对错误类型进行区分和分组
断言
//编写一个断言类
public class Asserts {
public static void test(boolean v) {
if(v) return;
//打印异常,并输出异常的栈位置
System.err.println(new RuntimeException().getStackTrace()[1]);
//抛出异常
//throw new IllegalArgumentException("条件不满足");
}
}
//使用
int age = 10;
//用于判断结果是否正确,如果正确没有反应,反之,抛出异常
Asserts.test(age>0);
String name = "";
Asserts.test(name != null && name.length() == 0);