目录
8.2.2异常---Exception1)运行时异常(RuntimeException):
第8章异常处理
8.1异常概述
Java 中的异常又称为例外,是一个在程序执行期间发生的事件,它中断正在执行程序的正常指令流。为了能够及时有效地处理程序中的运行错误,必须使用异常类,这可以让程序具有极好的容错性且更加健壮。
在 Java 中一个异常的产生,主要有如下三种原因:
Java 内部错误发生异常,Java 虚拟机产生的异常。
编写的程序代码中的错误所产生的异常,例如空指针异常、数组越界异常等。
通过 throw 语句手动生成的异常,一般用来告知该方法的调用者一些必要信息。
Java 通过面向对象的方法来处理异常。在一个方法的运行过程中,如果发生了异常,则这个方法会产生代表该异常的一个对象,并把它交给运行时的系统,运行时系统寻找相应的代码来处理这一异常。
我们把生成异常对象,并把它提交给运行时系统的过程称为拋出(throw)异常。运行时系统在方法的调用栈中查找,直到找到能够处理该类型异常的对象,这一个过程称为捕获(catch)异常。
import java.util.Scanner;
public class Test01 {
public static void main(String[] args) {
System.out.println("请输入您的选择:(1~3 之间的整数)");
Scanner input = new Scanner(System.in);
int num = input.nextInt();
switch (num) {
case 1:
System.out.println("one");
break;
case 2:
System.out.println("two");
break;
case 3:
System.out.println("three");
break;
default:
System.out.println("error");
break;
}
}
}
8.2异常的分类
Throwable 类是所有异常和错误的超类,下面有 Error 和 Exception 两个子类分别表示错误和异常
Exception 类用于用户程序可能出现的异常情况,它也是用来创建自定义异常类型类的类。
Error 定义了在通常环境下不希望被程序捕获的异常。一般指的是 JVM 错误,如堆栈溢出。
Error(错误)和 Exception(异常)都是 java.lang.Throwable 类的子类,在 Java 代码中只有继承了 Throwable 类的实例才能被 throw 或者 catch。
Exception 和 Error 体现了 Java 平台设计者对不同异常情况的分类,Exception 是程序正常运行过程中可以预料到的意外情况,并且应该被开发者捕获,进行相应的处理。Error 是指正常情况下不大可能出现的情况,绝大部分的 Error 都会导致程序处于非正常、不可恢复状态。所以不需要被开发者捕获。
8.2.1系统错误---Error
错误(Error):
NoClassDefFoundError:找不到 class 定义异常
StackOverflowError:深递归导致栈被耗尽而抛出的异常
OutOfMemoryError:内存溢出异常
// 通过无限递归演示堆栈溢出错误
class StackOverflow {
public static void test(int i) {
if (i == 0) {
return;
} else {
test(i++);
}
}
}
public class ErrorEg {
public static void main(String[] args) {
// 执行StackOverflow方法
StackOverflow.test(5);
}
}
8.2.2异常---Exception
1)运行时异常(RuntimeException):
NullPropagation:空指针异常;
ClassCastException:类型强制转换异常
IllegalArgumentException:传递非法参数异常
IndexOutOfBoundsException:下标越界异常
NumberFormatException:数字格式异常
2)非运行时异常:
ClassNotFoundException:找不到指定 class 的异常
IOException:IO 操作异常
// 通过无限递归演示堆栈溢出错误
class StackOverflow {
public static void test(int i) {
if (i == 0) {
return;
} else {
test(i++);
}
}
}
public class ErrorEg {
public static void main(String[] args) {
// 执行StackOverflow方法
StackOverflow.test(5);
}
}
8.3捕捉异常处理异常
Java 的异常处理通过 5 个关键字来实现:try、catch、throw、throws 和 finally。try catch 语句用于捕获并处理异常,finally 语句用于在任何情况下(除特殊情况外)都必须执行的代码,throw 语句用于拋出异常,throws 语句用于声明可能会出现的异常。
Java 的异常处理机制提供了一种结构性和控制性的方式来处理程序执行期间发生的事件。异常处理的机制如下:
在方法中用 try catch 语句捕获并处理异常,catch 语句可以有多个,用来匹配多个异常。
对于处理不了的异常或者要转型的异常,在方法的声明处通过 throws 语句拋出异常,即由上层的调用方法来处理。
try {
逻辑程序块
} catch(ExceptionType1 e) {
处理代码块1
} catch (ExceptionType2 e) {
处理代码块2
throw(e); // 再抛出这个"异常"
} finally {
释放资源代码块
}
8.3.1try--catch代码块
- printStackTrace() 方法:指出异常的类型、性质、栈层次及出现在程序中的位置
- getMessage() 方法:输出错误的性质。
- toString() 方法:给出异常的类型与性质。
-
import java.util.Scanner; public class Test02 { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("---------学生信息录入---------------"); String name = ""; // 获取学生姓名 int age = 0; // 获取学生年龄 String sex = ""; // 获取学生性别 try { System.out.println("请输入学生姓名:"); name = scanner.next(); System.out.println("请输入学生年龄:"); age = scanner.nextInt(); System.out.println("请输入学生性别:"); sex = scanner.next(); } catch (Exception e) { e.printStackTrace(); System.out.println("输入有误!"); } System.out.println("姓名:" + name); System.out.println("年龄:" + age); } }
try { // 可能会发生异常的语句 } catch(ExceptionType e) { // 处理异常语句 } catch(ExceptionType e) { // 处理异常语句 } catch(ExceptionType e) { // 处理异常语句 ... }
public class Test03 { public static void main(String[] args) { Date date = readDate(); System.out.println("读取的日期 = " + date); } public static Date readDate() { FileInputStream readfile = null; InputStreamReader ir = null; BufferedReader in = null; try { readfile = new FileInputStream("readme.txt"); ir = new InputStreamReader(readfile); in = new BufferedReader(ir); // 读取文件中的一行数据 String str = in.readLine(); if (str == null) { return null; } DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); Date date = df.parse(str); return date; } catch (FileNotFoundException e) { System.out.println("处理FileNotFoundException..."); e.printStackTrace(); } catch (IOException e) { System.out.println("处理IOException..."); e.printStackTrace(); } catch (ParseException e) { System.out.println("处理ParseException..."); e.printStackTrace(); } return null; } }
8.3.2finally代码块
根据 try catch 语句的执行过程,try 语句块和 catch 语句块有可能不被完全执行,而有些处理代码则要求必须执行。例如,程序在 try 块里打开了一些物理资源(如数据库连接、网络连接和磁盘文件等),这些物理资源都必须显式回收。
try {
// 可能会发生异常的语句
} catch(ExceptionType e) {
// 处理异常语句
} finally {
// 清理代码块
}
try {
// 逻辑代码块
} finally {
// 清理代码块
}
try catch finally 语句块的执行情况可以细分为以下 3 种情况:
如果 try 代码块中没有拋出异常,则执行完 try 代码块之后直接执行 finally 代码块,然后执行 try catch finally 语句块之后的语句。
如果 try 代码块中拋出异常,并被 catch 子句捕捉,那么在拋出异常的地方终止 try 代码块的执行,转而执行相匹配的 catch 代码块,之后执行 finally 代码块。如果 finally 代码块中没有拋出异常,则继续执行 try catch finally 语句块之后的语句;如果 finally 代码块中拋出异常,则把该异常传递给该方法的调用者。
如果 try 代码块中拋出的异常没有被任何 catch 子句捕捉到,那么将直接执行 finally 代码块中的语句,并把该异常传递给该方法的调用者
import java.util.Scanner;
public class Test04 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("Windows 系统已启动!");
String[] pros = { "记事本", "计算器", "浏览器" };
try {
// 循环输出pros数组中的元素
for (int i = 0; i < pros.length; i++) {
System.out.println(i + 1 + ":" + pros[i]);
}
System.out.println("是否运行程序:");
String answer = input.next();
if (answer.equals("y")) {
System.out.println("请输入程序编号:");
int no = input.nextInt();
System.out.println("正在运行程序[" + pros[no - 1] + "]");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("谢谢使用!");
}
}
}
8.4在方法中抛出异常
throws 关键字和 throw 关键字在使用上的几点区别如下:
- throws 用来声明一个方法可能抛出的所有异常信息,表示出现异常的一种可能性,但并不一定会发生这些异常;throw 则是指拋出的一个具体的异常类型,执行 throw 则一定抛出了某种异常对象。
- 通常在一个方法(类)的声明处通过 throws 声明方法(类)可能拋出的异常信息,而在方法(类)内部通过 throw 声明一个具体的异常信息。
- throws 通常不用显示地捕获异常,可由系统自动将所有捕获的异常信息抛给上级方法; throw 则需要用户自己捕获相关的异常,而后再对其进行相关包装,最后将包装后的异常信息抛出。
- 8.4.1使用throws关键字抛出异常
- 当一个方法产生一个它不处理的异常时,那么就需要在该方法的头部声明这个异常,以便将该异常传递到方法的外部进行处理。使用 throws 声明的方法表示此方法不处理异常。throws 具体格式如下:
returnType method_name(paramList) throws Exception 1,Exception2,…{…}
import java.io.FileInputStream; import java.io.IOException; public class Test04 { public void readFile() throws IOException { // 定义方法时声明异常 FileInputStream file = new FileInputStream("read.txt"); // 创建 FileInputStream 实例对象 int f; while ((f = file.read()) != -1) { System.out.println((char) f); f = file.read(); } file.close(); } public static void main(String[] args) { Throws t = new Test04(); try { t.readFile(); // 调用 readFHe()方法 } catch (IOException e) { // 捕获异常 System.out.println(e); } } }
public class OverrideThrows { public void test() throws IOException { FileInputStream fis = new FileInputStream("a.txt"); } } class Sub extends OverrideThrows { // 子类方法声明抛出了比父类方法更大的异常 // 所以下面方法出错 public void test() throws Exception { } }
使用 throws 声明抛出异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由向上一级的调用者处理;如果 main 方法也不知道如何处理这种类型的异常,也可以使用 throws 声明抛出异常,该异常将交给 JVM 处理。JVM 对异常的处理方法是,打印异常的跟踪栈信息,并中止程序运行,这就是前面程序在遇到异常后自动结束的原因。
8.4.2使用throw关键字抛出异常
throw 语句用来直接拋出一个异常,后接一个可拋出的异常类对象,其语法格式如下: throw ExceptionObject;
import java.util.Scanner; public class Test05 { public boolean validateUserName(String username) { boolean con = false; if (username.length() > 8) { // 判断用户名长度是否大于8位 for (int i = 0; i < username.length(); i++) { char ch = username.charAt(i); // 获取每一位字符 if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { con = true; } else { con = false; throw new IllegalArgumentException("用户名只能由字母和数字组成!"); } } } else { throw new IllegalArgumentException("用户名长度必须大于 8 位!"); } return con; } public static void main(String[] args) { Test05 te = new Test05(); Scanner input = new Scanner(System.in); System.out.println("请输入用户名:"); String username = input.next(); try { boolean con = te.validateUserName(username); if (con) { System.out.println("用户名输入正确!"); } } catch (IllegalArgumentException e) { System.out.println(e); } }
8.5自定义异常
如果 Java 提供的内置异常类型不能满足程序设计的需求,这时我们可以自己设计 Java 类库或框架,其中包括异常类型。实现自定义异常类需要继承 Exception 类或其子类,如果自定义运行时异常类需继承 RuntimeException 类或其子类
-
<class><自定义异常名><extends><Exception>
class IntegerRangeException extends Exception { public IntegerRangeException() { super(); } public IntegerRangeException(String s) { super(s); } }
import java.util.InputMismatchException; import java.util.Scanner; public class Test07 { public static void main(String[] args) { int age; Scanner input = new Scanner(System.in); System.out.println("请输入您的年龄:"); try { age = input.nextInt(); // 获取年龄 if(age < 0) { throw new MyException("您输入的年龄为负数!输入有误!"); } else if(age > 100) { throw new MyException("您输入的年龄大于100!输入有误!"); } else { System.out.println("您的年龄为:"+age); } } catch(InputMismatchException e1) { System.out.println("输入的年龄不是数字!"); } catch(MyException e2) { System.out.println(e2.getMessage()); } }
在该程序的主方法中,使用了 if…else if…else 语句结构判断用户输入的年龄是否为负数和大于 100 的数,如果是,则拋出自定义异常 MyException,调用自定义异常类 MyException 中的含有一个 String 类型的构造方法。在 catch 语句块中捕获该异常,并调用 getMessage() 方法输出异常信息。
提示:因为自定义异常继承自 Exception 类,因此自定义异常类中包含父类所有的属性和方法。
-
8.6异常的使用原则
class SelfException extends RuntimeException {
SelfException() {
}
SelfException(String msg) {
super(msg);
}
}
public class PrintStackTraceTest {
public static void main(String[] args) {
firstMethod();
}
public static void firstMethod() {
secondMethod();
}
public static void secondMethod() {
thirdMethod();
}
public static void thirdMethod() {
throw new SelfException("自定义异常信息");
}
}
public class ThreadExceptionTest implements Runnable {
public void run() {
firstMethod();
}
public void firstMethod() {
secondMethod();
}
public void secondMethod() {
int a = 5;
int b = 0;
int c = a / b;
}
public static void main(String[] args) {
new Thread(new ThreadExceptionTest()).start();
}
- 多线程异常的跟踪栈,从发生异常的方法开始,到线程的 run 方法结束。从上面的运行结果可以看出,程序在 Thread 的 run 方法中出现了 ArithmeticException 异常,这个异常的源头是 ThreadExcetpionTest 的 secondMethod 方法,位于 ThreadExcetpionTest.java 文件的 14 行。这个异常传播到 Thread 类的 run 方法就会结束(如果该异常没有得到处理,将会导致该线程中止运行)。
- 前面已经讲过,调用 Exception 的 printStackTrace() 方法就是打印该异常的跟踪栈信息,也就会看到上面两个示例运行结果中的信息。当然,如果方法调用的层次很深,将会看到更加复杂的异常跟踪栈。
- 提示:虽然 printStackTrace() 方法可以很方便地用于追踪异常的发生情况,可以用它来调试程序,但在最后发布的程序中,应该避免使用它。应该对捕获的异常进行适当的处理,而不是简单地将异常的跟踪栈信息打印出来。