Java 学习(四)—— 异常处理

时间:2022-12-12 20:03:10

一、异常的概念

  异常指的是运行期出现的错误,也就是当程序开始执行以后执行期出现的错误。出现错误时观察错误的名字和行号最为重要。

     异常发生的原因

  • 用户输入了非法数据。

  • 要打开的文件不存在。

  • 网络通信时连接中断,或者JVM内存溢出。

二、异常的分类

  • 检查性异常(Exception)用户错误或问题引起的异常,这是程序员无法预见的。是异常的父类,子类需要用户显式声明或捕获;例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。

  • 运行时异常(Runtime Exception) 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。例如,除数为0,数组下标超出范围等。

  • 错误(ERROR):由Java虚拟机生成并抛出,错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,栈溢出,一个错误就发生了,它们在编译也检查不到的;动态链接失败;虚拟机错误。

Exception的层次

    所有的异常类是从java.lang.Exception类继承的子类,Exception类是Throwable类的子类。除了Exception类外,Throwable还有一个子类Error 。Java程序通常不捕获错误。错误一般发生在严重故障时,它们在Java程序处理的范畴之外。

    Throwable类:可以被抛出的,是所有异常类的根基类,所有的异常类都是从它继承。

    Error类:是不能处理的错误。Java虚拟机(JVM)运行时出错,用来指示运行时环境发生的错误。例如,JVM内存溢出。一般地,程序不会从错误中恢复。

    IOException......类:这些错误必须被处理;

    Runtime Exception类:不一定都要处理,可以忽略;

Java 学习(四)—— 异常处理

三、异常的捕获和处理

    Java异常处理的五个关键字:try、catch、finally、throw、throws

捕获异常

    使用try和catch关键字可以捕获异常。try/catch/finally代码块放在异常可能发生的地方。

    try/catch代码块中的代码称为保护代码,使用 try/catch/finally的语法如下:

try
{
   // 可能抛出异常的语句
}catch(ExceptionName e1)
{
   //Catch 块
}catch(ExceptionName e1)
{
   //Catch 块
}finally{
    ...
}

    try语句:捕获可能抛出异常的语句,如果有例外异常,交给后面的catch语句进行相应处理,后面可以跟一个或多个catch代码段。 

    Catch语句:包含要捕获异常类型的声明。当保护代码块中发生一个异常时,try后面的catch块就会被检查。

如果发生的异常包含在catch块中,异常会被传递到该catch块处理。在catch代码块中可以用一些方法获取异常信息

    getMessage()方法:用来得到有关异常事件的信息。

    printStackTrace()方法:一般使用这种方法,包括了getMessage()方法,用来跟踪异常事件发生时执行堆栈的内容。使用前需要 new 一个错误对象再调用,因为它是专属某个错误对象里面的方法,

    finally语句:为异常处理提供的统一出口,无论是否发生异常都执行,可以不写。在finally语句中,可以进行资源的清除工作,例如,打开关闭文件、删除临时文件...

实例

下面的例子中声明有两个元素的一个数组,当代码试图访问数组的第三个元素的时候就会抛出一个异常。

import java.io.*;
public class ExcepTest{

public static void main(String args[]){
   try{
      int a[] = new int[2];
      System.out.println("Access element three :" + a[3]);  //可能抛出异常语句
   }catch(Exception e1){                                    //捕获数组下标越界异常
      System.out.println("数组下标越界"); 
   }catch(ArrayIndexOutOfBoundsException e2){
       System.out.println("异常发生");                      //输出错误信息
   }
 }
}

因为Exception类中包含ArrayIndexOutOfBoundsException异常,所以第二个catch异常重复,是不允许的,编译不会通过。

以上代码编译运行输出结果如下:

Exception thrown  :java.lang.ArrayIndexOutOfBoundsException: 

throws/throw关键字:

如果一个方法没有捕获一个检查性异常,那么该方法必须使用throws 关键字来声明。throws关键字放在方法签名的尾部。

也可以使用throw关键字抛出一个异常,无论它是新实例化的还是刚捕获到的。

下面方法的声明抛出一个RemoteException异常:

import java.io.*;
public class className
{
   public void deposit(double amount) throws RemoteException
   {
      // Method implementation
      throw new RemoteException();
   }
   //Remainder of class definition
}

一个方法可以声明抛出多个异常,多个异常之间用逗号隔开。

例如,下面的方法声明抛出RemoteException和InsufficientFundsException:

import java.io.*;
public class className
{
   public void withdraw(double amount) throws RemoteException,
                              InsufficientFundsException
   {
       // Method implementation
   }
   //Remainder of class definition
}

测试异常

