一、异常概述
异常是为了反馈和处理/解决问题设计的一套机制。异常的顶级父类是Throwable,它有两个子类:Error、Exception。下面分别详细介绍二者。
二、异常的特点
1、Exception的定义:表示在合理的应用程序中出现的可以处理的问题。
2、异常的分类:
- 编译时异常:在编译的时候就出现并要求处理的异常。比如CloneNotSupportedException ParseException
- 运行时异常:在编译的时候不出现,到了运行的时候才会报错。在使用的时候可以处理,如果不处理在运行的时候会报错。比如:RuntimeException NullPointerException ArrayIndexOutOfBoundsException ClassCastException等。
3、异常的处理方式:
- 继续向上抛出:throws
- 可以捕获:try-catch
4、自定义异常:如果在实际开发过程中,遇到了问题但是在java中没有对应的异常,那么就需要自己定义一个异常。
如果定义的是一个编译时异常,写一个类继承Exception。
如果需要定义一个运行时异常,需要继承RuntimeException。
示例如下:
public class ExceptionDemo2 {public static String readTxtFile(String path) throws PathNotExistException, FileFormatException { // 路径问题 if (!new File(path).exists()) // 需要将抛出的问题以对象的形式来进行封装 // 如果跑出了异常对象,那么该方法的后续代码不再执行 throw new PathNotExistException("亲,这个路径不存在哦~~~"); // 文件格式不正确 if (!path.endsWith(".txt")) { // xxxx.xxx.xxx int index = path.lastIndexOf('.'); String suffix = path.substring(index + 1); throw new FileFormatException("需要一个txt文件,但是传入一个" + suffix + "文件"); } // 正常读取 return "读取成功~~~"; } } @SuppressWarnings("serial") class PathNotExistException extends Exception { // private String message; public PathNotExistException(String msg) { // this.message = msg; super(msg); } // public String getMessage() { // return message; // } } @SuppressWarnings("serial") class FileFormatException extends Exception { public FileFormatException(String msg) { super(msg); } }
5、异常的捕获方式
- 分别处理:每一个异常分别对应一个catch,适合于每一个异常的处理方式各不一样的情况。
- 统一处理:如果所有的异常处理方式都一样,那么可以统一捕获一个父类异常对象。
- 分组处理:如果异常之间的处理方式进行了分组,那么同一组异常之间可以用 “|”隔开 ---从JDK1.7开始。
public static void main(String[] args) /* throws FileFormatException */ { //1.分别处理异常 // try { // String msg = readTxtFile("D:\a.bmp"); // System.out.println(msg); // } catch (PathNotExistException e) { // // 处理问题 // // System.out.println("捕获到了一个问题:路径不存在~~~"); // System.out.println(e.getMessage()); // } catch (FileFormatException e) { // // System.out.println("文件格式不正确~~~"); // System.out.println(e.getMessage()); // } //2. 统一处理 // try { // String msg = readTxtFile("D:\a.bmp"); // System.out.println(msg); // } catch (Exception e) { // System.out.println(e.getMessage()); // } //3.分组处理 try { String msg = readTxtFile("D:\a.bmp"); System.out.println(msg); } catch (PathNotExistException | FileFormatException e) { // 表示捕获PathNotExistException或者是FileFormatException System.out.println(e.getMessage()); } }
6、异常对方法的重载没有影响。
// 异常对方法的重载的有影响吗? class A { public void m() throws IOException { System.out.println("A m()"); } public void m(int i) throws ParseException { System.out.println("A m(int)"); } } class B extends A { public void m() throws EOFException, FileNotFoundException, NullPointerException { System.out.println("B m()"); } }
7、异常对方法重写的影响:在方法重写的时候,子类中重写的方法抛出的编译时异常不能超过父类方法抛出的编译时异常的范围。即:子类不能抛出比父类更高级的异常。
8、finally:无论前边的代码执行成功与否,finally中的代码都会执行一次。内存中的栈可以分为计算区和结果区;当try检测到finally时,程序还是会顺序执行。只不过会返回结果会延迟,此时,当执行到try中的return语句时,会先将结果存储到结果区,等到finally程序执行完毕后,再将结果返回。如果try 和 finally都有return语句,这以finally返回语句为准。例如如下实例返回结果为5。
public static void main(String[] args) { System.out.println(m()); } private static int m() { int i = 5; try { // 代码依然是从上到下顺次执行的 // 在执行try之前会先检测是否有finally // 如果有finally,那么会将try中的返回过程推后 // 栈内存的结构:计算区域,结果区域 -> 栈帧 // 实际执行过程:try->finally->方法返回实际结果 // 先执行try中的代码 // 执行return语句,只是将结果放入结果区域 // i在计算区域继续自增为6 return i++; } finally { // 继续执行finally // i在计算区域继续自增为7 // 执行完成finally,方法会将结果区域的值进行返回 i++; System.out.println("finally:" + i); } }
当返回的是一个引用类型时,由于finally中值的改变是改变的结果区的数据,因此,此时会影响到返回结果。例如:
public class ExceptionDemo8 { public static void main(String[] args) { System.out.println(m()); } private static Student m() { Student s = new Student(); try { s.setName("翠花"); s.setAge(18); // 因为结果区域存储的是s的地址 return s; } finally { s = new Student(); s.setName("周星星"); s.setAge(80); } } } class Student { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student [name=" + name + ", age=" + age + "]"; } }
此时,返回的结果是:周星星,80
9、如果方法抛出的是父类异常,那么使用的时候也必须捕获一个父类异常;再捕获异常的时候,必须先捕获子类异常后捕获父类异常。原因:如果先捕获父类异常,父类异常中已经包含子类异常,就会报错了。
10、当项目处在开发期间是,如果项目中出现了异常,打印这个异常的栈轨迹,然后根据这个轨迹进行调错;如果项目已经上线,出现了异常,记录错误日志。
三、Error
1、表示严重的错误。
2、出现之后不能处理。
3、出现之后只能尽量优化代码,减少错误出现的几率。