Java-Day-15
异常
引出异常
( Exception )
-
如:零成分母时,会抛出 ArithmeticException 异常,然后程序就会崩溃退出,下面的代码也就不执行了
- 但这种不出现致命错误就使得系统崩溃就不合理了
- 所以设计者提供了异常处理机制来解决此问题
-
解决方式 — 异常捕获
-
如果程序员认为一段代码可能出现异常 / 问题,可以使用 try-catch 异常处理机制来解决,从而保证程序的健壮性
-
选中代码后快捷键 ctrl + alt + t 选中 try - catch
-
处理后即使出现了异常,程序仍可以继续执行
int n1 = 1; int n2 = 0; try { int res = n1 / n2; } catch (Exception e) { System.out.println("出现异常的原因是:" + e.getMessage()); }
-
-
异常介绍
- java语言中,将程序执行中发生的不正常情况称为 ”异常“。
- 开发过程中语法的错误和逻辑错误不是异常
- java语言中,将程序执行中发生的不正常情况称为 ”异常“。
-
执行过程中所发生的异常事件可分为两类
- Error ( 错误 ):Java 虚拟机无法解决的严重问题,如:JVM 系统内部错误、资源耗尽等严重情况。
- StackOverflowError [ 栈溢出 ] 和 OOM ( out of memory ),Error 是严重错误,程序会崩溃
- Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理,如:空指针访问,试图读取不存在的文件,网络连接中断等待
- 往下还分两大类:运行时异常 [ 程序运行时,发生的异常 ] 和编译时异常 [ 编程时,编译器检查出的异常 ]
- Error ( 错误 ):Java 虚拟机无法解决的严重问题,如:JVM 系统内部错误、资源耗尽等严重情况。
-
异常体系图
-
idea 里,在 Throwable 源码里右键打开类图
-
查找分支
-
完成异常体系图
-
虚线:实现类箭头指向的接口
-
实线:继承了箭头指向的类
-
-
Java 源程序 —— ( 编译异常 ) ——> 字节码文件 —— ( 运行异常 ) ——> 内存中加载、运行类
-
异常分为两大类,运行时异常和编译时异常
- 运行时异常,是编译器不要求强制处置的异常,一般是指编程时的逻辑错误,是程序员应该避免其出现的异常
- 对于运行时异常,可以不做处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响
- 而编译时异常是编译器要求必须处置的异常
- 一般像 idea 都会红色波浪线标出来
- 运行时异常,是编译器不要求强制处置的异常,一般是指编程时的逻辑错误,是程序员应该避免其出现的异常
常见运行异常
-
NullPointerException 空指针异常:当应用程序试图在需要对象的地方却使用了 null 时,抛出该异常
String name = null; System.out.println(name.length());
-
ArithmeticException 数学运算异常:当出现异常的运算条件时,抛出此异常
int n1 = 1; int n2 = 0; int res = n1 / n2;
-
ArrayIndexOutOfBoundsException 数组下标越界异常:用非法索引访问数组时抛出的异常
- 缺少命令行参数
int[] arr = {1, 2}; // for (int i = 0; i < arr.length; i++) { 正确 for (int i = 0; i <= arr.length; i++) { // 数组从0开始,只有0、1,到不了2 }
-
ClassCastException 类型转换异常:当试图将对象强制转换为不是实例的子类时,抛出该异常
class B extends A {} class C extends A {} // main A b = new B(); C c = (C)b;
-
NumberFormatException 数字格式不正确异常:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常 ( 使用异常来确保输入是满足条件的数字 )
String name = "啦啦啦"; int num = Integer.parseInt(name);
编译异常
- 编译期间就必须处理的异常,否则代码不能通过编译
- 常见的编译异常
- SQLException // 操作数据库时,查询表可能发生异常
- IOException // 操作文件时发生的异常
- FileNoFoundException // 操作一个不存在的文件时发生的异常
- ClassNotFoundException // 加载不存在的类时
- EOFException // 操作文件到文件末尾,发生异常
- IllegalArguementException // 参数异常
异常处理
-
以下两种处理方式二选一
-
try-catch-finally
-
程序员在代码中捕获发生的异常,自行处理
try { // 代码 / 可能有异常 } catch(Exception e) { // 捕获到异常 // 当异常发生时,系统将异常封装成 Exception 对象 e,传递给 catch // 得到异常对象后,程序员自行处理 // 如果没有发生异常,此处代码块不执行 } finally { // 不管 try 代码块是否有异常发生,始终要执行 finally,也可以选择不写 finally // 所以通常将释放资源的代码放在此处 }
-
注意细节
- 如果异常发生,就不再继续执行后面代码,直接进入 catch 块
- 如果异常没有发生,则顺序执行 try 代码块,不进 catch
- 如果希望不管是否发生异常都执行某段代码,用 finally
-
如果 try 代码有多个异常,就使用多个 catch 分别捕获不同的异常,相应处理
-
但要求子类异常写在前面,父类异常写在后面
catch { ... } catch
-
-
可以直接 try-finally,但这样相当于没有捕获异常,因此程序会直接崩掉 / 退出
try{ int n1 = 10; int n2 = 0; System.out.println(n1 / n2) }finally { System.out.println("执行了finally"); } System.out.println("程序继续执行"); // 若是n2不为零,就会输出计算结果和两条System输出语句 // 如上述代码的话,就只输出:“执行了finally” // 后面一切代码包括后面的System都不会执行
- 应用:不管是否发生异常,都必须执行某个业务逻辑
-
-
throws ( 默认方式 )
-
如果一个方法 ( 中的语句执行时 ) 可能生成某种异常,但并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而交给该方法的调用者来处理,最顶级的处理者就是 JVM
- JVM 处理:输出异常信息,退出程序.
- 就是平常默认的出错方式
-
在方法的声明中用 throws 语句可以声明抛出异常的列表,throws 后面的异常类型可以是方法中产生的异常类型,也可以是异常的父类 ( Exception )
public class test { public static void main(String[] args){ } // 使用第二种方法:throws,抛出异常,让调用f1方法的调用者(方法)处理 // public void f1() throws FileNotFoundException { // throws后面也可以是异常列表,即可以抛出多个异常 // public void f1() throws FileNotFoundException,NullPointerException { // 或者直接用异常的父类,全包括了 public void f1() throws Exception { FileInputStream fis = new FileInputStream("d://aa.txt"); } }
-
注意细节
-
编译异常必须要处理 ( 会标红 )
-
对于运行时异常,程序中如果没有处理,默认就是 throws 的方式处理 ( 最终就是抛给了 JVM )
-
子类重写父类方法时,对抛出异常的规定:子类重写的方法,所抛出的异常要么和父类抛出的异常一致,要么为父类抛出的异常的类型的子类型
// 从先前画的异常体系图可以看出NullPointerException是RuntimeException的子类 class Father { public void method() throws RuntimeException { } } class Son extends Father { @Override public void method() throws NullPointerException { } }
-
编译异常时,方法显示地声明抛出异常,表明该方法将不对这些异常进行处理,而交给该方法的调用者来处理,则调用者 ( 即下述的 f2() ) 要不 try-catch,要不继续 throws
public void f1() throws FileNotFoundException { FileInputStream fis = new FileInputStream("d://aa.txt"); } // 若是这个时候有一个方法调用了这个抛出异常的方法f1 // 只这样写会报错 // public static f2() { // F1: public static f2() throws FileNotFoundException { f1(); } // F2: public static f2() { try { f1(); } catch (FileNotFoundException e) { e.printStackTrace(); } }
-
运行异常时,是存在默认处理的,而 java 中并不要求程序员显式处理,因为有默认处理机制
public static f3() throws AirthmeticException { } public static f4() { // 不会报错 f3(); }
-
-
-
练习
-
看输出什么
public class test { public static void main(String[] args){ System.out.println(Exception.method()); } } class Exception { public static int method(){ int i = 1; try { i++; // i=2 String[] names = new String[3]; if (names[1].equals("tom")) { // 空指针错误直接跳到Null... System.out.println(names[1]); } else { names[3] = "zhuyazhu"; } return 1; } catch (ArrayIndexOutOfBoundsException e) { return 2; } catch (NullPointerException e){ return ++i; // i=3,但还有finally,return不会马上执行,在底层会临时变量temp=3保存 } finally { ++i; // i=4 System.out.println("i=" + i); // 输出 i=4 } } }
-
输出为
i=4
3 -
如果没有出现异常,则执行 try 块中的所有语句,不执行 catch 块中语句,如果有 finally,最后还需要执行 finally 里的语句
-
如果出现异常,则 try 块中异常发生后,try 块剩下的语句不再执行,将执行 catch 块中的语句,如果有 finally,最后还需要执行 finally 里的语句
-
如果没有 catch 捕获,就 finally 后强行退出程序 ( 如果有 finally 的话 ),捕获了的话就继续执行
-
-
如果用户输入的不是一个整数,就提示他反复输入,直到输入一个整数为止
public class test { public static void main(String[] args){ Scanner scanner = new Scanner(System.in); int num = 0; while(true){ try { System.out.println("请输入一个整数:"); num = Integer.parseInt(scanner.next()); break; } catch (NumberFormatException e) { System.out.println("你输入的不是个整数"); } } System.out.println("你输入的值是=" + num); } }
-
自定义异常
-
当程序出现了某些 “ 错误 ”,但该错误信息并没有在 Throwable 子类中描述处理,这个时候可以自己设计异常类,用于描述该错误信息
- 好处就是可以使用默认的处理机制
-
自定义异常的步骤
- 定义类:自定义异常类名 ( 程序员自己写 ) 继承Exception 或 RuntimeException
- 如果继承 Exception,属于编译异常
- 若是用编译异常,就要在调用其的方法里 ( 如:main ) 也要 throws 自定义的异常
- 如果继承 RuntimeException,属于运行异常 ( 一般来说,继承 RuntimeException )
-
应用
-
当接收 Person 对象年龄时,要求范围在 18 ~ 120 之间,否则抛出一个自定义异常 ( 要求继承 RuntimeException ),并给出提示信息
public class test { public static void main(String[] args) { int age = 80; if (!(age >= 18 && age <= 120)) { throw new AgeException("年龄错误,应该在18~120之间"); } System.out.println("输入年龄范围正确"); } } //自定义异常 class AgeException extends RuntimeException { public AgeException(String message) { // 追寻父类是一直到Throwable类,其方法即为将message信息显示于控制台提示 super(message); } }
-
-
throw 和 throws 区别
意义 位置 后面所跟 throws 异常处理的一种方式 方法声明处 异常类型 throw 手动生成异常对象的关键字 方法体中 异常对象 - 如:在上述自定义异常时有一句 throw new AgeException("年龄错误,应该在18~120之间");
-
练习
-
查看输出
public class test { public static void main(String[] args) { try { ReturnExceptionDemo.methodA(); // 从methodA中拿到throw出的异常被下面的catch捕获 } catch (Exception e) { // message:”制造异常“,在这里才被输出 System.out.println(e.getMessage()); // 3 } ReturnExceptionDemo.methodB(); } } //自定义异常 class ReturnExceptionDemo { static void methodA() { try { System.out.println("进入方法A"); // 1 throw new RuntimeException("制造异常"); // 3 } finally { System.out.println("用A方法的finally"); // 2 } } static void methodB() { try { System.out.println("进入方法B"); // 4 return; } finally { System.out.println("用B方法的finally"); // 5 } }
-
输出
进入方法A
用A方法的finally
制造异常
进入方法B
用B方法的finally
-
-
编写应用程序,接收命令行的两个参数,计算两数相除,要求使用 cal (int n1, int n2),对数据格式不正确、缺少命令行参数、除零进行异常处理
public class test1 { public static void main(String[] args) { try { if (args.length != 2) { throw new ArrayIndexOutOfBoundsException("参数个数不对"); } int n1 = Integer.parseInt(args[0]); int n2 = Integer.parseInt(args[1]); double res = cal(n1, n2); System.out.println("计算结果为=" + res); } catch (ArrayIndexOutOfBoundsException e) { System.out.println(e.getMessage()); } catch (NumberFormatException e) { System.out.println("参数格式不正确,需要输入整数"); } catch (ArithmeticException e) { System.out.println("出现了除以零的异常"); } } public static double cal(int n1, int n2) { return n1 / n2; } }
-