Java基础编程--异常处理详细教程(Exception)

Java中供了一种识别和响应错误情况的一致性机制——异常处理机制,有效地异常处理能够使程序具有更强的健壮性、易于调试。本篇博客将详细介绍Java中的异常处理(Exception)。


目录:

☍ 异常概述与异常体系结构

☍ 常见异常

☍ 异常处理机制

☍ 异常处理机制一:try-catch-finally

☍ 异常处理机制二:throws抛出异常

☍ 手动抛出异常

☍ 用户自定义异常类

☍ 总结


在使用计算机语言进行项目开发的过程中,即使程序员把代码写得尽善尽美,在系统的运行过程中仍然会遇到一些问题,因为存在很多问题不是靠代码能够避免的,比如:客户输入数据的格式,读取文件是否存在,网络是否始终保持通畅等。这时就需要借助Java异常处理机制来解决这部分问题,合理的使用异常处理机制可以降低bug的产生,提高程序的健壮性。

☍ 异常概述与异常体系结构

▴ 异常概述

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

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

Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:栈溢出错误:StackOverflowError和堆溢出错误:OOM(OutOfMemoryError)。一般不编写针对性的代码进行异常处理,修改导致错误出现的代码即可。

public class ExceptionTest {
	public static void main(String[] args) {
		//栈(statck)溢出异常: java.lang.StackOverflowError	
		//main(args);
		
		//堆(heap)溢出异常:java.lang.OutOfMemoryError
		//Integer[] arr = new Integer[1020*1024*1024];
	}
}

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

✔ 空指针访问

✔ 试图读取不存在的文件

✔ 网络连接中断

✔ 数组下标越界

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

☃ 捕获错误最理想的是在编译期间,但有的错误只有在运行时才会发生,如数组越界。

☃ 按异捕获的时间可分为:编译时异常运行时异常

异常处理机制

▴ Exception异常分类:编译时异常&运行时异常

编译时异常

☃ 是指编译器要求必须处置的异常。即程序在运行时由于外界因素造成的一般性异常。编译器要求Java程序必须捕获或声明所有的编译时异常。

☃ 在Java中, Exception类中除了RuntimeException类及其子类外都是编译时异常,对应上图中受检异常。编译时异常的特点是Java编译器会对其进行检查,如果存在异常情况就必须对异常进行处理,否则程序无法通过编译,如读取空文件的异常。

☃ 对于这类异常,如果程序不处理,可能会带来意想不到的结果。

☃ 处理编译时期的异常有两种方式,具体如下:

(1)使用try…catch语句对异常进行捕获处理。

(2)使用throws关键字声明抛出异常,等待调用者对其处理,如果调用者不处理继续抛出异常。

运行时异常

☃ 是指编译器不要求强制处置的异常。一般是指编程时的逻辑错误,是程序员应该积极避免出现的异常。java.lang.RuntimeException类及它的子类都是运行时异常。

☃ 运行时异常的特点是Java编译器不会对其进行检查,也就是说,当程序中出现这类异常时,即使没有使用try…catch语句捕获或使用throws关键字声明抛出,程序也能编译通过(运行时可能会报错),如数组角标越界异常。

☃ 对于这类异常,可以视情况选择处理(不确定逻辑是否正确)或者不处理(确定逻辑正确时),因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。


☍ 常见异常

▴ 常见运行时异常:java.lang.RuntimeException及其子类

空指针异常NullPointerException

public void NullPointerExceptionDemo() {
	Object obj = null;
	System.out.println(obj.toString());
}

数组角标越界异常:ArrayIndexOutOfBoundsException

public void ArrayIndexOutOfBoundException1() {
	int[] arr = new int[3];
	System.out.println(arr[5]);
}
public void ArrayIndexOutOfBoundException2() {
   	String string = "abcd";
    System.out.println(string.toCharArray()[5]);
}


字符串角标越界异常:StringIndexOutOfBoundsException

