文章目录
JAVA异常
JAVA异常的分类和概述:
- 异常的概述: 异常就是Java程序在运行过程中出现的错误。
- JAVA异常继承体系的图示:
- 异常的基类: Throwable
- 严重问题: Error (不予处理,因为这种问题一般是很严重的问题,比如: 内存溢出)
- 非严重问题: Exception(异常)
a. 编译时异常: 非RuntimeException
b. 运行时异常: RuntimeException
JVM默认是如何处理异常的:
- JVM默认是如何处理异常的?
main函数收到这个问题时,有两种处理方式:
a: 自己将该问题处理,然后继续运行(自己try,catch处理)
b: 自己没有针对的处理方式,只有交给调用main的jvm来处理(向上抛出异常)
jvm有一个默认的异常处理机制,就将该异常进行处理.并将该异常的名称,异常的信息.异常出现的位置打印在了控制台上,同时将程序停止运行(就是报错!),然后退出虚拟机。
运行期异常:
- 概念:发生在程序运行过程当中,你可以解决,也可以不解决。(自己不解决,则会把异常跑给JVM虚拟机,虚拟机会根据默认的处理方式来打印异常信息并且中断程序运行然后退出虚拟机!)
- 特别地:那如果说,你觉得JVM的处理方式不够友好,那么你可以自己处理运行期异常(不把它抛给JVM去处理),利用try…catch自己来进行处理。
try…catch的方式处理异常:
- 格式:(完整)
try { 可能出现问题的代码 ; }catch(异常名 变量名){ 针对问题的处理 ; }finally{ 释放资源; }
- 格式:(变型)
变形格式: try { 可能出现问题的代码 ; }catch(异常名 变量名){ 针对问题的处理 ; }
- 小结:
try 里面 放的是,有可能会出现异常的代码,一旦出现异常,就会执行catch里面的代码
当然try里面没有遇到异常,那么catch里面的代码,就不执行
finally中的语句不管是否出现异常都会执行。(我们一般在finally里面喜欢做一些善后收尾工作,比如释放资源) - 问题:C代码占用的资源在程序运行结束之后要是不手动释放,也会继续占用该资源,实则为:内存泄露。那具体的原因是什么?
- 注意事项:
a: try中的代码越少越好
b: catch中要做处理,哪怕是一条输出语句也可以.(不能将异常信息隐藏) - 示例:
public class MyTest3 { public static void main(String[] args) { //我们自己处理运行期异常,使用关键字try{}catch() int a = 1; int b = 0; try { //try 里面 放的是,有可能会出现异常的代码,一旦出现异常,就会执行catch里面的代码 //当然try里面没有遇到异常,那么catch里面的代码,就不执行 System.out.println(a / 1); } catch (ArithmeticException e) { //处理异常的方式 System.out.println("除数为0了"); } System.out.println("下面的代码"); System.out.println("下面的代码"); } } ----------- 输出: 除数为0了 下面的代码 下面的代码
try…catch的方式处理多个异常:
-
快捷键:选中一个类,然后ctrl+h(可以看到一个类的继承关系!)
-
多个异常捕获方法:
a: 一般情况下,尽可能明确异常类型,不要一下全用异常的父类进行捕获。(异常的父类Exception可以捕获所有的错误类型,捕获后打印e即可看到详细的异常种类。)int[] arr={2,3}; arr=null; try { System.out.println(arr.length); } catch (Exception e) { System.out.println(e); } ---------------- 输出: java.lang.NullPointerException
b: 如果,你捕获的异常,是平级关系,谁前谁后无所谓,如果异常之间有继承关系,则把父类异常写到最后。
c: try里面放一些可能会出现异常的代码,无谓的代码不要往其中放置。 -
示例:
public class MyTest { public static void main(String[] args) { int a=1; int b=0; int[] arr={2,3}; arr=null; try { //try 里面放一些有可能会出现异常的代码,那么无谓的代码就不要往里面放 System.out.println(arr.length); System.out.println(a / b); System.out.println(arr[3]); System.out.println("===================="); //catch 可以并列写多个,捕获不同的异常 }catch (ArrayIndexOutOfBoundsException e){ System.out.println("数组角标越界异常"); }catch (ArithmeticException e){ System.out.println("除数为0的异常"); }catch (NullPointerException e){ System.out.println("空指针异常"); }catch (Exception e){ System.out.println("其他异常"); } //注意:你能明确的异常类型,尽量名确,不要一股脑的全用异常的父类捕获了 //如果你捕获的异常,是平级关系,谁前谁后无所谓,如果异常之间有继承关系,父类异常放在最后 System.out.println("下面的代码"); System.out.println("下面的代码"); } } -------- 输出: 空指针异常 下面的代码 下面的代码
注意:这里可以看到,如果一个异常被捕获,则就执行catch语句了。即:不会在处理完异常后再回到try中去执行其他语句了。
-
JDK7针对多个异常的处理方案:(一般不推荐这样使用!)
try { 可能出现问题的代码 ; }catch(异常名1 | 异常名2 | .... 变量名){ 对异常的处理方案 ; }
-
好处:就是简化了代码
-
弊端: 对多个异常的处理方式是一致的
-
注意事项: 多个异常之间只能是平级的关系,不能出现子父类的继承关系
编译期异常和运行期异常的区别:
- 概念:Java中的异常被分为两大类:编译时异常和运行时异常。
a. 所有的RuntimeException类及其子类的实例被称为运行时异常,
b. 其他的异常就是编译时异常 - 运行时异常:发生在程序运行过程当中,你可以解决,也可以不解决(例如:上述除数为0的情况。)
- 编译期异常:发生在编译期间,你必须解决,不解决程序无法运行(例如:clone()方法、SimpleDateFormat()类的parse方法等。)
- 编译期异常的处理方式:
a. 一种抛给调用者处理,谁调谁处理,简称:甩锅(甩到JVM这个调用者的时候,如果真的发生了错误,JVM就会执行默认的处理方式进行处理)
b. 另外一种:就是自己捕获处理(参考示例) - 快捷键:选中需要处理的代码块儿(编译器报错的代码),然后Alt+Enter智能纠错。
- 快捷键纠错的方式:
a. 将异常抛给调用者(throws)
b. 自己手动处理异常(try…catch) - 示例:(自己捕获处理异常)
public class MyTest { public static void main(String[] args) { // 编译期异常:非 RuntimeException及其子类,发生在编译期间,必须处理,不处理,程序无运行 //编译期的处理方式:一种抛给调用者,处理,谁调用谁处理,简称甩锅 throws //另外一种就是自己捕获处理 //编译期异常,我们自己不捕获处理,那么就抛给调用者,去处理 try { hehe(); 这里出现了编译器异常(IDEA检测为红:编译器无法通过!) } catch (ParseException e) { 手动捕获处理 e.printStackTrace(); } System.out.println("========执行下面的代码!"); } public static void hehe() throws ParseException{ 这里是hehe()这个方法选择了继续向上抛出异常! new SimpleDateFormat("yyyy-MM-dd").parse("2017:11-11"); } }
- 示例:(向上抛出)
public class MyTest { public static void main(String[] args) throws ParseException { 继续向上抛出错误(给JVM) hehe(); System.out.println("执行下面的代码!"); } public static void hehe() throws ParseException{ new SimpleDateFormat("yyyy-MM-dd").parse("2017-11-11"); } } --------- 输出: 执行下面的代码!
- 问题:为什么要设计这个编译期错误?
个人理解:在IDEA中发现红字的方法(我们称之为:编译器错误),这种错误是由于调用的这些方法利用了throws 抛出了一个错误(只不过是有可能报错的一种标志),后面对这种错误进行处理的方法不是继续向上抛,就是把它直接解决了!(相当于try里面的语句也不一定会报错,只不过用这种方式显示地告诉你,这个方法的调用可能会报错,你需要对其进行处理(而不是简单地抛出)从而不会影响到程序的正常运行,所以最好的的办法就是在main函数用try,catch把这些异常都处理了,从而不会继续甩锅给JVM。甩锅给JVM后,编译期报错的问题是消失了,但并没有解决,出了问题还是会报错,进而影响程序的运行!这才是人家将方法指定为:throws的真正目的:让你意识到这里可能会有错,需要进一步预防处理。(而不是简单地抛出!))
打印异常堆栈信息:
- Throwable的几个常见方法
a: getMessage(): 获取异常信息,返回字符串。
b: toString(): 获取异常类名和异常信息,返回字符串。
c: printStackTrace(): 获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。 - 示例:
public class MyTest { public static void main(String[] args) { //ctrl+alt+T 把你括住的一段代码,进行异常处理 try { int a = 10; int b = 0; System.out.println(a / b); } catch (Exception e) { e.printStackTrace(); System.out.println("============"); System.out.println(e.toString()); System.out.println("============"); System.out.println(e.getMessage()); } } } ------------------- 输出:(这里都是跟JVM抛出的异常效果是一样的,红色高亮显示!) java.lang.ArithmeticException: / by zero ============ java.lang.ArithmeticException: / by zero at org.westos.demo5.MyTest.main(MyTest.java:17) ============ / by zero
自定义异常类:
- 为什么需要自定义异常?
回答:因为在以后的开发过程中,我们可能会遇到各种问题,而jdk不可能针对每一种问题都给出具体的异常类与之对应。为了满足需求,我们就需要自定义异常。 - 概念:当Java提供的这些异常类,并不能完全描述我在开发中所有描述的异常,那么就需要我们自定义异常
- 格式:
自定义异常概述 需要将我们自定义的异常类纳入到我们的异常体系中
继承自Exception
继承自RuntimeException - 示例(参考:throw的概述以及和throws的区别中的示例)
throw的概述以及和throws的区别:
- throw的概述: 在功能方法内部出现某种情况,程序不能继续运行,需要进行跳转时,就用throw把异常对象抛出。
- throw 和 throws 的区别
(1)throws: 用在方法声明后面,跟的是异常类名,可以跟多个异常类名,用逗号隔开。表示抛出异常,由该方法的调用者来处理,throws表示出现异常的一种可能性,并不一定会发生这些异常
(2)throw :用在方法体内,跟的是异常对象名,只能抛出一个异常对象名。这个异常对象可以是编译期异常对象,可以是运行期异常对象。表示抛出异常,由方法体内的语句处理,throw则是抛出了异常,执行throw则一定抛出了某种异常(手动抛出异常) - 示例:(手动抛出异常)
public class MyTest { public static void main(String[] args) { //自定义异常;当Java提供的这些异常类,并不能完全描述,我在开发中所要描述的异常,那么就需要我们自定义异常 int num = 100; Scanner scanner = new Scanner(System.in); System.out.println("请输入你的取款金额"); int money = scanner.nextInt(); if (money <= num) { num -= money; } else { // System.out.println("余额不足"); //我想要抛出一个余额不足异常 throw new NoMoneyException("余额不足"); } } } ------------------------ 自定义异常类: public class NoMoneyException extends RuntimeException{ public NoMoneyException(String msg) { super(msg); } }
finally关键字的特点及作用
- 特点:
被finally控制的语句体一定会执行(前提 jvm没有停止)
特殊情况:在执行到finally之前jvm退出了(比如System.exit(0)) - finally的作用: 用于释放资源,在IO流操作和数据库操作中会见到
- 面试题:
问题:如果catch里面有return语句,请问finally的代码还会执行吗?如果会,请问是在return前还是return后?
回答:会执行, 而且在return前。
注意:如果将catch中的return更换为:system.exit(0),那么finally中的语句就不会执行了!
异常的注意事项及如何使用异常处理
- 异常在继承中的关系和问题:
(1)子类在重写父类方法时,方法内部有编译期异常,如果父类方法,没有抛出该异常,则:子类也不能抛出该异常!
(2)子类不能抛出父类(方法上)没有抛出过的异常
(3)父类有抛出异常,子类可以不抛出异常
(4) 子类抛出的异常不能比父类的大,但是可以小于等于它(在继承关系中的大小!) - 如何使用异常处理?
原则: 如果该功能内部可以将问题处理,那就用try处理。如果处理不了,那就交由调用者处理,这是用throws。
区别:
(1)后续程序需要继续运行就try
(2)后续程序不需要继续运行就throws - 如果JDK没有提供对应的异常,需要自定义异常。