概念
Java中的异常指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。它并不是语法错误或者逻辑错误。
Java程序在执行过程中所发生的异常事件可分为两类:
- Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError(栈溢出)和OOM(内存溢出)。一般不编写针对性的代码进行处理。
- Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:
空指针访问、试图读取不存在的文件、网络连接中断、数组角标越界等。Exception又分为两大类:运行时异常[程序运行时,发生的异常]和编译时异常[编程时,编译器检查出的异常]。
异常的体系结构
Error和Exception的共同父类是Throwable类。
广义上的异常分为Error和Exception,而我们常说的是狭义上的异常Exception
广义上的异常分为检查异常(checked exception)和非检查异常(unchecked exception),其中的检查和非检查是对于javac而言的。
- 检查异常就是编译器要求必须处理(catch或抛出)的异常,编译器在编译时会对这类异常进行检查,否则编译不会通过。这样的异常一般是由程序的运行环境导致的。因为程序可能被运行在各种未知的环境下,而程序员无法干预用户如何使用他编写的程序,于是程序员就应该为这样的异常时刻准备着。除了运行时异常和Error的都是检查异常(也即编译异常都是检查异常),如SQLException , IOException,ClassNotFoundException 等。
- 非检查异常即编译器不要求强制处置的异常,虽然可能出现错误,但是编译器不会在编译的时候检查。 包括RuntimeException与其子类,以及错误(Error),对于这类异常,我们一般都进行修改代码来进行处理,因为这样的异常发生的原因多半是代码写的有问题。当然也可以进行catch或抛出,或干脆不处理
运行时异常(RuntimeException)
运行时异常编译器检查不出来。一般是指编程时的逻辑错误,是程序员应该避免其出现的java.lang.RuntimeException类及它的子类都是运行时异常,常见的运行时异常包括:
- NullPointerException 空指针异常,当应用程序试图在需要对象的地方使用 null 时,抛出该异常
package com.dhw.exception;
public class NullPointerExceptionTest {
//当应用程序试图在需要对象的地方使用 null 时,抛出该异常
public static void main(String[] args) {
String str = null;
System.out.println(fun(str));
}
static int fun(String str){
return str.length();
}
}
- ArithmeticException 数学运算异常,当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例,注意:浮点类型(float和double)除以0时不会抛出异常,因为Java的浮点运算是基于 IEEE-754 标准
package com.dhw.exception;
public class ArithmeticExceptionTest {
//当出现异常的运算条件时,抛出此异常。例如,一个整数除以零时,抛出此类的一个实例
//注意:浮点类型(float和double)除以0时不会抛出异常,因为Java的浮点运算是基于 IEEE-754 标准
public static void main(String[] args) {
int num1 = 1;
double num2 = 2.0;
float num3 = 3.0f;
System.out.println(num3 / 0);
System.out.println(num2 / 0);
System.out.println(num1 / 0);
}
}
- ArrayIndexOutOfBoundsException 数组下标越界异常,非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引
- ClassCastException 类型转换异常,当试图将对象强制转换为不是其运行类型或其父类的类型时,抛出该异常。
package com.dhw.exception;
//当试图将对象强制转换为不是其运行类型或其父类的类型时,抛出该异常。
public class ClassCastExceptionTest {
public static void main(String[] args) {
A i1 = new C();
B i2 = (B)i1; // 正确,因为对象实例是C,编译类型是A,
// 这么向下转型后相当于把编译类型变为B,运行类型还是C,而B是C的父类,还是一个向上转型,
// 只是把他的编译类型向下转了一级
C i3 = (C)i2;
A i4 = new B();
C i5 = (C) i4;// 抛出异常,运行类型是B,不能把他强转为C类型,因为C类型不是B类或其父类
}
}
class A{}
class B extends A{}
class C extends B{}
- NumberFormatException 数字格式不正确异常,当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常
package com.dhw.exception;
public class NumberFormatExceptionTest {
//当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常
public static void main(String[] args) {
System.out.println(Boolean.parseBoolean("abc"));//输出false
String name = "abc";
//将 String 转成 int
//使用异常我们可以确保输入是数字
int num = Integer.parseInt(name);//抛出 NumberFormatException
System.out.println(num);//
}
}
编译时异常
RuntimeException以外的异常就是编译时异常,类型上都属于Exception类及其子类。必须进行处理,否则程序就不能编译通过。常见的编译时异常包括:
- SQLException//操作数据库时,查询表可能发生异常
- IOException//操作文件时,发生的异常
- FileNotFoundException//当操作一个不存在的文件时,发生异常
- ClassNotFoundException//加载类,而该类不存在时,异常
- EOFException//操作文件,到文件未尾,发生异常
- lllegalArguementException //参数异常
异常处理
Java中可以使用异常处理机制来解决异常的问题,防止程序因异常而退出,从而保证程序的健壮性
处理异常主要有以下两种方式:
- 1、使用try…catch…finally语句块捕获异常并处理
- 2、在函数签名中使用throws 声明,将异常抛出,交给函数调用者caller去解决,最顶级的处理者是jvm,它处理的方式是打印异常信息并退出程序
try-catch处理方式
语法:
try{
//可能产生异常的代码
}
catch( ExceptionName1 e ){
//当产生ExceptionName1型异常时的处置措施
}
catch( ExceptionName2 e ){
//当产生ExceptionName2型异常时的处置措施
}
......
[ finally{
//无论是否发生异常,都无条件执行的语句,比如关闭连接,释放资源等
//没有finally块,语法也能通过
} ]
try-catch处理规则:
- 如果没有出现异常,则执行try块中所有语句,不执行catch块中语句,如果有finally,最后还需要执行finally里面的语句
- 如果出现异常,则try块中异常发生后,try块剩下的语句不再执行。将执行catch块中的语句,如果有finally,最后还需要执行finally里面的语句
try-catch-finally处理异常中关于return的问题(重要,难点)
package com.dhw.exception;
public class TryCatchFinallyDetail {
public static void main(String[] args) {
System.out.println(fun());
}
static int fun(){
int i = 1;
i++;
try {
String[] names = new String[3];
if (names[1].equals("tom")){//空指针异常 抛出NullPointerException
System.out.println(names[1]);
}else {
names[1]="tom";}
return 1;
} catch (ArrayIndexOutOfBoundsException e) {
return 2;
} catch (NullPointerException e) {
return i++;//i=2 在return前会使用一个临时变量保存return的值 temp = i (i = 2)
//return前必须执行finally块
//执行完finally块中的语句后再返回保存的临时变量temp=2
} finally {
++i; //i=4
System.out.println("i=" +i);//i=4
}
}
}
使用try-catch-finally处理异常时,若出现异常,且catch块中有return语句,会
- 先执行return语句中的运算,再将运算完后的值保存在一个临时变量temp中
- 随后执行finally块中的语句,若finally块中有return语句,则在finally块中return
- 若finally块中没有return语句,则返回到catch块中返回,return的值为临时变量temp的值
注意:若finally块和catch块中都无return语句,会出现error提示,过不了编译(throw语句和return类似处理)
throws处理方式
概念:
如果一个方法中可能产生某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者(也是方法)负责处理。
在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。
public void method() throws ArithmeticException, NullPointerException{
//函数体
}
细节:
- 对于运行时异常,程序中如果没有处理,默认就是 throws
- 子类重写的方法,所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常类型的子类型
- 如果有 try-catch,相当于处理异常,就可以不必 throws
- 如果方法抛出了编译异常,则该方法的调用者必须进行处理,否则报错,因为编译异常是必须要处理的
package com.dhw.exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class ThrowsTest {
public static void main(String[] args) throws FileNotFoundException {
//如果方法抛出了编译异常,则该方法的调用者必须进行处理,否则报错,因为编译异常是必须要处理的
fun2();
}
public static void fun1(){
int num1 = 0;
int num2 = 1;
int num3 = num2/num1;
//对于运行时异常,程序中如果没有处理,默认就是 throws
//一直throws到JVM中,JVM的处理方式就是打印错误并终止程序
}
public static void fun2() throws FileNotFoundException {
fun1();
FileInputStream fis = new FileInputStream("d://aa.txt");
}
}
自定义异常
在Java中还可以自己设计异常类,用于描述程序中的错误信息。只需自定义一个异常类,继承Exception或RuntimeException即可。如果继承Exception,属于编译异常;如果继承RuntimeException,属于运行异常(一般来说,继承RuntimeException)
package com.dhw.exception;
public class CustomException {
public static void main(String[] args) {
int age = 180;
if(!(age >= 18 && age <= 120)) {
//使用throw生成一个异常,这个异常是RuntimeException,默认作抛出处理
//这里我们可以通过构造器,设置信息
throw new AgeException("年龄需要在 18~120 之间");
//throw new AgeException();
//抛出的Excption类型的异常是编译异常
//throw new Exception();
}
System.out.println("你的年龄范围正确.");
}
}
class AgeException extends RuntimeException {
public AgeException(String message) {//构造器
super(message);
}
public AgeException(){}
}
throws和throw的区别
图源自韩顺平《循序渐进学Java》
代码示例(重要):
package com.dhw.exception;
public class ExceptionExercise {
static void funA(){
try {
System.out.println("funA");
//手动抛出这个异常,抛出异常后方法会在这里终止,但终止之前要执行finally语句
throw new RuntimeException("funA create an exception!");
//System.out.println("test");
}finally {
System.out.println("use funA finally");
}
}
static void funB(){
try {
System.out.println("funB");
}finally {
System.out.println("use funB finally");
}
}
public static void main(String[] args) {
try {
funA();
}catch (Exception e){//捕获到手动throw出的异常,进行处理,将其信息打印出来
System.out.println(e.getMessage());
}
funB();
}
}
可以用throw来手动制造一个异常。
其他Java笔记:Java学习笔记——内部类(InnerClass)浅析 Java学习笔记——多态(polymorphic)