public void StringIndexOutOfBoundExceptionDemo() {
	String str = "abcd";
	System.out.println(str.charAt(5));
}


类型转换异常:java.lang.ClassCastException

public void ClassCastExceptionDemo() {
	class Dog{	
	}
	class Cat{	
	}
    //Dog dog = new Cat();  编译不通过,语法错误
	Object obj = new Dog();
	Cat cat = (Cat)obj;
}


数字格式异常:java.lang.NumberFormatException

public void NumberFormatExceptionDemo(){
	String str = "abc";
	//Integer number = new Integer(str);
	int num = Integer.parseInt(str);
}


控制台输入格式异常:java.util.InputMismatchExceptionDemo

public void InputMismatchExceptionDemo() {
	Scanner scanner = new Scanner(System.in);
	System.out.print("请输入数字:");
	int num = scanner.nextInt();
	//控制台输入不符合规则时报的异常:如此时输入非数字字符
	System.out.println(num);
}


算术运算异常:java.lang.ArithmeticException

public void ArithmeticExceptionDemo() {
	int a = 4;
	int b = 0;
	System.out.println(a / b);
}


▴ 常见编译时异常:除RunTimeException外的其他异常类

☃ 编译时异常会在编译时就提醒,要求程序员在编译时就对该异常进行处理

☃ 常见的编译时异常有:

◌ SQL异常:java.sql.SQLException(提供关于数据库访问错误或其他错误信息的异常)

◌ IO流异常: java.io.IOExeption(当发生某种 I/O 异常时,抛出此异常。此类是失败或中断的 I/O 操作生成的异常的通用类。)

java.io.FileNotFoundException(当试图打开指定路径名表示的文件失败时,抛出此异常)

java.io.EOFException(表示在输入过程中意外地到达文件结束或流结束。这个异常主要由数据输入流用来表示流的结尾。请注意,许多其他输入操作在流的末尾返回特殊值,而不是抛出异常。)

◌ 无法找到指定的类异常: java.lang.ClassNotFoundException

◌ 数据格式异常:java.util.zip.DataFormatException(当数据格式发生错误时,抛出此异常)

◌ 找不到方法的异常:NoSuchMethodException(无法找到某一特定方法时,抛出该异常)

◌ 中断异常: java.lang.InterruptedException(线程在等待、睡眠或以其他方式占用时抛出,线程在活动间中断。偶尔,一个方法可能希望测试当前线程是否已被中断,如果是,立即抛出该异常)


☍ 异常处理机制

在编写程序时,经常要在可能出现错误的地方加上检测的代码,
如进行x/y运算时,要检测分母为0,数据为空,输入的不是数据而是字符等。过多的if-else分支会导致程序的代码加长、臃肿、可读性差。因此采用异常处理机制。

☃ Java采用的异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅,并易于维护。

☃ Java异常处理的方式分为:try-catch-finally模式和throws+异常类型模式。

☃ Java提供的是异常处理的抓抛模型。

☃Java程序的执行过程中如出现异常,会生成一个异常类对象,该异常对象将被提交给Java运行时系统,这个过程称为抛出
(throw)异常。

☃ 当程序一旦抛出异常对象后,{}中其后的代码就不再执行。

异常对象的生成:

☃ 由虚拟机自动生成:程序运行过程中,虚拟机检测到程序发生了问题,如果在当前代码中没有找到相应的处理程序,就会在后台自动创建一个对应异常类的实例对象并抛出——自动抛出

☃ 由开发人员手动创建,如:Exception exception = new ClassCastException();创建好的异常对象不抛出对程序没有任何影响,和创建一个普通对象一样

☃ 如果一个方法内抛出异常,该异常对象会被抛给调用者方法中处理。如果异常没有在调用者方法中处理,它继续被抛给这个调用方法的上层方法。这个过程将一直继续下去,直到异常被处理。这一过程称为捕获(catch)异常。

☃ 如果一个异常回到main()方法,并且main()也不处理,则程序运行终止。

