06-异常处理

0. 什么是异常?

在使用计算机语言进行项目开发的过程中,即使程序员把代码写得尽善尽美,在系统的运行过程中仍然会遇到一些问题,因为很多问题不是靠代码能够避免的,比如:客户输入数据的格式,读取文件是否存在,网络是否始终保持通畅等等。

  • 异常:在 Java 语言中,将程序执行中发生的不正常情况称为“异常”。(开发过程中的语法错误和逻辑错误不是异常)

  • Java 程序在执行过程中所发生的异常事件可分为两类:

    • Error:Java 虚拟机无法解决的严重问题。如:JVM 系统内部错误、资源耗尽等严重情况。一般不编写针对性的代码进行处理。

    • Exception:其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:

      • 空指针访问

      • 试图读取不存在的文件

      • 网络连接中断

  • 对于这些错误,一般有两种解决方法:一是遇到错误就终止程序的运行。另一种方法是由程序员在编写程序时,就考虑到错误的检测、错误消息的提示,以及错误的处理。

  • 捕获错误最理想的是在编译期间,但有的错误只有在运行时才会发生。比如:除数为 0,数组下标越界等

    • 分类:编译时异常和运行时异常。

1. 体系结构

(1)java.lang.Object

|---java.lang.Throwable
|-------①java.lang.Error:错误,java程序对此无能为力,不显式的处理
|-------②java.lang.Exception:异常。需要进行处理
|----------2.1RuntimeException:运行时异常
|---------------ArrayIndexOutOfBoundsException
|---------------NullPointerException
|---------------ArithmeticException
|---------------ClassCastException
|----------2.2非RuntimeException:编译时异常

(2)因为 java 程序分为 javac.exejava.exe 两个过程,在每个过程中,都有可能出现异常。故分为编译时异常、运行时异常。

  • 对于运行时异常比较常见,可以不显式的来处理。

  • 对于编译时异常,必须要显式的处理

编译时异常,不是说有异常才处理,而是存在异常的隐患,必须在编译前,提示程序,万一出现异常,如何处理!

注意,运行时异常常有以下四种:

//1.数组下标越界异常:ArrayIndexOutOfBoundsException
@Test
public void test2(){
int[] i = new int[10];
//System.out.println(i[10]);
System.out.println(i[-10]);
}
//2.算是异常:ArithmeticException
@Test
public void test3(){
int i = 10;
System.out.println(i / 0);
}
//3.类型转换异常:ClassCastException
@Test
public void test4(){
Object obj = new Date();
String str = (String)obj;
//String str1 = (String)new Date();
}
//4.空指针异常:NullPointException
@Test
public void test5(){
//Person p = new Person();
//p = null;
//System.out.println(p.toString());
String str = new String("AA");
str = null;
System.out.println(str.length());
}

2. 如何处理异常

Java 中的“抓抛模型”

  1. "抛":当我们执行代码时,一旦出现异常,就会在异常的代码处生成一个对应的异常类型的对象,并将此对象抛出。(自动抛出 / 手动抛出)一旦抛出此异常类的对象,那么程序就终止执行。此异常类的对象抛给方法的调用者。

  2. "抓":抓住上一步抛出来的异常类的对象。如何抓?即为异常处理的方式。Java 提供了两种方式用来处理一个异常类的对象。

(1) 处理方式一

try{
//可能出现异常的代码
}catch(Exception1 e1){
//处理的方式1
}catch(Exception2 e2){
//处理的方式2
}finally{
//一定要执行的代码
}