package test;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class testException {
	
	/**
	 * 任何方法往外抛能处理的异常的时候都有一种简单的写法:“throws Exception”,
	 * 因为Exception类是所有能处理的异常类的根基类,因此抛出Exception类就会抛出所有能够被处理的异常类里了。
	 * 使用“throws Exception”抛出所有能被处理的异常之后,这些被抛出来的异常就是交给JAVA运行时系统处理了,
	 * 而处理的方法是把这些异常的相关错误堆栈信息全部打印出来。
	 * @throws Exception
	 */
	void fn() throws Exception{
		
	}
	
	/**
	 * 已知异常的类型,方法声明时使用throws把异常往外抛
	 * @param i
	 * @throws ArithmeticException
	 */
	void m1(int i) throws ArithmeticException{
		
	}
	
	/**
	 * 手动抛出异常
	 * new了一个异常对象,在构建这个对象的时候还可以指定他相关的信息,如这里指明了异常信息“i不能等于0”
	 * 这个对象抛出去的时候使用getMessage()方法拿到的就是“i不能等于0”这种信息。
	 * @param i
	 */
	void m2(int i) {
		if (i==0) {
			throw new ArithmeticException("i不能等于0");
		}
	}
	
	/**
	 * 正常情况下如果这里不写try……catch语句那么程序编译时一定会报错,
     * 因为这里有可能会产生两个个必须要处理的异常:FileNotFoundException和IOException。
     * 但由于在声明方法f()时已经使用throws把可能产生的这两个异常抛出了,
     * 所以这里可以不写try……catch语句去处理可能会产生的异常。
     * f()方法把抛出的异常交给下一个要调用它的方法去处理
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	void f()throws FileNotFoundException,IOException {
		//这里有可能会产生FileNotFoundException异常
		FileInputStream fis=new FileInputStream("1.txt");
		//这里有可能会产生IOException异常
		int b = fis.read();
		while (b!=-1) {
			System.out.println((char)b);
			b=fis.read();
		}
	}
	
	/**
     * 在f2()方法里面调用f()方法时必须要处理f()方法抛出来的异常,
     * 当然,如果f2()方法也没有办法处理f()方法抛出来的异常,那么f2()方法也可以使用throws把异常抛出,
     * 交给下一个调用了f2()的方法去处理f()方法抛出来的异常。
     * 这里f2()调用f()方法时,选择不处理f()方法中可能抛出的异常,将异常继续抛出
     * @throws Exception
     */
    void f2() throws Exception {
        f();
    }
    
    /**
     * f3方法调用f方法捕获f()方法抛出的2个异常并进行处理
     */
    void f3() {
        try {
            f();
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());		//处理的方法是把错误信息打印出来
        } catch (IOException e) {
            e.printStackTrace();					//处理的方法是使用printStackTrace()方法把错误的堆栈信息全部打印出来。
        }
    }
    
    public static void main(String[] args) {
    	FileInputStream fis = null;
        try {
            fis = new FileInputStream("MyFile.txt");
            int b = fis.read();					//这个有可能会抛出IOException异常
            while (b != -1) {
                System.out.println((char)b);
                b = fis.read();
            }
        } catch (FileNotFoundException e) {
            //使用catch捕获FileNotFoundException类异常的异常对象e。并让异常对象e自己调用printStackTrace方法打印出全部的错误信息
            e.printStackTrace();
        } catch (IOException e) {
            //再次使用catch捕获IOException类的异常对象e,并让异常对象e自己调用getMessage()方法将错误信息打印出来。
            System.out.println(e.getMessage());;
        }finally{
            try {
                /**
                 * 前面已经把一个文件打开了,不管打开这个文件时有没有错误发生,即有没有产生异常,最后都一定要把这个文件关闭掉,
                 * 因此使用了finally语句,在finally语句里面不管前面这个文件打开时是否产生异常,在finally这里执行in.close()都能把这个文件关闭掉,
                 * 关闭文件也有可能会产生异常,因此在finally里面也使用了try……catch语句去捕获有可能产生的异常。
                 */
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }	
}
以上代码编译运行输出结果如下:
java.io.FileNotFoundException: MyFile.txt (系统找不到指定的文件。)
	at java.io.FileInputStream.open0(Native Method)
	at java.io.FileInputStream.open(FileInputStream.java:195)
	at java.io.FileInputStream.<init>(FileInputStream.java:138)
	at java.io.FileInputStream.<init>(FileInputStream.java:93)
	at test.testException.main(testException.java:88)
Exception in thread "main" java.lang.NullPointerException
	at test.testException.main(testException.java:107)

四、自定义异常

编写自己的异常类时需要记住下面的几点。

  • 继承java.lang.Exception;声明自己的异常类。

  • 如果希望写一个检查性异常类,则需要继承Exception类。

  • 如果你想写一个运行时异常类,那么需要继承RuntimeException 类。

实例:自定义异常

package test;

public class MyException extends Exception{
	private int id;

    /**
     * 自定义异常类的构造方法
     * @param message
     * @param id
     */
    public MyException(String message,int id) {
        super(message);			//调用父类Exception的构造方法
        this.id = id;
    }
    
    /**
     * 获取异常的代码
     * @return
     */
    public int getId() {
        return id;
    }
}

实例:自定义异常测试

package test;
import java.text.MessageFormat;

public class TestMyException {
	//throws MyException,抛出我们自定义的MyException类的异常。
    public void regist(int num) throws MyException {
        if (num < 0) {
            //使用throw手动抛出一个MyException类的异常对象。
            throw new MyException("人数为负值,不合理", 1);
        }
        /**
         * 注意:当我们抛出了异常之后,
         * System.out.println(MessageFormat.format("登记人数:{0}",num));是不会被执行的。
         * 抛出异常之后整个方法的调用就结束了。
         */
        System.out.println(MessageFormat.format("登记人数:{0}",num));
    }
    
    public void manage() {
        try {
            regist(-100);
        } catch (MyException e) {
            System.out.println("登记失败,错误码:"+e.getId());
            e.printStackTrace();
        }
        System.out.println("操作结束");
    }
    
    
    public static void main(String[] args) {
        TestMyException t = new TestMyException();
        t.manage();
    }
}
登记失败,错误码:1
操作结束
test.MyException: 人数为负值,不合理
	at test.TestMyException.regist(TestMyException.java:9)
	at test.TestMyException.manage(TestMyException.java:21)
	at test.TestMyException.main(TestMyException.java:32)
登记失败,错误码:1
操作结束
test.MyException: 人数为负值,不合理
	at test.TestMyException.regist(TestMyException.java:9)
	at test.TestMyException.manage(TestMyException.java:21)
	at test.TestMyException.main(TestMyException.java:32)

文章参考:孤傲苍狼