☃ 程序员通常只能处理Exception,而对Error无能为力。

✦ 为保证程序正常执行,代码必须对可能出现的异常进行处理,代码执行过程中可以将异常抛给调用者,但异常必须在某一时刻被处理,而不是一直向上抛出异常而不进行处理。


☍ 异常处理机制一:try-catch-finally

try-catch:

☃ 异常处理是通过try-catch-finally语句实现的。

try {
	//可能产生异常的代码
} 
catch (异常类型1  变量名1) {
	// 当产生指定类型(包括子类)的异常时的处理措施
} 
catch (异常类型2  变量名2) {
	// 当产生指定类型(包括子类)的异常时的处理措施
} 
...
finally {  //finally根据情况可加可不加
	//无论是否发生异常,都会执行的语句
}
//其他代码

☃ 捕获异常的第一步是用try{…}语句块选定捕获异常的范围,将可能出现异常的代码放在try语句块中。一旦出现异常,就会生成一个对应的异常类的对象,根据此对象的类型,去catch中进行匹配。

☃ 在catch(Exceptiontype e)语句块中是对异常对象进行处理的代码。每个try语句块可以伴随一个或多个catch语句,用于处理可能产生的不同类型的异常对象。

☃ 一旦try中的异常匹配到某一个catch时,就进入catch中进行异常的处理,一旦处理完成跳出当前的try-catch结构,继续向下执行其后的代码。

↪ 如果明确知道产生的是何种异常,可以用该异常类作为catch的参数;也可以用其父类作为catch的参数。

比如 :可以用 ArithmeticException类作为参数的地方,就可以用RuntimeException类作为参数,或者用所有异常的父类Exception类作为参数。但不能是与ArithmeticException类无关的异常,如NullPointerException(catch中的语句将不会行)。

☃ catch中的异常类型如果没有子父类关系,则声明顺序没有影响,如果catch中异常类型如果满足子父类关系,则要求子类一定声明在父类的上面,否则报错。

捕获异常的有关信息:

☃ 与其它对象一样,可以访问一个异常对象的成员变量或调用它的
方法。

➣ getMessage()获取异常信息,返回字符串

➣ printStackTrace() 获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。

public static void tryCatchFinally() {
	try {
		//可能产生异常的代码
		 int arr[] = new int[3];
		 System.out.println("开始执行");
		 arr[4] = 232;
		 //出现异常后,{}其后的代码不再执行
		 System.out.println(arr[4]);
	}catch (NumberFormatException e) {
		//数字格式异常
		System.out.println("数字格式异常");
	}
	/*子类异常一定声明在父类的上面
	  catch (IndexOutOfBoundsException e) {
	  }*/
	catch (ArrayIndexOutOfBoundsException e) {
		// 当产生指定类型(包括子类)的异常时的处理措施
		System.out.println("数组越界异常");
		//异常对象中的getMessage()方法,返回字符串类型的异常信息
		System.out.println("getMessage方法:" + e.getMessage());
		//异常对象中的printStackTrace()方法
		e.printStackTrace();   //控制台输出详细的异常信息
	} catch (Exception e) {
		System.out.println("出现异常");
	}finally {
		//无论是否发生异常,都会执行的语句
		System.out.println("ArrayIndexOutOfBoundsException Test");
	}
	//其他代码
    //try-catch中定义的变量出了try-catch范围就不能使用了,除非将变量定义在try-catch前面
	//arr[1] = 10; 
	System.out.println("orther code");
}

输出结果:

✦ 使用try-catch-finally处理编译时异常,使得程序在编译时就不再报错,但是在运行时仍可能报错使用try-catch-finally相当于将一个编译时可能出现的异常,延迟到运行时出现。

finally:

☃ finally语句和catch语句是任选的,但必须至少存在一个。

☃ 捕获异常的最后一步是通过finally语句为异常处理提供一个统一的出口,使得在控制流转到程序的其它部分以前,能够
对程序的状态作统一的管理。

