JavaSE学习笔记(二十四)—— 异常
一、异常概述
1.1 什么是异常
异常就是程序出现了不正常的情况。
发现错误的理想时机是在编译期。然而,编译器并不能发现所有的错误,余下的问题就需要在程序运行时解决。这就需要错误能通过某种方式,把适当的信息传递给特定的接收者处理。Java中的异常处理的目的在于通过使用少量的代码来简化大型、可靠的程序的生成,通过此方式让你的应用中没有未处理的错误,而且它还带来了一个明显的好处:降低错误处理代码的复杂度。
异常,根据字面理解,有意外之意。把它置于代码层面来理解,即阻止了当前方法或作用域继续执行。
在Java中,异常被当做对象来处理,其基类是Throwable。
1.2 Java的异常体系
Java中的所有不正常类都继承于Throwable类。Throwable主要包括两个大类,一个是Error类,另一个是Exception类;
其中Error类中包括虚拟机错误和线程死锁,一旦Error出现了,程序就彻底的挂了,被称为程序终结者;这种问题一般都是很严重的,不是我们处理的。
Exception类,也就是通常所说的“异常”。主要指编码、环境、用户操作输入出现问题,Exception主要包括两大类,编译期异常和运行期异常(RuntimeException)
RuntimeException异常主要包括以下四种异常(其实还有很多其他异常,这里不一一列出):空指针异常、数组下标越界异常、类型转换异常、算术异常。RuntimeException异常会由java虚拟机自动抛出并自动捕获(就算我们没写异常捕获语句运行时也会抛出错误!!),此类异常的出现绝大数情况是代码本身有问题应该从逻辑上去解决并改进代码。
不是RuntimeException的异常都是编译期异常,引起该异常的原因多种多样,比如说文件不存在(IOException)、或者是连接错误(SQLException)等等。跟它的“兄弟”RuntimeException运行异常不同,该异常我们必须手动在代码里添加捕获语句来处理该异常,因为你不处理,编译就不能通过。这也是我们学习java异常语句中主要处理的异常对象。
下面通过一个例子来理解上述异常分类:
今天天气很好,班长出去旅游。骑着自行车,去山里面呼吸新鲜空气。
Error:走到半路上,发生山路塌陷,或者出现了泥石流,这个问题很严重,不是班长能够立马解决的。严重的问题
Exception:出门前,班长要看看车轮子以及车链子等是否还在。出发前就应该检查的问题。
RuntimeException:在骑车的过程中,有好路不走,偏偏要走石子路,结果爆胎了。旅游的过程中出现的问题。
1.3 编译时异常和运行时异常的区别
Java中的异常被分为两大类:编译时异常和运行时异常。所有的RuntimeException类及其子类的实例被称为运行时异常,其他的异常就是编译时异常
编译时异常:Java程序必须显示处理,否则程序就会发生错误,无法通过编译。
运行时异常:无需显示处理,也可以和编译时异常一样处理
二、Throwable中的方法
- public String getMessage():获取异常信息,返回字符串。
- public String toString():获取异常类名和异常信息,返回字符串。
此对象的类的 name(全路径名) ": "(冒号和一个空格) 调用此对象 getLocalizedMessage()方法的结果 (默认返回的是getMessage()的内容)
- public String printStackTrace():获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。
- public String printStackTrace(PrintStream s):通常用该方法将异常内容保存在日志文件中,以便查阅。
public class ExceptionDemo { public static void main(String[] args) { String s = "2014-11-20"; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); try { Date d = sdf.parse(s); // 创建了一个ParseException对象,然后抛出去,和catch里面进行匹配 System.out.println(d); } catch (ParseException e) { // ParseException e = new ParseException(); // ParseException // e.printStackTrace(); // getMessage() // System.out.println(e.getMessage()); // Unparseable date: "2014-11-20" // toString() // System.out.println(e.toString()); // java.text.ParseException: Unparseable date: "2014-11-20" e.printStackTrace(); //跳转到某个指定的页面(index.html) } System.out.println("over"); } }
三、异常处理方案
3.1 JVM的默认处理方案
如何程序出现了问题,我们没有做任何处理,最终jvm会做出默认的处理。处理方式是:
把异常的名称,原因及出现的问题等信息输出在控制台。
同时会结束程序。
1 public class ExceptionDemo { 2 public static void main(String[] args) { 3 // 第一阶段 4 int a = 10; 5 int b = 0; 6 System.out.println(a / b); 7 8 // 第二阶段 9 System.out.println("over"); 10 } 11 }
上述程序中,我们并没有做任何处理,运行到第6行时,程序报错,并结束程序,导致"over"并没有输出。
3.2 try-catch
格式:
try { 可能出现问题的代码; }catch(异常名 变量) { 针对问题的处理; }
格式说明:
try块:负责捕获异常,一旦try中发现异常,程序的控制权将被移交给catch块中的异常处理程序。try语句块不可以独立存在,必须与 catch 或者 finally 块同存
catch块:如何处理?比如发出警告:提示、检查配置、网络连接,记录错误等。执行完catch块之后程序跳出catch块,继续执行后面的代码。
public class ExceptionDemo { public static void main(String[] args) { // 第一阶段 int a = 10; // int b = 2; int b = 0; try { System.out.println(a / b); } catch (ArithmeticException ae) { System.out.println("除数不能为0"); } // 第二阶段 System.out.println("over"); } }
注意:
A:try里面的代码越少越好。因为try里面的代码要走异常处理机制,jvm就会开辟新的资源来管理,代码越多,Java就要用更多的资源来处理它。
B:catch里面必须有内容,哪怕是给出一个简单的提示
3.3 try-catch-catch
格式:
try{ ... }catch(异常类名 变量名) { ... } catch(异常类名 变量名) { ... } ...
注意事项:
- 能明确的尽量明确,不要用大的来处理。
- 平级关系的异常谁前谁后无所谓,如果出现了子父关系,父必须在后面。
public class ExceptionDemo { public static void main(String[] args) { // method1(); method2(); } public static void method2() { int a = 10; int b = 0; int[] arr = { 1, 2, 3 }; // 爷爷在最后 try { System.out.println(a / b); System.out.println(arr[3]); System.out.println("这里出现了一个异常,你不太清楚是谁,该怎么办呢?"); } catch (ArithmeticException e) { System.out.println("除数不能为0"); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("你访问了不该的访问的索引"); } catch (Exception e) { System.out.println("出问题了"); } // 爷爷在前面是不可以的 /*try { System.out.println(a / b); System.out.println(arr[3]); System.out.println("这里出现了一个异常,你不太清楚是谁,该怎么办呢?"); } catch (Exception e) { System.out.println("出问题了"); } catch (ArithmeticException e) { System.out.println("除数不能为0"); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("你访问了不该的访问的索引"); }*/ } public static void method1() { int a = 10; int b = 0; int[] arr = {1, 2, 3}; try { System.out.println(a / b); System.out.println(arr[3]); } catch (ArithmeticException e) { System.out.println("除数不能为0"); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("你访问了不该的访问的索引"); } System.out.println("over"); } }
注意:
一旦try里面出了问题,就会在这里把问题给抛出去,然后和catch里面的问题进行匹配,一旦有匹配的,就执行catch里面的处理,然后结束了try...catch,继续执行后面的语句。
【JDK7新特性】
JDK7出现了一个新的异常处理方案:
try{ }catch(异常名1 | 异常名2 | ... 变量 ) { ... }
注意:这个方法虽然简洁,但是也不够好。
A:处理方式是一致的。(实际开发中,好多时候可能就是针对同类型的问题,给出同一个处理)
B:多个异常间必须是平级关系。
public class ExceptionDemo { public static void main(String[] args) { method(); } public static void method() { int a = 10; int b = 0; int[] arr = {1, 2, 3}; /*try { System.out.println(a / b); System.out.println(arr[3]); } catch (ArithmeticException e) { System.out.println("除数不能为0"); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("你访问了不该的访问的索引"); } catch (Exception e) { System.out.println("出问题了"); }*/ // JDK7的处理方案 try { System.out.println(a / b); System.out.println(arr[3]); } catch (ArithmeticException | ArrayIndexOutOfBoundsException e) { System.out.println("出问题了"); } System.out.println("over"); } }
3.4 try-catch-finally
格式:try...catch...finally...
特点:被finally控制的语句体一定会执行。特殊情况:如果在执行到finally之前jvm退出了,就不能执行了。
作用:用于释放资源,在IO流操作和数据库操作中会见到
public class FinallyDemo { public static void main(String[] args) { String s = "2018--5-16"; SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date date = null; try { date = format.parse(s); } catch (ParseException e) { e.printStackTrace(); //System.exit(0);//jvm退出,finally的代码不会执行 }finally { System.out.println("这里的代码是可以执行的"); } System.out.println(date); } }
【final,finally和finalize的区别】
final:最终的意思,可以修饰类,成员变量,成员方法
修饰类,类不能被继承
修饰变量,变量是常量
修饰方法,方法不能被重写
finally:是异常处理的一部分,用于释放资源。
一般来说,代码肯定会执行,特殊情况:在执行到finally之前jvm退出了
finalize:是Object类的一个方法,用于垃圾回收
【补充问题】
如果catch里面有return语句,请问finally里面的代码还会执行吗?如果会,请问是在return前,还是return后?
答:会。在return前。(准确的说,应该是在中间)
public class FinallyDemo2 { public static void main(String[] args) { System.out.println(getInt()); } public static int getInt() { int a = 10; try { System.out.println(a / 0); a = 20; } catch (ArithmeticException e) { a = 30; return a; /* * return a在程序执行到这一步的时候,这里不是return a而是return 30;这个返回路径就形成了。 * 但是呢,它发现后面还有finally,所以继续执行finally的内容,a=40 * 再次回到以前的返回路径,继续走return 30; */ } finally { a = 40; // return a;//如果这样结果就是40了。 } return a; } }
3.5 throws
有些时候,我们是可以对异常进行处理的,但是又有些时候,我们根本就没有权限去处理某个异常。或者说,我处理不了,我就不处理了。为了解决出错问题,Java针对这种情况,就提供了另一种处理方案:throws(抛出)。
格式:throws 异常类名
注意:这个格式必须跟在方法的括号后面。
public class ExceptionDemo { public static void main(String[] args) { System.out.println("今天天气很好"); try { method(); } catch (ParseException e) { e.printStackTrace(); } System.out.println("但是就是不该有雾霾"); method2(); } // 运行期异常的抛出 public static void method2() throws ArithmeticException { int a = 10; int b = 0; System.out.println(a / b); } // 编译期异常的抛出 // 在方法声明上抛出,是为了告诉调用者,你注意了,我有问题。 public static void method() throws ParseException { String s = "2018-11-20"; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date d = sdf.parse(s); System.out.println(d); } }
小结:
编译期异常抛出,将来调用者必须处理。
运行期异常抛出,将来调用可以不用处理。
3.6 throw
在功能方法内部出现某种情况,程序不能继续运行,需要进行跳转时,就用throw把异常对象抛出。
public class ExceptionDemo { public static void main(String[] args) { // method(); try { method2(); } catch (Exception e) { e.printStackTrace(); } } /** * 如果throw的是运行期异常,方法上就不需要throws了 */ public static void method() { int a = 10; int b = 0; if (b == 0) { throw new ArithmeticException(); } else { System.out.println(a / b); } } /** * 如果throw的是编译时期异常,因为你既然写了throw,说明你不想在这里try...catch了,所以要在方法上throws出该异常 * @throws Exception */ public static void method2() throws Exception { int a = 10; int b = 0; if (b == 0) { throw new Exception(); } else { System.out.println(a / b); } } }
【throws和throw的区别】
throws:
-
- 用在方法声明后面,跟的是异常类名
- 可以跟多个异常类名,用逗号隔开
- 表示抛出异常,由该方法的调用者来处理
- throws表示出现异常的一种可能性,并不一定会发生这些异常
throw:
-
- 用在方法体内,跟的是异常对象名
- 只能抛出一个异常对象名
- 表示抛出异常,由方法体内的语句处理
- throw则是抛出了异常,执行throw则一定抛出了某种异常
四、自定义异常
java不可能对所有的情况都考虑到,比如考试成绩必须在0-100之间,如果成绩超过100或成绩小于0的话,都属于异常,很明细Java中没有对应的异常,这时候就需要我们自己来做一个异常。
而我们自己随意的写一个类,是不能作为异常类来看的,要想你的类是一个异常类,就必须继承自Exception或者RuntimeException
public class MyException extends Exception { public MyException() { } public MyException(String message) { super(message); } }
定义一个类:
public class Teacher { public void check(int score) throws MyException { if (score > 100 || score < 0) { throw new MyException("分数必须在0-100之间"); } else { System.out.println("分数没有问题"); } } }
自定义异常测试类
public class StudentDemo { public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.println("请输入学生成绩:"); int score = sc.nextInt(); Teacher t = new Teacher(); try { t.check(score); } catch (MyException e) { e.printStackTrace(); } } }
五、异常的注意事项
- 子类重写父类方法时,子类的方法必须抛出相同的异常或父类异常的子类。(父亲坏了,儿子不能比父亲更坏)
- 如果父类抛出了多个异常,子类重写父类时,只能抛出相同的异常或者是他的子集,子类不能抛出父类没有的异常
- 如果被重写的方法没有异常抛出,那么子类的方法绝对不可以抛出异常,如果子类方法内有异常发生,那么子类只能try,不能throws
public class ExceptionDemo { } class Fu { public void show() throws Exception { } public void method() { } } class Zi extends Fu { @Override public void show() throws ArithmeticException { } @Override public void method() { // String s = "2014-11-20"; // SimpleDateFormat sdf = new SimpleDateFormat(); // Date d = sdf.parse(s); // System.out.println(d); } }