n 异常的概念
程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。异常发生时,是任程序自生自灭,立刻退出终止,还是输出错误给用户?
比如除法运算、读写文件操作,都可能发生异常。(当除数为0时;文件路径不存在时)。该如何处理?
C语言的风格:用函数返回值作为执行状态。比如返回一个为0的值表示文件不存在这个状态。缺点是代码比较散乱。
而Java语言提供了一种优秀的解决办法:异常处理机制。java将处理异常的代码放到一个统一的 try-catch-finally结构中去处理,且代码易读。请看下面的例子
Ø 示例一
数学运算之除0异常
import java.util.Scanner;
public class AllDemo {
public static void main(String[] args) {
System.out.println("----欢迎使用命令行除法计算器----");
Scanner scan = new Scanner(System.in);
int num1 = scan.nextInt();
int num2 = scan.nextInt();
int result = a(num1,num2);
System.out.println("result:" + result);
scan.close();
}
private static int a(int num1, int num2) {
return b(num1,num2);
}
private static int b(int num1, int num2) {
return devide(num1,num2);
}
public static int devide(int num1, int num2) {
return num1 / num2;
}
}
执行时输入7和0,会看到控制台结果
----欢迎使用命令行除法计算器----
7
0
Exception in thread "main" java.lang.ArithmeticException: / by zero
at AllDemo.devide(AllDemo.java:24)
at AllDemo.b(AllDemo.java:20)
at AllDemo.a(AllDemo.java:16)
at AllDemo.main(AllDemo.java:9)
分析:当devide函数发生除0异常时,devide函数将抛出ArithmeticException异常,于是调用它的函数b也发生了异常,于是调用b的函数a也发生异常,于是调用a的函数main也发生了异常,这样一直向调用栈的栈底回溯,这叫做异常的冒泡。这个例子没有使用异常处理机制,异常最终由main函数抛给java虚拟机,导致程序终止。
Ø 示例二
读写文件异常
import java.io.FileInputStream;
import java.io.IOException;
class ReadFile {
public static void testException() throws IOException
{
//FileInputStream的构造函数会抛出FileNotFoundException
FileInputStream fileIn = new FileInputStream("E:\\a.txt");
int word;
while((word = fileIn.read())!=-1) //read方法会抛出IOException
{
System.out.print((char)word);
}
fileIn.close(); //close方法会抛出IOException
}
public static void main(String arg[]) throws IOException{
ReadFile.testException();
}
}
说明:
1、如果E盘下没有文件a.txt,发生FileNotFoundException。
2、如果a.txt存在,但是被其它进程锁住,有可能发生IOException
3、鉴于1、2,此处为了编译正确,所以只得在testException()加上“throws IOException”,调用它的main()也得加上“throws IOException”。(注:FileNotFoundException是的IOException的子类)
n java异常处理的try-catch-finally结构
我们可以使用异常处理结构改进上面读文件的例子
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
class ReadFile {
public static void testException() {
FileInputStream fileIn = null;
try {
// FileInputStream的构造函数会抛出FileNotFoundException
fileIn = new FileInputStream("E:\\a.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
System.out.println(e.getMessage());
}
int word;
try {
while ((word = fileIn.read()) != -1) // read方法可抛出IOException
{
System.out.print((char) word);
}
fileIn.close(); // close方法可抛出IOException
} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println("finally块无论如何都要执行");
}
}
public static void main(String arg[]) {
ReadFile.testException();
}
}
说明
1、如果E盘下没有文件a.txt,发生FileNotFoundException。进入catch结构,打印出一些信息。
2、程序仍然往下能继续运行,在fileIn.read()时发生异常NullPointerException,这是因为第1步进了catch块,这样fileIn变量仍是最初的null值,一旦调函数便发生NullPointerException。
3、问:我们针对NullPointerException为什么不需要写try-catch-finally结构?具体原因请阅读后面的RuntimeException内容
4、这个例子中故意为了演示出NullPointerException,而把try-catch块写成了两个,实际上可以合并。
n try-catch-finally结构说明
1、一个try可以对应多个catch块。
2、如果发生异常,异常被抛给第一个catch 块,如果异常的类型与 catch匹配,它在这里就会被捕获。如果不匹配,它会被传递给第二个 catch 块。如此,直到异常被捕获或者通过所有的 catch 块。
3、finally块始终会被执行。
4、如果try或catch块中存在return语句,那么catch、finally块中的语句也会被执行完了后,才真正return。除非遇到下面的几种情况(1)System.exit(n)可导致立即终止(2)finally块中发生异常(3)程序所在线程死亡(4)关闭CPU。
练习
class Snippet {
public static String t() {
String s = "1";
try {
s = "2";
return s;
//throw new Exception("some");
} catch (Exception e) {
s = "3";
System.out.println("in catch block");
return s;
}finally {
s = "4";
System.out.println("in finally block");
return s;
}
}
public static void main(String[] args) {
String s= t();
System.out.println(s);
}
}
//无论如何,都要进到finally块执行,如果finally{}里有return 那么返回的肯定是finally里的
Java异常类图
从上面的示例代码可以看出,java把异常也看成是对象。进而设计了下面所示的异常方面的类结构体系。最上层类叫做Throwable。可以把这些分成两大种类
Ø 非检查异常(unckecked exception):Error 和 RuntimeException 以及他们的子类。javac在编译时,不会提示和发现这样的异常,不要求程序员必须处理这些异常。在运行阶段,倘若发生Error则虚拟机几乎崩溃,倘若发生RuntimeException若程序员没处理它则一直回溯向上抛给java虚拟机处理。当然,如果程序员愿意的话,也可以编写代码处理(使用try…catch…finally)这样的异常(但是通常情况下不会这样做。需要这样做的情况是比如搞数学运算的这个专业领域要处理ArithmeticException)。对于这些异常,我们应该修正代码,而不是去通过异常处理器处理。这种异常发生的原因多半是代码写的有问题。如除0错误ArithmeticException,错误的强制类型转换错误ClassCastException,数组索引越界ArrayIndexOutOfBoundsException,使用了空对象NullPointerException等等。
Ø 检查异常(checked exception):除了Error 和 RuntimeException的其它异常。javac强制要求程序员为这样的异常做预备处理工作(使用try…catch…finally或者throws)。在方法中要么用try-catch语句捕获它并处理,要么用throws子句声明抛出它,否则编译不会通过。这样的异常一般是由程序的运行环境导致的。因为程序可能被运行在各种未知的环境下,而程序员无法干预用户如何使用他编写的程序,于是程序员就应该为这样的异常时刻准备着。如SQLException , IOException,ClassNotFoundException 等。
n 常见异常类型说明
异常类型说明
Exception 异常层次结构的父类
ArithmeticException算术错误情形,如以零作除数
ArrayIndexOutOfBoundsException数组下标越界
NullPointerException尝试访问 null 对象成员
ClassNotFoundException不能加载所需的类
IllegalArgumentException方法接收到非法参数
ClassCastException对象强制类型转换出错
NumberFormatException数字格式转换异常,如把"abc"转换成数字
n throws关键字
声明本方法不处理异常,让调用者处理。
在函数签名中使用throws 声明交给函数调用者caller去解决。
import java.util.Scanner;
public class HelloWorld {
public static void main(String[] args) {
try {
divide();//调用的此方法抛出了异常
}
catch (Exception e) {
System.out.println("错误:被除数和除数必须是整数,且除数不能为零。");
e.printStackTrace();
e.getMessage();//暂时打印不出任何内容
}
finally{
System.out.println("感谢使用本程序");
}
}
public static void divide() throws Exception{//声明异常
Scanner scanner = new Scanner(System.in);
System.out.println("请输入被除数");
int num1 = scanner.nextInt();//有可能异常InputMismatchException
System.out.println("请输入除数");
int num2 = scanner.nextInt();//有可能异常InputMismatchException
System.out.println(num1 / num2);//有可能异常除数等于0:ArithmeticException
}
}
n throw关键字
主动抛出异常
如果对于具体的一些处理逻辑,程序员也可以主动的抛出异常让 外层处理。(此异常可能是程序员自己定义的)。
class Person {
private String name;
private String sex = "男";
public void setSex(String sex) throws Exception{
if("男".equals(sex) || "女".equals(sex)){
this.sex = sex;
}
else{
//主动抛出异常。也可做成自定义异常并抛出
throw new Exception("性别输入错误,必须是男或女");
}
}
public void print() {
System.out.println(this.name + this.sex);
}
}
class Test {
public static void main(String[] args) {
Person p = new Person();
try {
p.setSex("male");
p.print();
} catch (Exception e) {
e.printStackTrace();
}
}
}
n 异常类的两个打印异常信息的好办法
一般,我们需要打印出异常的相关信息。在Exception类中,定义了下面两个方法,
e.printStackTrace();//打印调用堆栈信息 并把e.getMessage()的信息也打出来了
e.getMessage();//打印异常的相关信息
与IOException类相似,我们自己定义的异常类往往也是Exception的子类,我们可以(1)覆盖e.getMessage()方法; 也可以(2)构造的时候传入具体的字符串信息,因为e.getMessage()就是获取这个信息
示例
public class Test {
public static void main(String[] args) {
try {
Exception e = new Exception("哈哈,我是异常");
throw e;
} catch (Exception e) {
e.printStackTrace();
System.out.println("message="+ e.getMessage());
}
}
}
n 建议
(1)多重catch块:Catch块的排列顺序必须是从子类到父类。最后一个一般是Exception。
(2)不要在fianlly中使用return。
不要在finally中抛出异常。
减轻finally的任务,不要在finally中做一些其它的事情,finally块仅仅用来释放资源是最合适的。
将尽量将所有的return写在函数的最后面,而不是try … catch … finally中。
n 练习
import java.util.InputMismatchException;
import java.util.Scanner;
public class HelloWorld {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int num1=1 , num2=1;
try {
System.out.println("请输入被除数");
num1 = scanner.nextInt();//有可能异常InputMismatchException
System.out.println("请输入除数");
num2 = scanner.nextInt();//有可能异常InputMismatchException
System.out.println(String.format("%d / %d = %d", num1, num2, num1 / num2));//有可能异常除数等于0:ArithmeticException
}
catch(InputMismatchException e)
{
System.err.println("被除数和除数必须是整数");
return;
}
catch(ArithmeticException e)
{
System.err.println("除数不能为零");
//return;
System.exit(0);//这个是立即终止
}
catch (Exception e) {
System.out.println("错误:被除数和除数必须是整数,且除数不能为零。");
e.printStackTrace();
e.getMessage();//暂时打印不出任何内容
}
finally{
System.out.println("感谢使用本程序");
}
}
}