注意:

  1. try 内声明的变量,类似于局部变量,出了 try{} 语句,就不能被调用

  2. finally 是可选的。

  3. catch 语句内部是对异常对象的处理:getMessage();printStackTrace();

  4. 可以有多个 catch 语句,try 中抛出的异常类对象从上往下去匹配 catch 中的异常类的类型,一旦满足就执行 catch 中的代码。执行完,就跳出其后的多条 catch 语句

  5. 如果异常处理了,那么其后的代码继续执行。

  6. 若 catch 中多个异常类型是"并列"关系,孰上孰下都可以。若 catch 中多个异常类型是"包含"关系,须将子类放在父类的上面,进行处理,否则报错。

  7. finally 中存放的是一定会被执行的代码,不管 try 中、catch 中是否仍有异常未被处理,以及是否有 return 语句。

  8. try-catch 是可以嵌套的。

(2) 处理方式二

在方法的声明处,显式的使用 throws + 异常类型:

public void method1() throws Exception1 e1,Exception2 e2{
//可能出现异常(尤其是编译时异常,一定要处理)
}
public void method2() throws Exception1 e1,Exception2 e2{
method1();
}
public void method3(){
try{
method2();
}catch(Exception1 e1){
System.out.println(e1.getMessage());
}catch(Exception2 e2){
System.out.println(e2.getMessage());
}
}
public static void main(String[] args){
对象1.method3();//不会再出现上述的Exception1和Exception2的异常!
}

3. 如何手动的抛出一个异常

在方法的内部,可以使用 throw + 异常类对象,来手动的抛出一个异常!

//比较两个圆的半径的大小。
public int compareTo(Object obj) throws Exception{
if(this == obj){
return 0;
}
else if(obj instanceof Circle){
Circle c = (Circle)obj;
if(this.radius > c.radius){
return 1;
}else if(this.radius == c.radius){
return 0;
}else{
return -1;
}
}else{
//return -2;
//手动的抛出一个异常
//throw new Exception("传入的类型有误!");
//throw new String("传入的类型有误!");
throw new MyException("传入的类型有误!");
}
}

4. 如何自定义一个异常类

手动的抛出一个异常,除了抛出的是现成的异常类的对象之外,还可以抛出一个自定义的异常类的对象!

如何自定义一个异常类呢?

  1. 自定义的异常类继承现有的异常类

  2. 提供一个序列号,提供几个重载的构造器

public class MyException extends Exception{
static final long serialVersionUID = -70348975766939L;
public MyException(){
}
public MyException(String msg){
super(msg);
}
}

5. “5个关键字搞定异常处理!”

其中,要区分:throw 与 throws 的区别?

6. 异常处理综合练习

编写应用程序 EcmDef.java,接收命令行的两个参数,要求不能输入负数,计算两数相除。

对数据类型不一致(NumberFormatException)、缺少命令行参数(ArrayIndexOutOfBoundsException、除 0(ArithmeticException)及输入负数(EcDef 自定义的异常)进行异常处理。

提示:

(1) 在主类(EcmDef)中定义异常方法(ecm)完成两数相除功能。

(2) 在 main() 方法中使用异常处理语句进行异常处理。

(3) 在程序中,自定义对应输入负数的异常类(EcDef)。

(4) 运行时接受参数 java EcmDef 20 10 //args[0]=“20” args[1]=“10”

(5) Interger 类的 static 方法 parseInt(String s) 将 s 转换成对应的 int 值。如:int a=Interger.parseInt(“314”);

public class EcmDef {
public static void main(String[] args) {
try{
int i = Integer.parseInt(args[0]);//被除数
int j = Integer.parseInt(args[1]);//除数
ecm(i,j);
}catch(NumberFormatException e){
System.out.println("输入的数据类型不一致");
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("缺少命令行参数");
}catch(ArithmeticException e){
System.out.println("分母为零了");
}catch(EcDef e){
System.out.println(e.getMessage());
}
}
public static void ecm(int i,int j) throws EcDef{
if(i < 0 || j < 0){
throw new EcDef("您输入的数值存在负数!");
}
System.out.println(i / j);
}
}
//自定义异常类
class EcDef extends Exception{
static final long serialVersionUID = -3387524229948L;
public EcDef(){
}
public EcDef(String msg){
super(msg);
}
}