☃ 不论在try代码块中是否发生了异常事件,catch语句是否执行,catch语句中是否有异常,catch语句中是否有return,finally块中的语句都会被执行。

☃ 由于finally一定会执行,所以若方法有返回值,finally中如果也定义了返回值,则会覆盖try-catch中的返回值

@SuppressWarnings("finally")
public int test1() {
	try {
		int a = 23;
		int b = 0;
		System.out.println(a / b);
		return 0;
	} catch (ArithmeticException e) {
		e.printStackTrace();
		return -1;
	} catch (Exception e) {
		e.printStackTrace();
		return -1;
	} finally {
		System.out.println("执行到最后了");
		//返回值会覆盖try-catch中的返回值
		return 1;
	}
}

输出结果:

☃ 由于JVM无法自动回收类似数据库链接、输入输出流、网络编程Socket等的资源,我们需要手动关闭释放资源,这时定义在finally中最为合适(防止因为异常影响导致代码未执行)

public void test2() {
	//定义在try-catch前面,方便try-catch后面的使用
	FileInputStream fis = null;
	try {
		File file = new File("abc.txt");
		fis = new FileInputStream(file);
		int data = fis.read();
		while(data != -1) {
			System.out.print((char)data);
			data = fis.read();
		}
	} catch (FileNotFoundException e) {
		e.printStackTrace();
	}catch (IOException e) {
		e.printStackTrace();
	}finally {
		//关闭流,try-catch可以嵌套使用
		try {
			if(fis != null)  //避免空指针
				fis.close();
		} catch (IOException e2) {
			e2.printStackTrace();
		}
	}
    System.out.println("end");
}

捕获异常vs不捕获异常

☃ RuntimeException类或是它的子类,这些类的异常的特
点是:即使没有使用try和catch捕获,Java自己也能捕获,并且编译通过(但运行时会发生异常使得程序运行终止)。

☃ 如果抛出的异常是IOException等类型的非运行时异常,则必须捕获,否则编译错误。也就是说,我们必须处理编译时异常,将异常进行捕捉,转化为运行时异常。


☍ 异常处理机制二:throws抛出异常

声明抛出异常

☃ 声明抛出异常是Java中处理异常的第二种方式

☄ 如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这
种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。

☄ 在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类 。

☃ throws抛出异常得方式写在方法声明初处,指明此方法执行时,可能会抛出的异常类型一旦方法体运行时出现异常,仍会在代码处生成一个异常对象并且方法体中的代码不会继续向下执行,此对象满足throws异常类型时,就会抛出,方法体内,异常后面的代码不再执行

☃ 如果一个方法内抛出异常,该异常对象会被抛给调用者方法中处理。如果异常没有在调用者方法中处理,它继续被抛给这个调用方法的上层方法。这个过程将一直继续下去,直到异常被处理或最终抛给JVM虚拟机。如果一个异常回到main()方法,并且main()也不处理,则程序运行终止。

↪ try-catch-finally:正真的处理了异常;throws 异常:只是将异常抛给了调用者,并没有真正处理异常

