什么是异常
- 异常字面翻译就是"意外"、"例外"的意思,也就是非正常情况.
- 异常本质上是程序上的错误.
程序中的异常
- 错误在我们编写程序的过程中会经常发生,包括编译期间和运行期间的错误.
- 在编译期间出现的错误有编译器帮助我们一起修正,然而运行期间的错误便不是编译器力所能及的了,并且运行期间的错误往往是难以预料的.
- 在程序运行过程中,意外发生的情况,背离我们程序本身的意图的表现,都可以理解为异常.
- 当程序在运行期间出现了异常,如果置之不理,程序可能会不正常运行、强制中断运行、造成用户数据丢失、资源无法正常释放、直接导致系统崩溃,显然这不是我们希望看到的结果.
- Java提供了异常机制来进行处理,通过异常机制,我们可以更好地提升程序的健壮性.
异常的分类
在程序开发中,异常指不期而至的各种状况.它是一个事件,当发生在程序运行期间时,会干扰正常的指令流程.
-
在Java中,通过Throwable及其子类描述各种不同的异常类型.
异常分类.png Throwable有两个重要的子类:Exception和Error
1.ErrorError是程序无法处理的错误,表示运行应用程序中较严重问题.大多数错误与代码编写者执行的操作无关,而表示代码运行时JVM(Java虚拟机)出现的问题.
例如,Java虚拟机运行错误(Virtual MachineError),当JVM不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError
这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况.
对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况.
因此我们编写程序时不需要关心这类异常.
2.ExceptionException是程序本身可以处理的异常.异常处理通常指针对这种类型异常的处理.
Exception类的异常包括checked exception和unchecked exception
2.1.unchecked exceptionunchecked exception:编译器不要求强制处置的异常.
包含RuntimeException类及其子类异常.
如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常时unchecked exception.
Java编译器不会检查这些异常,在程序中可以选择捕获处理,也可以不处理,照样正常编译通过.
2.2. checked exception编译器要求必须处置的异常.
是RuntimeException及其子类以外,其他的Exception类的子类.
如IOException、SQLException等
3.异常处理Java编译器会检查这些异常,当程序中可能出现这类异常时,要求必须进行异常处理,否则编译不会通过.
在Java应用程序中,异常处理机制为:抛出异常、捕获异常
4.抛出异常当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统.
异常对象中包含了异常类型和异常出现时的程序状态等异常信息.
运行时系统负责寻找处置异常的代码并执行.
5.捕获异常在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器.
运行时系统从发生异常的方法开始,一次回查调用栈中的方法,当异常处理器所处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器.
当运行时系统遍历调用栈而未找到合适的异常处理器,则运行时系统终止.同时,意味着Java程序的终止.
对于运行时异常、错误或可查异常,Java技术所要求的的异常处理方式有所不同.
总体来说,Java规定:对于可查异常必须捕捉、或者声明抛出.允许忽略不可查的RuntimeException和Error.
简单地说,异常总是先被抛出,后被捕捉的.
6.异常处理-
通过5个关键字来实现:try、catch、finally、throw、throws
异常处理.png
7.try-catch-finally
public void method(){
try{
// 代码段1
// 产生异常的代码段2
}catch(异常类型ex){
// 对异常进行处理的代码段3
}finally{
// 代码段4
}
}
8.使用try-catch块捕获并且处理异常
public void method(){
try{
// 代码段
}catch(异常类型ex){
// 对异常进行处理的代码段
}
}
9.多重catch块
- 引发多种类型的异常
-排列catch语句的顺序:先子类后父类
-发生异常时按顺序逐个匹配
-只执行第一个与异常类型匹配的catch语句
public void method(){
try{
// 代码段
// 产生异常
}catch(异常类型1 ex){
// 对异常进行处理的代码段
}catch(异常类型2 ex){
// 对异常进行处理的代码段
}catch(异常类型3 ex){
// 对异常进行处理的代码段
}
// 代码段
}
10.try-catch-finally
- try块后可以接零个或多个catch块
- 如果没有catch,则必须跟一个finally块
- catch、finally可选
- 语法组合:
-try-catch
-try-finally
-try-catch-finally
-try-catch-catch-finally - 在try-catch块后加入finally块
-是否发生异常都只需
-不执行的唯一情况:无异常,中断程序,退出Java虚拟机
实际应用中的经验总结
* 处理运行时异常时,采用逻辑去合理规避同时辅助try-catch处理
* 在多重catch块后面,可以加一个catch(Exception)来处理可能会被遗漏的异常
* 对于不明确的代码,也可以加上try-catch,处理潜在的异常
* 尽量去处理异常,切记只是简单地调用printStackTrace()去打印输出
* 具体如何处理异常,要根据不同的业务需求和异常类型去决定
* 尽量添加finally语句块去释放占用的资源
public void method(){
try{
// 代码段1
// 产生异常的代码段2
}catch(异常类型ex){
// 对异常进行处理的代码段3
return;
} finally{
// 代码段4
}
}
11.常见的异常类型
12.throw & throws
- 可以通过throws声明将要抛出何种类型的异常,通过throw将产生的异常抛出.
12.1.throws - 如果一个方法可能会出现异常,但没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常.
- 例如:汽车在运行时可能会出现故障,汽车本身没办法处理这个故障,那就让开车的人来处理.
- throws语句用在方法定义时声明该方法要抛出的异常类型.
public void method()throws Exception1,Exception2,...,ExceptionN {
// 可能产生异常的代码
}
- 当方法抛出异常列表中的异常时,方法将不对这些类型及其子类类型的异常作处理,而抛向调用该方法的方法,由他去处理.
throws的使用规则: - 1、如果是不可查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出.
- 2、如果一个方法中可能出现可查异常,要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误.
- 3、当抛出了异常,则该方法的调用者必须处理或者重新抛出该异常类.
- 4、当子类重写父类抛出异常的方法时,声明的异常必须是父类方法所声明异常的同类或子类.
12.2throw - throw用来抛出一个异常
例如:throw new IOException(); -
throw抛出的只能够是可抛出类Throwable或者其子类的实例对象.
例如:throw new String("出错啦");是错误的
throw.png
13.自定义异常
- 使用Java内置的异常类可以描述在编程时出现的大部分异常情况.
- 也可以通过自定义异常描述特定业务产生的异常类型
- 所谓自定义异常,就是定义一个类,去继承Throwable类或者它的子类.
14.异常链 - 有时候我们会捕获一个异常后再抛出另一个异常
- 顾名思义就是:将异常发生的原因一个传一个串起来,即把底层的异常信息传个上层,这样逐层抛出
15.Java常见异常类型及原因分析
一.NullPointerException异常
顾名思义,NullPointerException是空指针异常.但是在Java中没有指针,怎么会有空指针异常呢?
在C++中,声明的指针需要指向一个实例(通过new方法构造),这个指针可以理解为地址.在Java中,虽然没有指针,但是有引用(通常称为对象引用,一般直接说对象),引用也是需要指向一个实例对象(通过new方法构造)的,从这种意义上说,Java中的引用与C++中的指针没有本质的区别,不同的是,处于安全的目的,在Java中不能对引用进行操作,而在C++中可以直接进行指针的运算,例如book++等.
所以这里的NullPointerException虽然不是真正的空指针异常,但本质上差不多,是引用没有指向具体的实例,所以当访问这个引用的方法的时候就会产生这种异常.例如下面的代码:
String str = "这是衣蛾测试的字符串!";
System.out.println(str.length());
// 这段代码没有问题,如果改成下面的代码:
String str = null;
System.out.println(str.length());
// 就会产生NullPointerException异常了:
那么这种异常通常是如何产生的呢?比较多见的是下面的两种情况:
a) 把调用某个方法的返回值直接赋值给某个引用,然后调用这个引用的方法.在这种情况下,如果返回的值是null,必然会产生NullPointerException异常.
例如:
// 声明一个People类,并打印出该对象中的Name值.
public static void main(String[] args){
People p = null;
p.setName("张三");
System.out.println(p.getName());
}
说明:这个时候p就会出现空指针异常,因为这里只是声明了这个People类型的对象并没有创建对象,所以它的堆里面没有地址引用,切忌你哟啊用对象调用方法的时候一定要先创建对象.
b) 在方法体中调用参数的方法
这种情况下,如果调用方法的时候传递进来的值是null,也会产生NullPointerException异常.要解决这种异常,只需要检查异常出现在第几行(通常在集成开发环境中会提示用户错误发生在第几行),然后查看调用了哪个对象的方法,然后检查这个对象为什么没有赋值成功即可.
要避免程序产生这种异常,比较好的解决方法时在调用某个对象的方法时候判断这个对象是否为空,如果可能,则增加判断语句,例如上面的代码可以写成:
if(str!=null){
System.out.println(str.length());
}else{
System.out.println(0);
}
二.ClassCastException异常
从字面上看,是类型转换错误,通常是进行强制类型转换时候出的错误.
这种异常时如何产生的呢?举个例子.
// Animal 表示动物,Dog表示狗,是动物的子类,Cat表示猫,是动物的子类.
Animal a1 = new Dog();
Animal a2 = new Cat();
Dog d1 = (Dog)a1;
Dog d2 = (Dog)a2;
第三行和第四行代码基本相同,从字面意思看都是动物(Animal)强制转换为狗(dog).但是第四行代码将产生java.lang.ClassCastException.原因是把一个Cat转换成Dog不可以,而第三行中是把Dog转换成Dog,所以可以.
从上面的例子看,java.lang.ClassCastException是进行强制类型转换的时候产生的异常,
强制类型转换的前提是父类引用指向的对象的类型是子类的时候才可以进行强制类型转换,如果父类引用指向的对象的类型不是子类的时候将产生java.lang.ClassCastException异常.
遇到这样的异常的时候如何解决呢?如果你知道要访问的对象的具体类型,直接转换成该类型即可.如果不能确定类型可以通过下面的两种方式进行处理(假设对象为o):
- 通过o.getClass().getName()得到具体的类型,可以通过输出语句输出这个类型,然后根据类型进行具体的处理
- 通过if(o instanceof 类型)的语句来判断o的类型是什么.
三.ArrayIndexOutOfBoundsException 异常
这是一个非常常见的异常,从名字上看是数组下标越界错误,解决方法就是查看为什么下标越界,下面是一个错误示例:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsExcption:2at test4.nextStates
(State.java:93)at test4.State.main(State.java:478)
从这些提示信息中可以获取如下信息:
1)错误发生在93行,
2)发生错误的时候,下标的值为2
接下来分析为什么下标值是2为什么不可以就可以解决了.
四.UnsupportedClassVersionError
错误提示如下:
java.lang.UnsupportedClassVersionError:Bad version number in .class file
错误原因:
编译Java和运行Java所使用的的Java的版本不一致.例如,编译的时候使用的Java版本是6,运行时候使用的Java版本是5.
解决方案:修改运行环境的Java版本或者修改编译环境的Java版本,让两者保持一致.
五.NumberFormatExcption异常
数字转换异常,在把一个表示数字的字符串转换成数字类型的时候可能会报这个异常,原因是作为参数的字符串不是有数字组成的.
六.堆栈溢出和内存溢出
在递归调用的时候可能会产生堆栈溢出的情况,因为在递归调用的时候需要把调用的状态保存起来,如果递归的深度达到一定程度,将产生堆栈溢出的异常.
如果虚拟机的内存表较小,而程序对内存的要求比较高,则可能产生内存溢出错误.
常见异常类
异常 | 说明 |
---|---|
RuntimeException | Java.lang包中多数异常的基类 |
ArithmeticException | 算术错误,如除以0 |
IIIegalArgumentException | 方法收到非法参数 |
SecurityException | 试图违反安全性 |
ClassNotFoundException | 不能加载请求的类 |
AWTException | AWT中的异常 |
IOException | I/O异常的根类 |
FileNotFoundException | 不能找到文件 |
EOFException | 文件结束 |
IIIegalAccessException | 对类的访问被拒绝 |
NoSuchMethodException | 请求的方法不存在 |
InterruptedException | 线程中断 |