异常机制
◼ 异常机制是指当程序出现错误后,程序如何处理。
◼ 在异常引发后,应用程序应该能够转移到一个安全状态,使得系统能够恢复控制权或降级运行 或正常结束程序运行,不至于使系统崩溃或死机,并且尽可能地保存数据、避免损失。
没有异常处理示例:
1 2 3 4 5 6 7 8 9 10 11
| public class Test { public static void main (String args[]) { int i = 0; String[ ] greetings = { "Hello world!", "No, I mean it!", "HELLO WORLD!!" }; while ( i < 4 ) { System.out.println ( greetings[i] ); i++; } System.out.println ("运行完毕"); } }
|
添加异常处理
1 2 3 4 5 6 7 8 9
| try { while ( i < 4 ) { System.out.println( greetings[i] ); i++; } } catch ( Exception e ) { System.out.println( "有异常!"); } System.out.println ("运行完毕");
|
Java 异常处理允许程序捕获异常并处理,异常处理 代码与正常流程代码分离,更易识别和管理
更具体的异常处理(异常分类)
1 2 3 4 5 6 7 8 9
| try { while (i < 4) { System.out.println(greetings[i]); i++; } } catch ( ) { System.out.println( "数组下标" + i + "越界啰!"); } System.out.println ("运行完毕");
|
异常产生的主要原因:
◼ Java 内部错误导致异常,即 Java 虚拟机产生的异常
◼ 编写的程序代码中的错误所产生的异常,例如空指针异常、数组越界异常等
◼ 通过 throw 语句手动生成的异常,一般用来告知该方法的调用者一些必要信息
异常处理
◼ Java异常处理涉及五个关键字:
try、catch、finally
throws、throw (这两个见后序)
try 和 catch 语句
1 2 3 4 5 6 7
| try { } catch ( 异常类型 异常变量名 ) { }
|
注意:一个try块可搭配多个catch块,每一catch块可处理不同的异常
多个 catch 块示例
多个catch匹配原则:由上到下只匹配其中 一个 异常,如匹配则不会再执行其他 catch 块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class Test { public static void main(String[] args) { int i = 0; String[] data = {"5", "0", "aaa"}; while (i < 4) { try { int a = Integer.parseInt(data[i]); System.out.println( reciprocal(a) ); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("数组下标越界:" + i); } catch (NumberFormatException e) { System.out.println("数据格式不正确"); } catch (ArithmeticException e) { System.out.println("除0异常"); } catch (Exception e) { System.out.println("有异常"); } i++; } System.out.println("运行完毕"); } public static int reciprocal(int a) { return 1/a; } }
|
finally 语句
◼ finally 语句定义一个总是要执行的代码块,而不考虑异常是否被捕获。
◼ 基本框架:
1 2 3 4 5 6 7 8 9 10
| try { ... } catch (...) { } finally {
}
|
finally 示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Test { public static void main(String[] args) { Scanner input = new Scanner(System.in); System.out.println("Windows 系统已启动!"); String[] pros = { "记事本", "计算器", "浏览器" }; try { for (int i = 0; i < pros.length; i++) { System.out.println(i + 1 + ":" + pros[i]); } System.out.print("是否运行程序(y/n):"); String answer = input.next(); if (answer.equals("y")) { System.out.println("请输入程序编号:"); int no = input.nextInt(); System.out.println("正在运行程序[" + pros[no - 1] + "]"); } } catch (Exception e) { e.printStackTrace(); } finally { System.out.println("谢谢使用!"); } } }
|
注意:
◼ try、catch、finally 三个语句块均不能单独使用
◼ 三者可以组成 try...catch
、try...catch...finally
、try...finally
三种结构
◼ catch 语句可以有一个或多个,finally 语句最多一个
注意:try、catch、finally 三个代码块中变量的 作用域为代码块内部,分别独立而不能相互访问。如果要在三个块中都能访问,则需要将变量定义到这些块的外面。
关于 getMessage() 和 printStackTrace()
异常分类
◼ Java异常分为两大类,错误类 Error 和异常类 Exception
◼ java.lang.Throwable 作为所有异常的超类
异常分类:
常见的 RuntimeException 异常
运行时异常类型 |
说明 |
ArithmeticException |
算术错误异常,如以零做除数 |
ArraylndexOutOfBoundException |
数组索引越界 |
ArrayStoreException |
向类型不兼容的数组元素赋值 |
ClassCastException |
类型转换异常 |
IllegalArgumentException |
使用非法实参调用方法 |
lIIegalStateException |
环境或应用程序处于不正确的状态 |
lIIegalThreadStateException |
被请求的操作与当前线程状态不兼容 |
IndexOutOfBoundsException |
某种类型的索引越界 |
NullPointerException |
尝试访问 null 对象成员,空指针异常 |
NegativeArraySizeException |
再负数范围内创建的数组 |
NumberFormatException |
数字转化格式异常,比如字符串到 float 型数字的转换无效 |
TypeNotPresentException |
类型未找到 |
常见非运行时异常
非运行时异常类型 |
说明 |
ClassNotFoundException |
类未找到异常 |
NoSuchFieldException |
字段未找到异常 |
NoSuchMethodException |
方法未找到异常 |
InstantiationException |
实例化异常 |
IllegalAccessException |
没有访问权限 |
InterruptedException |
线程被另一个线程中断的异常 |
IOException |
线程被另一个线程中断的异常 |
SQLException |
操作数据库sql异常 |
FileNotFoundException |
文件未找到异常 |
示例:常见的运行时异常
◼ ArithmeticException:算术异常
◼ 如:int k = 1/0;
整数除 0
◼ NullPointerException:空指针异常
◼ 如:String s = null; boolean eq = s.equals("abc");
◼ NumberFormatException:数字格式异常
◼ 如:int x = Integer.parseInt("12.34");
◼ ArrayIndexOutOfBoundsException:数组索引越界异常
◼ 如:int[] a=new int[3]; int x=a[-1] 或 a[3];
◼ StringIndexOutOfBoundsException:字符串索引越界异常
◼ 如:String s = "hello"; char c = s.chatAt(5);
◼ ClassCastException:类型转换异常
◼ 如:A 不是 B 的父类或子类, A a = new A(); B b=(b)a;
◼ IllegalArgumentException:传递非法参数异常
◼ 如:Color c = new Color(0,256,255);
颜色值只能 0-255
异常抛出
◼ 非 RuntimeException 异常,是必须进行处理的异常,如果不处理,程序就 不能编译通过
◼ 对于有些异常是 当前层不能或不想解决的 ,则可 抛出异常 ,由上层调用者来处理
◼ 上层可以使用 try ... catch
捕获处理,也可继续向上抛出…(最终会由 JVM 处理)
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class Test { public static void main(String[] args) { String url ="jdbc:mysql://localhost:3306/school"; Class.forName("org.gjt.mm.mysql.Driver").newInstance(); Connection conn= DriverManager.getConnection(url,"root","dba"); Statement stmt=conn.createStatement(); String sql="select * from teacher"; ResultSet rs=stmt.executeQuery(sql); while( rs.next() ) { System.out.print(rs.getString("id")+" "); System.out.print(rs.getString("name")+" "); System.out.print(rs.getString("address")+" "); System.out.print(rs.getString("year")+"\n"); } rs.close(); stmt.close(); conn.close(); } }
|
非运行时异常,如果不处理,程序就不能编译通过
**添加异常处理:使用 try…catch **
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| String url = "jdbc:mysql://localhost:3306/school"; try { Class.forName("org.gjt.mm.mysql.Driver").newInstance(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } try { Connection conn = DriverManager.getConnection(url, "root", "dba"); Statement stmt = conn.createStatement(); … } catch (SQLException e) { e.printStackTrace(); }
|
try…catch处理存在的不足:过多会影响程序完整性和可阅读性
更好的异常处理:抛出异常
1 2 3 4 5 6 7 8 9 10
| public class Test { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException { String url ="jdbc:mysql://localhost:3306/school"; Class.forName("org.gjt.mm.mysql.Driver").newInstance(); Connection conn= DriverManager.getConnection(url,"root","dba"); Statement stmt=conn.createStatement(); … } }
|
代码结构看上去更完整
throws 关键字
◼ throws 关键字:
◼ 出现在方法的声明中,表示该方法可能会抛出的异常
◼ 允许 throws 后面跟着多个异常类型 (用逗号分隔)
◼ 这些异常类可以是方法中调用了可能拋出异常的方法而产生的异常,也可以是方法体中生成并拋出的异常
◼ main 方法也可使用 throws 抛出异常,该异常将交给 JVM 处理
注意:重写方法抛出异常的限制
◼ 规则:重写方法一定不能抛出新的非运行时异常,或者比被重写方法声明更加宽泛的非运行时异常
◼ 解释:子类方法抛出的异常类型应该是父类方法抛出的异常类型的子类或相同,子类方法抛出的异常 不允许比父类方法抛出的异常多。
示例1
1 2 3 4 5 6 7 8 9 10
| class Father { public void test() throws IOException { } }
class Son extends Father { @Override public void test() throws Exception { } }
|
示例2:
1 2 3 4 5 6 7 8 9
| public class A { public void foo() throws EOFException { } } class B extends A { @Override public void foo() throws FileNotFoundException { } }
|
throw关键字
◼ throw关键字:
◼ throw出现在方法体中,用于明确抛出一个具体的异常对象
◼ throw关键字不会单独使用 (通常要配合一些条件)
用法1:if (异常条件) throw 异常对象 ;
用法2:try {…}
catch { throw 异常对象; }
说明:当 throw 语句执行时,它后面的语句将不执行,此时程序转向调用者程序,寻找与之相匹配的 catch 语句,执行相应的异常处理程序。 如果没有找到相匹配的 catch 语句,则再转向上一层的调用程序。这样逐层向上,直到最外层的异常处理程序终止程序并打印出调用栈情况。
throw 示例1:
◼ 在某仓库管理系统中,要求管理员的用户名需要由 8 位以上的字母或者数字组成,不能含有其他的字符。
◼ 当长度在 8 位以下时拋出异常,并显示异常信息;
◼ 当字符含有非字母或者数字时,同样拋出异常,显示异常信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class Test { public static boolean validateUserName(String username) throws IllegalArgumentException{ boolean confirm = false; if ( username.length() > 8 ) { for (int i = 0; i < username.length(); i++) { char ch = username.charAt(i); if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { confirm = true; } else { throw new IllegalArgumentException("用户名只能由字母和数字组成!"); } } } else { throw new IllegalArgumentException("用户名长度必须大于 8 位!"); } return confirm; } public static void main(String[] args) { Scanner input = new Scanner(System.in); System.out.print("请输入用户名:"); String username = input.next(); try { boolean confirm = validateUserName(username); if (confirm) { System.out.println("用户名输入正确!"); } } catch (IllegalArgumentException e) { System.out.println(e.getMessage()); } } }
|
注:当异常被抛出时,程序会跳出方法
throws 示例2:自定义 sqrt
1 2 3 4 5 6 7 8 9
| public class Test { public static void main(String[] args) { try { System.out.println( Math.sqrt(-144) ); } catch( Exception e ) { System.out.println("根号下不能负数"); } } }
|
自定义 MyMath.sqrt() 来改进 Math.sqrt() ,并对异常进行重新封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class MyMath { public static double sqrt(String nStr) throws Exception { if (nStr == null) { throw new Exception("输入的字符不能为空!"); } double n = 0; try { n = Double.parseDouble(nStr); } catch( NumberFormatException e ) { throw new Exception("输入的字符串必须能够转化成数字!"); } if ( n < 0 ){ throw new Exception("输入的字符串转化成的数字必须大于0!"); } return Math.sqrt(n); } }
|
测试一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public static void main(String[] args) { boolean f = true; while(f) { try{ System.out.print("请输入值:"); String s=new Scanner(System.in).nextLine(); System.out.println( MyMath.sqrt(s) ); f=false; } catch( Exception e ) { System.out.println(e.getMessage()); f=true; } } }
|
throws 和 throw 几点区别:
◼ throws 用在方法的声明处,而 throw 用在方法内部通过
◼ throws 用来声明方法可能抛出是哪种类型的异常,throw 则是拋出的一个具体的异常实例
◼ throws 抛出异常是一种可能出现的异常(并不一定会发生),throw 如果执行了,则一定是抛出了某种异常
◼ 两种抛出都不会由方法自身去处理,真正的处理由上层调用者处理
自定义异常类
◼ 用户自定义异常类是通过扩展Exception类来创建的
◼ 这种异常类可以包含一个“普通类”所包含的任何东西
示例
◼ 编写一个程序,对会员注册时的年龄进行验证,即检测是否在 0~100 岁
自定义异常类
1 2 3 4 5 6 7
| class AgeRangeException extends Exception { public AgeRangeException() { } public AgeRangeException(String msg) { super(msg); } }
|
自定义异常类一般包含两个构造方法:
① 无参的默认构造函数
② 以字符串的形式接收一个定制的异常消息,并将该 消息传递给超类的构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class Test { public static void main(String[] args) { int age; Scanner input = new Scanner(System.in); System.out.print("请输入年龄:"); try { age = input.nextInt(); if (age < 0) { throw new AgeRangeException("输入的年龄为负数!输入有误!"); } else if (age > 100) { throw new AgeRangeException("输入的年龄大于100!输入有误!"); } else { System.out.println("您的年龄为:" + age); } } catch ( AgeRangeException e ) { System.out.println( e.getMessage() ); } catch ( InputMismatchException e ){ System.out.println("输入的年龄不是数字!"); } } }
|
附录
try/catch/finally中的return
◼ 在 try 块、catch 块遇到 return 语句时, 当执行完 return 语句之后 ,并不会立即结束该方法,而是去寻找其是否包含 finally 块
◼ 如果没有 finally 块,方法终止,返回相应的返回值。如果有 finally 块,则开始执行 finally 块;
◼ 只有当 finally 块执行完成后,系统才会再次跳回来根据 return 语句结束方法;
◼ 如果在 finally 块里使用了 return 语句来导致结束方法,则 finally 块已经结束了方法,系统不会跳回去执行 try、catch 块里的任何代码。
示例 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class Test { public static boolean foo() { try { int i = 10 / 0; System.out.println("i = " + i); return true; } catch (Exception e) { System.out.println(" -- catch --"); return false; } finally { System.out.println(" -- finally --"); } } public static void main(String[] args) { System.out.println( foo() ); } }
|
示例 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class Test { public static boolean foo () { boolean b = true; try { int i = 10 / 0; System.out.println("i = " + i); return true; } catch (Exception e) { System.out.println(" -- catch --"); System.out.println("b:" + b); return b = false; } finally { System.out.println(" -- finally --"); System.out.println("b:" + b); } } public static void main(String[] args) { System.out.println( foo() ); } }
|
示例 3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Test { public static boolean foo() { try { int i = 10 / 0; System.out.println("i = " + i); return true; } catch (Exception e) { System.out.println(" -- catch --"); return false; } finally { System.out.println(" -- finally --"); return true; } } public static void main(String[] args) { System.out.println( foo() ); } }
|
示例 4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Test { public static int foo() { int count = 5; try { return ++count; } catch (Exception e) { } finally { System.out.println("finally()执行"); return count++; } } public static void main(String[] args) { System.out.println( foo() ); } }
|
日志
◼ 日志用来记录程序的运行轨迹,方便查找关键信息,也方便快速定位解决问题。
◼ Java 自带的日志工具类:java.util.logging
日志级别
日志用法示例
1 2 3 4 5 6 7 8 9
| public class Test { private static Logger log = Logger.getLogger( Test.class.toString() ); public static void main(String[] args) { log.info("这是info级信息"); log.warning("这是warning级信息"); log.severe("这是server级信息"); } }
|
断言
◼ 断言是一种调试方式,断言失败会抛出 AssertionError,只能在开发和测试阶段启用断言
◼ 基本用法:
assert boolean 表达式 : 错误信息
功能:
◼ 当表达式为真时,程序继续执行;
◼ 当表达式为假时,程序中断,显示 AssertionError 异常和错误信息:
如何开启断言
assert 默认是关闭的,开启方法:在运行配置中添加 VM 参数:-ea
断言示例:
1 2 3 4 5 6 7
| public class Test { public static void main(String[] args) { double x = 100; assert x > 200 : "x must > 200"; System.out.println(x); } }
|