public class ThrowsException {
	static FileInputStream fis = null;
	public static void main(String[] args) {
		ThrowsException t = new ThrowsException();
		//调用者处理异常
		try {
			t.getReadFile();;
		} catch (IOException e) {
			System.out.println("出现异常");
			e.printStackTrace();
		}finally {
			//关闭io流
			try {
				if(fis != null)
				fis.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			System.out.println("finally");
		}
	}
	//调用者继续抛出异常,IOException是FileFoundException父类,若对于两个异常不做区分处理,则抛出父类异常即可
	public void getReadFile() throws  IOException {
		String filePath = "abc.txt";
		readFile(filePath);
	}
	//声明抛出异常
	public void readFile(String filePath) throws FileNotFoundException,IOException{
		File file = new File(filePath);
	    // 读文件的操作可能产生FileNotFoundException 类型的异常
		fis = new FileInputStream(file);
		int data = fis.read();
		while(data != -1) {
			System.out.print((char)data);
			data = fis.read();
		}
		System.out.println("方法体末尾");
	}
}

输出结果:

抛出异常过程

重写方法声明抛出异常的原则

☃ 重写方法不能抛出比被重写方法范围更大的异常类型。在多态的情况下,对methodA()方法的调用-异常的捕获按父类声明的异常处理。

☃ 如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws抛出异常,意味着此时如果子类重写的方法中有异常,必须使用try-catch-finally方式处理。

public class A {
	public void methodA() throws IOException {……} 
    public void methodB() {……}
}
public class B1 extends A {
	public void methodA() throws FileNotFoundException {……} 
    //public void methodB() throws FileNotFoundException {……} 报错
}
public class B2 extends A {
	public void methodA() throws Exception { // 报错……} 
    public void methodB(){
    	try{...}
        catch(ExcptionType e){...}
        finally{...}
    }
}

异常处理方式的选择

执行的方法A中,先后又调用了另外几个方法,这几个方法又递进的关系(如:方法A调用方法B,B需要得到方法C中的数据,方法C需要得到方法D中的数据),此时建议这几个被调用的方法使用用throws抛出异常,而执行的方法A考虑使用try-catch-finally方式处理,这样可以保证在执行到A方法时,其他被调用的方法如果发生异常不会被处理掉而影响A的执行,同时便于异常的集中处理。

try-catch-finally异常处理方式和throws异常处理方式只能二选一使用,不能同时使用,因为同时使用,由于try-catch-finally已经将异常处理了,再抛出异常就没意义了。


☍ 手动抛出异常

Java异常类对象除在程序执行过程中出现异常时由系统自动生成并抛出(以上都是JVM自动生成),也可根据需要使手动创建并抛出异常。

☃ 首先要生成异常类对象,然后通过throw语句实现抛出操作(提交给Java运
行环境)。

public class ThrowTest {
	public static void main(String[] args) {
		ThrowTest t = new ThrowTest();
		try {
			t.method(-2);
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
	}
	public void method(int num) throws Exception{
		int i = 0;
		if(num >= 0) {
			i = num;
		}else {
			throw new Exception("数字不能小于0");
		}	
	}
}

☍ 用户自定义异常类

☃ 一般地,用户自定义异常类都是RuntimeException的子类

☃ 自定义异常类通常需要编写几个重载的构造器

☃ 自定义异常需要提供serialVersionUID序列号

☃ 自定义的异常通过throw抛出

☃ 自定义异命名必须要规范,能够见名知意,当异常出现时,可以根据名字判断异常类型

☃ 用户自己的异常类必须继承现有的异常类,通常是Exception & RuntimeException

public class MyExceptionTest {
	public void regist(int num) throws MyException {
		if (num < 0)
			throw new MyException("人数为负值,不合理", 3);
		else
			System.out.println("登记人数" + num);
	}
	public void manager() {
		try {
			regist(-1);
		} catch (MyException e) {
			System.out.println("登记失败," + e.getMessage());
			System.out.print("出错种类" + e.getId());
		}
		System.out.print("本次登记操作结束");
	}
	public static void main(String args[]) {
		MyExceptionTest t = new MyExceptionTest();
		t.manager();
	}
}

class MyException extends RuntimeException {
     // 自定义异常序列标识号,用于区分异常
	static final long serialVersionUID = 14232463463463535L;
	private int idnumber;
	public MyException() {}
	public MyException(String message) {
		super(message);
	}
	public MyException(String message, int id) {
		super(message);
		this.idnumber = id;
	}
	public int getId() {
		return idnumber;
	}
}

输出结果:


☍ 总结


本博客与CSDN博客༺ཌ༈君☠纤༈ད༻同步发布

posted @ 2020-03-26 00:54  ༺ཌ༈君☠纤༈ད༻  阅读(1625)  评论(0编辑  收藏  举报