异常学习笔记
一、异常概述
异常是Java程序在运行过程中出现的错误情况。
二、异常的分类
如图1所示。
图1
Throwable类:所有异常的超类。
Error类及其子类继承Throwable类,严重错误问题,这种异常无法解决,程序需要重新设计。
Exception类及其子类继承Throwable类,叫做编译期异常,这种异常必须要处理,不然编译通不过。
RuntimeExcecptin类及其子类继承Exception类,是一个特例,被称为编译器不检查异常,也叫运行期异常。这种异常不用处理编译会通过,不过这样的程序会有安全隐患。大部分的RuntimeExcecptin都是因为程序逻辑的问题,可通过提高代码的严谨性来解决。(参阅深入浅出Java 第二版)。
三、常见运行期异常
1、角标越界异常
测试案例:
publicclass RuntimeDEmo {//1
publicstatic void main(String[]args) {//2
System.out.println("数组下标越界异常");//3
intarr[]=newint[5]; //容许5个元素//4
arr[10]=7;//下标值超出所容许的范围//5
System.out.println("end of main() method !!");//6
}
}
输出结果:
数组下标越界异常
Exception in thread "main"java.lang.ArrayIndexOutOfBoundsException:10 at
RuntimeDEmo.main(RuntimeDEmo.java:5)
由结果可知:当程序运行到第5行时,JVM发现该行代码有逻辑错误,于是终止程序继续向下执行,并在控制台打印出异常信息,所以第6行代码不会执行。可以通过判断数组下标是否越界来避免该异常出现。
2、空指针异常
测试案例:
publicclass null1 {//1
publicstatic void main(String[]args) {//2
System.out.println("空指针异常");//3
int[]arr = {1,2,3};//4
arr =null;//5
System.out.println(arr[0]);//6
}
}
输出结果是:
空指针异常
Exception in thread"main"java.lang.NullPointerException at
null1.main(null1.java:6)
由输出结果可知:当程序运行到第6行时,JVM发现该行代码有逻辑错误,于是终止程序继续向下执行,并在控制台打印出异常信息。可以通过判断数组是否为null来解决
3、算数异常。
测试案例:
publicclass ExceptionDemo2 {//1
publicstatic void main(String[]args) {//2
System.out.println("算数异常");//3
intx = 5;//4
inty = 0;//5
System.out.println(x/y);//6
System.out.println("继续下面操作");//7
}
}
输出结果:
算数异常
Exception in thread "main"java.lang.ArithmeticException:/by zero atExceptionDemo2.main(ExceptionDemo2.java:6)
由输出结果可知:当程序运行到第6行时,JVM发现该行代码有逻辑错误,于是终止程序继续向下执行,并在控制台打印出异常信息。所以第7行代码不会执行。可以通过if语句来判断被除数是否为零从而避免该异常。
四、为什么要处理异常
因为java程序在处理抛出的异常同时不会降低程序运行的速度,所以在Java程序设计时,应充分地利用Java的异常处理机制,以增进程序的稳定性及效率。
注意事项:
1、 在java中所有的异常都封装成一个类。
2、 除了RuntimeExcecptin这种特例外,编译器会检查Exception所有子类。
3、 编译器不会检查RuntimeExcecptin类型的异常,RuntimeExcecptin不需要声明或被包含在try/catch的块中(虽然也可以这么做)。
4、 try/catch是用来处理真正的异常,而不是程序的逻辑错误。
五、异常的处理方式:
方式1:捕获异常,自己处理。
try {
可能出现异常的代码
} catch (异常类名 对象名) {
异常的处理代码
}finally {
一定要执行的代码(特殊情况不会执行[JVM退出了])
}
案例1:try…catch单个异常处理
publicclass ExceptionDemo3 {
publicstatic void main(String[]args) {
try{
//可能会出异常的代码
System.out.println(3/0);
} catch(ArithmeticExceptione) {
//异常处理代码
System.out.println("除数不能为0");
}
System.out.println("over");
}
}
输出结果:
除数不能为0
Over
案例2:多个异常处理方式1:每一个异常单独处理
public class ExceptionDemo4 {
public static void main(String[] args) {
intx = 5;
inty = 0;
try{
System.out.println(x/y);//算数异常
} catch(ArithmeticExceptione) {
System.out.println("除数不能为0");
}
int[]arr = {1,2,3};
try {
System.out.println(arr[3]);//数组下标越界异常
} catch(ArrayIndexOutOfBoundsExceptione) {
System.out.println("数组角标越界异常");
}
arr = null;
try{
System.out.println(arr[0]);//空指针异常
} catch(NullPointerExceptione) {
System.out.println("空指针异常");
}
System.out.println("over");
}
}
输出结果:
除数不能为0
数组角标越界异常
空指针异常
Over
案例3:多个异常处理方式2:所有的异常,统一处理 (推荐)
public class ExceptionDemo4 {
public static void main(String[] args) {
intx = 5;
inty = 5;
int[]arr = {1,2,3};
try {
System.out.println(x/y);//算数异常
System.out.println(arr[3]);//数组下标越界异常
} catch(ArithmeticExceptione) {
System.out.println("除数不能为0");
} catch(ArrayIndexOutOfBoundsExceptione) {
System.out.println("数组角标越界");
}
System.out.println("over");
}
}
输出结果:
1
数组角标越界
Over
注意:当try语句中有多条可能出现异常代码时,如果第一条代码出现异常那么它下面的所有可能出现异常的代码都不会执行。catch语句中的异常对象的顺序是“从小到大”或“平级”关系排列的,不能把Exception类的对象放在catch语句的第一行,该对象只能出现在catch语句的最后一行。
案例4:多个异常处理方式3:JDK1.7新格式,一个try对应一个catch语句,但是
在catch中包含多个异常对象。
public class ExceptionDemo5 {
public static void main(String[] args) {
int x = 5;
int y = 5;
int[] arr = {1,2,3};
try {
System.out.println(x/y);//算数异常
System.out.println(arr[3]);//数组下标越界异常
} catch(ArithmeticException | ArrayIndexOutOfBoundsExceptione) {
System.out.println("程序出错");
}
System.out.println("over");
}
}
输出结果:
1
程序出错
Over
注意:这种格式的错误信息不够明显,比较笼统。
Throwable的几个常见方法
1、getMessage() //获取异常信息,返回字符串。
2、toString() //异常类名和异常信息,返回字符串。
3、printStackTrace()//获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。
4、printStackTrace(PrintStream s)通常用该方法将异常内容保存在日志文件中,以便查阅。
测试案例:
publicclass ExceptionDemo {
public static void main(String[] args) {
int x = 5;
int y = 0;
try {
System.out.println(x/y);//可能出现错误的代码
} catch (Exceptione) {
System.out.println(e.getMessage() );//异常原因
System.out.println(e.toString());//异常名称+异常原因
e.printStackTrace();//异常名称+异常原因+异常位置
}
System.out.println("over");
}
}
输出结果:
/ by zero
java.lang.ArithmeticException: / by zero
atcn.itcast_02_throwable_method.ExceptionDemo.main(ExceptionDemo.java:26)
java.lang.ArithmeticException: / by zero
over
方式2:抛出异常,交给调用者解决
格式:
修饰符 返回值类型 方法名(参数列表) throws 异常类名 {
code...
}
案例:
publicclass ExceptionDemo {
public static void main(String[] args){
//主方法解决method()方法中出现的异常(也可以不解决抛给JVM处理)
try {
method();
} catch (ParseExceptione) {
e.printStackTrace();//打印异常信息
}
System.out.println("Over");
}
//method()方法自己不解决可能出现的异常,把异常抛给调用者
public static void method() throws ParseException {
SimpleDateFormatsdf = new SimpleDateFormat("yyyy年MM月dd日");
//Date date = sdf.parse("2015年04月20日");//正确格式
Date date = sdf.parse("2015-04-20");//错误格式,运行出错
System.out.println(date);//打印日期
}
}
输出结果:
java.text.ParseException: Unparseable date: "2015-04-20"
at java.text.DateFormat.parse(UnknownSource)
at cn.itcast_03_throws.ExceptionDemo.method(ExceptionDemo.java:32)
at cn.itcast_03_throws.ExceptionDemo.main(ExceptionDemo.java:23)
Over
注意:异常类名可以是多个,使用","逗号分隔,例如:
修饰符 返回值类型 方法名() throws 异常类名1,异常类名2,异常类名3,异常类名4... {}。
方法3:自定义异常:
格式:与普通的异常使用方式一样
class自定义异常类 extends Exception {
//空参数构造方法
public异常类名() {
super();
}
//String参数的构造方法
public异常类名(String s) {
super(s);
}
}
throw 抛出自定义异常
测试案例:
//测试类
public class ExceptionDemo {
publicstatic void main(String[]args) {
//int money = 400;//取400,正常情况
int money = 1000;//取1000,异常情况
try {
ATM.outMoney(money);//取钱方法,可能会出现余额不足异常
} catch (ATMException e) {
e.printStackTrace();//异常处理,打印异常信息
} finally {
System.out.println("请取走您的卡");//一定会执行
}
}
}
//自定义异常,只需写出两个构造函数即可
publicclass ATMExceptionextends Exception {
//空参构造方法
public ATMException() {
super();
}
//字符串参数构造方法
publicATMException(Stringmessage) {
super(message);
}
}
//取钱类,取钱方法可能会出现异常情况,所有需要抛出该方法对应的自定义异常类
public class ATM {
public static void outMoney(intmoney) throws ATMException {
if (money <= 500) {
System.out.println("请取走您的钱");
} else {
//ATM余额不足
throw new ATMException("ATM中余额不足");
}
}
}
输出结果:
cn.itcast_06_Exception.ATMException: ATM中余额不足
at cn.itcast_06_Exception.ATM.outMoney(ATM.java:9)
atcn.itcast_06_Exception.ExceptionDemo.main(ExceptionDemo.java:12)
请取走您的卡
六、finally关键字
finally关键字的用法
try{
可能出现异常的代码
} catch (异常类名 异常对象名) {
该异常处理的代码
} finally {
一定要执行的代码 (特殊情况会不执行)
主要功能:释放资源
}
finally的特点:被finally控制的语句体一定会执行,特殊情况:在执行到finally之前jvm退出了,比如System.exit(0)。
finally的作用:用于释放资源,在IO流操作和数据库操作中会见到
测试案例1:finally的用法
public class ExceptionDemo {
public static void main(String[] args) {
try {
System.out.println(3/0);//算数异常
} catch (Exceptione) {
e.printStackTrace();//打印异常信息
//System.exit(0);//特殊情况finally不执行,程序退出
} finally {
System.out.println("finally");
}
System.out.println("try...catch Over");
}
}
输出结果:
java.lang.ArithmeticException: / by zero
at cn.itcast_04_finally.ExceptionDemo.main(ExceptionDemo.java:24)
finally over
try...catch Over
测试案例2:如果catch里面有return语句,finally的代码还会执行吗?如果会,请问是在return前还是return后。
会执行,在中间执行
public class ExceptionDemo2 {
public static void main(String[] args) {
int x = method(10);
System.out.println("main:x=" +x);
}
public static int method(intx) {
try {
x = 20;
System.out.println(x/0);
//异常后面的代码执行不到
x = 30;
} catch (Exceptione) {
e.printStackTrace();//打印异常信息
x = 40;
returnx;
} finally {//finally语句一定会执行
x = 50;
System.out.println("finally:x=" +x);
//return x;
}
System.out.println("method:x=" +x);//不会执行
returnx;//不会执行
}
}
输出结果:
java.lang.ArithmeticException: / by zero
at cn.itcast_04_finally.ExceptionDemo2.method(ExceptionDemo2.java:15)
at cn.itcast_04_finally.ExceptionDemo2.main(ExceptionDemo2.java:9)
finally:x=50
main:x=40
由输出结果可知,虽然catch语句中有return语句,但finally语句还是会执行。当执行到catch语句的returnx时,此时x=40程序没有结束接着执行finally语句,当finally语句执行结束后,才正真的执行catch语句中的returnx语句,这时把X=40返回给调用函数。
七、常见问题
1、final,finally,finalize 三者的区别?
final: 最终的意思,它修饰的类是最终类,不能被其他类所继承;它修饰的方法是最终方法,子类不能重写该方法;它修饰的变量为常量,值不能改变。
finally: 是异常处理语句中的一部分try..catch..finally, 用来释放资源,如文件的关闭,对象设置为null等操作
finalize: Object类中方法当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
2、throw 与 throws 的区别?
throws:定义功能方法时需要把出现的问题暴露出来让调用者去处理。那么就通过throws在方法上标识。
用在方法声明后面,跟的是异常类名,可以跟多个异常类名,用逗号隔开。
表示抛出异常,由该方法的调用者来处理
Throws可能会抛出异常,也可能不抛出异常。
throw:表示在方法内部出现了某种情况时,程序无法继续运行,需要进行跳转时,就用throw把异常对象抛出。
throw用在方法体内,跟的是异常对象
只能抛出一个异常对象
表示抛出异常,由方法体内的语句处理
throw是一定抛出了某种异常。
3、异常使用的注意事项:
1)子类重写父类方法时,子类的方法必须抛出相同的异常或父类异常的子类.子类不能比父类“更坏”。
2)如果父类抛出了多个异常,子类重写父类时,只能抛出相同的异常或者是他的子集,子类不能抛出父类没有的异常
3)如果被重写的方法没有异常抛出,那么子类的方法绝对不可以抛出异常,如果子类方法内有异常发生,那么子类只能try,不能throws