生活中我们在使用一些产品的时候,经常会碰到一些异常情况。例如,使用ATM机取钱的时,机器会突然出现故障导致无法完成正常的取钱业务,甚至吞卡;在乘坐地铁时,地铁出现异常无法按时启动和运行;使用手机时,手机突然自动关机,等等。这些产品的异常情况,有些是系统硬件的问题,但更多的情况是软件出现了问题。
使用Java开发软件时,无论程序员多么小心谨慎,产品反复测试,还是会出现程序错误的现象。所以,优秀的编程语言可以为程序员提供高效、可靠的错误处理机制,便于开发高质量的软件产品。
Java语言提供了一种面向对象异常处理机制来报告和处理错误,这种异常处理机制对异常的处理都封装在异常类中完成。程序在运行过程中,如果出现异常,则会抛出一个异常类对象。
1.异常的概念
- 在程序的设计过程中通常会遇到两种不可避免的错误:编译错误和运行错误。
- 编译错误是由于编写的程序不符合程序的语法规定而导致的语法问题。大部分的编译错误,是由于对编程语言的语法不熟悉或拼写错误引起的。如果违反了编程语言规则,就会导致程序编译错误。
- 运行错误是指能够顺利的编译通过,在程序运行过程中产生的错误。例如:操作文件时文件无法找到,没有给对象开辟内存空间等。如果出现这样的错误,程序会停止运行,但是这样的错误,不容易被发现和排除。
- java中的异常处理机制,为程序提供了清晰的异常终止和处理错误的接口。当异常发生时,发生异常的方法抛出一个封装了错误信息的异常对象。此时,程序不会继续运行,发生错误的方法也不会返回正常运行的值。异常处理机制开始搜索异常处理器来处理这种错误。
- 异常(Exception)是一个在程序运行期间发生的事件。它会终端正在运行中的程序。Java中常见的异常包括 数组下标越界(ArrayIndexOfBoundsException)、空指针异常类(NullPointerException)、算术异常(ArithmeticException)、字符串转换为数字异常(NumberFormatException)、类型不存在异常(TypeNotPresentException)等。
2.异常的基本样式
与java异常处理的相关关键字包括try、catch、throw和finally等,异常的基本样式也使用这些关键字。
- 基本样式:
methodname(){
try{
...... //可能产生异常的语句或方法程序块
}
catch(ExceptionType1 e){
...... //对异常类型ExceptionType1对象的处理
}
catch(ExceptionType2 e){
...... //对异常类型ExceptionType2对象的处理
throw(e) //再抛出这个异常
}
finally {
......//无论是否抛出异常,都需要执行的代码块
}
}
在方法内:
- try语句内是用户监视的可能出现异常的程序语句和方法。
- catch语句快内是用户对抛出异常对象的处理语句。
- throw语句块内是手动抛出一个异常给函数method()的上级执行者,
- finally语句块内,是必须在函数method()返回之前执行的代码。
【例】异常处理简单示例
3.java异常类
java异常类都是由Throwable类派生而来的。派生出的两个分支分别是Error类和Exception类。
3.1异常的层次结构
异常类是一种特殊类,表示的是一种特殊的运行错误,用于处理异常。在Java程序设计中,异常对象都是从Throwable类派生的实例。
- error用于处理在运行过程中发生的系统内部错误和资源耗尽错误。
应该尽量不出现Error类的错误。因为一旦出现了这样的错误,除了通知用户并使线程安全终止,用户也无能为力。
通常Java程序不对Error类异常进行处理。并且这类错误也很少出现。 - Exception类又分为多个分支,这些分支可以分为两类,一类是派生于RunTimeException的异常。一类是派生于IOException的异常,
RunTimeException异常是由于程序错误导致的异常,IOException异常是本来可以正常运行的程序,在操作文件删除或者文件名修改等特殊情况下发生异常而导致程序不能正常运行。
派生于Throwable类的常用异常
被终止异常(InterruptedException) | 某个线程处于长时间的等待、休眠或其他暂停状态,其他线程通过Thread类的interrupt()方法终止该线程时抛出的异常 |
方法不存在异常(NoSuchMethodException) | 访问某个类的不存在方法时抛出的异常 |
找不到类异常(ClassNotFoundException) | ClassNotFoundException 表示在尝试加载类时找不到该类。这可能发生在运行时,通常是由于类路径不正确或缺少相应的类文件 |
不支持克隆异常(CloneNotSupportedException) | 当没有实现Cloneable接口或者不支持克隆方法时调用其clone方法抛出该异常 |
派生于RuntimeException类的常用异常
算术运算异常(ArithmeticException) | 整数除0时抛出这个异常 |
类造型异常(ClassCstException) | |
数组越界异常(ArrayIndexOutofBoundsofException) | |
数组存储异常(ArrayStoreException) | |
访问空指针(NullPointException) |
由IOException类派生的异常
文件已结束异常(EOFException) | |
文件未找到异常(FileNotFoundException) |
3.2异常处理方法
【例】
4.异常捕获
企业针对产品的异常,会使用一些关键字进行描述和处理。如:产品平均无故障时间、产品故障率、召回等。java的异常处理是通过5个关键词来实现的。分别是try catch throw throws finally 。
其中,try用来存放可能发生一个或多个异常的Java语句,catch语句块用来捕获try语句块抛出的异常。finally语句块用来存放释放的资源和句柄等。
4.1 异常捕获处理语块
为了捕获异常,需要使用catch语句块来配合try语句块。
try语句块用来指定可能发生异常的程序块,配合使用的catch语句块用来处理try语句块抛出的异常。
try语句块内部如果出现异常,那么程序将跳过try语句块中出现的异常语句之后的其它代码,直接进入catch语句块内执行代码
【例】
try-catch 语句块可以嵌套使用。当try语句块遇到异常时,Java会检查与try异常程序块相配合的catch捕获处理中是否存在能处理该异常的catch语句块。如果存在,则该异常将被改catch语句块捕获并处理,否则,该异常将被上一级的try-catch语句块捕获。
【例】
package example;
public class TrynestdealException {
public static void main(String[] args) {
tryCatch(); //调用静态try-catch函数
}
//静态tryCatch()函数
static void tryCatch() {
try { //外层try块
try { //内层try块
System.out.println("执行里层try块\n");
int inte = Integer.parseInt("pencil");//字符串转化为整数
System.out.println(inte);//输出整数
}
catch(ArrayIndexOutOfBoundsException e) { //捕获异常
System.out.println("执行里层catch块\n");
System.out.println("捕获ArrayIndexOutOfBoundsException异常" + e.getMessage());
}
catch(ArithmeticException e) { //捕获异常 ArithmeticException
//捕获异常后输出异常信息
System.out.println("执行里层catch2块\n");
System.out.println("捕获ArithmeticException异常" + e.getMessage());
}
}
catch(ClassCastException ie) { //捕获异常ClassCastException
System.out.println("捕获外层异常ClassCastException" + ie.getMessage());
}
catch(NumberFormatException ie) {
System.out.println("捕获外层异常NumberFormatException" + ie.getMessage());
}
}
}
4.2 必须执行语句块
有时候我们要去某地,有多条交通路线,其中有一条是必须走的,也就是通常所说的交通要道。在try-catch-finally结构中,finally语句块就是交通要道。
在try-catch语句块中定义了finally语句块。这时无论try-catch语句块是否抛出和捕获异常,程序都将执行finally语句块。
【例】
package example;
public class ExceptionFinally {
public static void main(String[] args) {
int num[] = new int[5];
try {
for(int i = 0;i<5;i++) {
num[i]= i+1;
for(int j = 0;i<=i;j++) {
num[i]+= num[j];
}
}
for(int i = 0;i<6;i++) {
System.out.println(num[i]);
}
}catch(Exception e){
e.printStackTrace();
}finally {
System.out.println("finally块是必经之路");
}
}
}
在操作文件的时候,无论操作是否成功,必须要在程序运行后关闭文件。同样,如果将释放内存的工作放到finally语句块内,那么程序不会由于未释放资源而导致内存泄漏
4.3 必须执行语句块的意外
finally语句块通常情况下总是会被执行,但在下面这几种情况下finally语句块不会被执行
- 电脑断电关机
- 在finally语句块之前的语句中含有SYSTEM.EXIT(0)语句
- 在finally语句块执行过程中抛出异常
- 程序所属线程死亡,程序在未执行finally语句块之前就退出
5.异常抛出
异常捕获语句块都存在于产生异常的方法中,异常抛出可以将产生的异常抛给调用它的方法。
如果某方法的try语句块中抛出的异常没有与之匹配的catch块,或者catch语句块已经不活了异常但不想立即处理,就可以将异常抛给调用它的上一级方法。
5.1 异常抛出的概念
- 在方法声明的时候,必须指定方法中可能产生的异常,使这个方法的调用者必须处理这些可能的异常。调用者可以自己处理这种异常,也可以将这种异常派送给它的调用者,因此异常可以逐级上溯。直到找到可以处理他的代码为止。
- 抛出异常必须做的两部分工作是在方法的声明中使用throws语句指定它所抛出的异常和使用throw语句抛出异常。
- 在方法中声明可能出现的异常是为了告诉编译器该方法可能产生的异常,因而要求该方法的使用者必须考虑处理方法抛出的异常,以增强程序的健壮性。
- 任何有异常抛出的方法都有可能导致程序的崩溃,如果使用者没有对方法抛出的异常进行处理,则会导致程序运行结束。
- 通过throw可语句可以明确的抛出一个异常,而在方法中使用throws声明次方法将抛出某种类型的异常。
5.2 throw
使用throw语句时必须声明一个异常对象,这个对象时Throwable派生类的对象
ThrowableInstance一定是Throwable类或Throwable子类的一个对象。简单类型(int、char)以及非Throwable类(String或Object)不能用作异常
【例】
package example;
public class UseThrowExample {
static void demoproc(String v1) {
try {
if(v1 == null) {
//抛出NullPointerException异常
throw new NullPointerException("Throw Example");
}
}catch(NullPointerException e){
System.out.println("输入的参数为空");
throw e;
}
}
public static void main(String[] args) {
try {
String string = null;
demoproc(string); //调用方法
}catch(NullPointerException e) {
System.out.println("异常情况为"+ e);
}
}
}
5.3 throws
如果一个方法可以导致一个异常但不处理它,那么他必须在方法声明时包含一个throws子句,以使方法的调用者库保护他们自己而不发生异常。
throws子句列举了一个方法可能引发的所有异常类型,一个方法引发的所有其他类型的异常必须在throws子句中生明。如果不这样做,会导致编译错误。
package example;
import java.io.IOException;
public class ThrowsUsingExample {
public static void main(String[] args) {
try {
System.out.println("请输入字符");
String s = getInput();
System.out.println("输入字符为:");
System.out.println(s);
}catch(IOException e) {
System.out.println("产生异常"+e.getMessage());
}
}
private static String getInput() throws IOException{
char[] buffer = new char[10];
int count = 0;
boolean flag = true;
while(flag) {
buffer[count] = (char)System.in.read();
if(buffer[count]=='\n') {
flag = false;
}else {
count++;
}
if(count >=10) {
IOException aException = new IOException("buffer is null");
throw aException;
}
}
return new String(buffer);
}
}
5.4 抛出异常的步骤
我们在使用手机软件时,经常会遇到出现异常、卡顿或者异常退出的情况,这主要和开发软件异常处理或者手机内存不足等情况有关。程序抛出异常,一般分为以下三个步骤:
- 确异常类
- 创建异常类的实例
- 抛出异常
package example;
import java.sql.SQLException;
public class ExceptionThreestep {
//可能会抛出异常的方法
public static void throwException() throws SQLException{
try {
throw new SQLException("SQLException1");
}catch(SQLException e) {
System.out.println("throwException:()抛出异常"+e.getMessage());
//捕获异常暂不处理,抛出异常
throw new SQLException("SQLException1");//抛出异常SQLException
}
}
public static void main(String[] args) {
try {
throwException();
}
catch(SQLException e) {
System.out.println("main()捕获SQLException:"+e.getMessage());
}
}
}
6.自定义异常
除了Java中提供的一些异常类,还可以根据自己的需求定义异常类。
6.1 创建用户自定义异常
package example;
class UserOwnException {
//声明UserOwnException无参数构造方法
public UserOwnException() {
}
//声明UserOwnException带有String类型参数的构造方法
public UserOwnException(String smg) {
super();//调用父类的构造方法
}
}
public class UsingUserException {
public static void main(String[] args) {
try {
execute(); //调用execute()方法
}catch(UserOwnException e) {
System.out.println(e.getMessage());
}
}
static void execute() throws UserOwnException{
try {
throw new Exception();//抛出异常
}
//捕获Exception类
catch(Exception e) {
//捕获后,直接抛出用户自定义异常类
throw new UserOwnException("自定义异常抛出"+e.getMessage());
}
}
}
6.2 捕获运行时异常
创建自己的异常类,可以方便的处理和捕获某些运行时的异常,并重新抛出一个新的异常提供给它的调用者捕获。
运行时异常属于未检查异常,如果在方法的throws子句中声明了此异常,那么方法的调用者就可以不处理此异常。
package example;
class MyArithException extends Exception{
MyArithException(){
}
MyArithException(String msg){
super(msg); //调用父类的构造方法
}
}
public class CatchRunException {
public static void main(String[] args) {
try {
executeRuntime(); //调用executeRuntime()方法
}catch(MyArithException e){
System.out.println("捕获运行时异常"+e.getMessage());
}
}
static void executeRuntime() throws MyArithException{
try {
int i = 1/0; //抛出运行时异常 ArithmeticException
}
catch(ArithmeticException e) { //捕获异常ArithmeticException
/*
* 在这里捕获运行时异常ArithmeticException 不处理,
* 而是抛出一个新的MyArithException 异常
* 因为运行时异常不在方法声明的throws子句中声明
*
* */
throw new MyArithException(e.getMessage());
}
}
}
7.拓展训练
7.1 训练一:强制类型转换异常
【拓展要点:ClassCastException异常】
当程序运行时,有时需要对不同的类型进行类型转换。在进行类型转换的过程中,当java虚拟机发现这两个类型不兼容而无法完成转换时,就会出现ClassCastException异常
7.2 训练二:找不到类型时的异常
【拓展要点:ClassNotFoundException异常】
当应用试图根据字符串形式的类名构造类,而在遍历CLASSPATH之后做不到对应名称的.class文件时,将抛出ClassNotFoundException异常。
8.技术解惑
8.1 如何优雅地处理异常
异常是一种强大的调试手段,是因为回答了以下三个问题:
- 为什么出了错 -------- 异常类型回答了“什么”被抛出
- 在哪里出了错 ------- 异常堆栈回答了“在哪里”被抛出
- 为什么出错 ------- 异常信息回答了“为什么”被抛出
三个原则可以帮助我们在调试过程中最大限度的使用好异常:
-具体明确 - 提早抛出 -延迟捕获
8.2 throw与throws的区别
(1)throw表示语句抛出一个异常。
一般用于程序出现某种逻辑时,程序员主动抛出某种特定类型的异常。
(2)throws是方法可能抛出异常的声明(在声明方法时,表示该方法可能要抛出异常)
当某个方法可能会抛出某种异常时,用throws声明可能抛出的异常,然后交给上层调用它的方法程序处理。
throw与throws关键字的比较
- throws 出现在函数头部,throws出现在函数体中
- throws表示出现异常的一种可能性,并不一定会发生异常;而throw表示抛出异常,执行throw一定会抛出某种异常对象
- 两者都是消极处理异常的方式,而是抛出或者可能抛出异常。但是不会由函数去处理异常,而是由函数的上层调用进行处理