Java异常处理

目录

一、异常概述与异常体系结构

1.1 Java 引入异常的原因:

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

1.2 异常的概念:

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

1.3 Java中的异常事件的分类:

  • Error:Java 虚拟机无法解决的严重问题。

例如:JVM系统内部错误、资源 耗尽等严重情况。比如:StackOverflowError 和 OOM。一般不编写针对性的代码进行处理

  • Exception(狭义的异常,Java中所讲的异常处理即处理 Exception):其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。

例如:空指针访问;试图读取不存在的文件;网络连接中断;数组角标越界。

  • 对于这些错误,一般有两种解决方法:

    一是遇到错误就终止程序的运行。

    另一种方法是由程序员在编写程序时,就考虑到错误的检测、错误消息的提示,以及错误的处理。捕获错误最理想的是在编译期间,但有的错误只有在运行时才会发生。 所以捕获的异常也分为:编译时异常和运行时异常

    比如:除数为0,数组下标越界等。

1.运行时异常:

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

对于这类异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。

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

对于这类异常,如果程序不处理,可能会带来意想不到的结果。
  • 所有的异常类是从 java.lang.Exception 类继承的子类。

    Exception 类是 Throwable 类的子类。除了Exception类外,Throwable 还有一个子类Error 。

二、常见异常

异常 中文介绍 描述
ArithmeticException 运算异 常 当出现异常的运算条件时,抛出此异常。例如,一个整数”除以零”时,抛出此类的一个实例。
InputMismatchException 输入不匹配异常 当用户输入的数据类型与程序定义的数据类型不匹配时所报的异常。
NumberFormatException 数据格式异常 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
ClassCastException 类型转换异常 当试图将对象强制转换为不是实例的子类时,抛出该异常。
ArrayIndexOutOfBoundsException 数组下标越界异常 用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。
StringIndexOutOfBoundsException 字符串下标越界异常 此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。
NullPointerException 空指针异常 当应用程序试图在需要对象的地方使用 null 时,抛出该异常。
public class ExceptionTest {
	
	//******************以下是编译时异常***************************
	@Test
	public void test7(){
//		File file = new File("hello.txt");
//		FileInputStream fis = new FileInputStream(file);
//		
//		int data = fis.read();
//		while(data != -1){
//			System.out.print((char)data);
//			data = fis.read();
//		}
//		
//		fis.close();
		
	}
	
	//******************以下是运行时异常***************************
	//ArithmeticException
	@Test
	public void test6(){
		int a = 10;
		int b = 0;
		System.out.println(a / b);
	}
	
	//InputMismatchException
	@Test
	public void test5(){
		Scanner scanner = new Scanner(System.in);
		int score = scanner.nextInt();
		System.out.println(score);
		
		scanner.close();
	}
	
	//NumberFormatException
	@Test
	public void test4(){
		
		String str = "123";
		str = "abc";
		int num = Integer.parseInt(str);
		
		
		
	}
	
	//ClassCastException
	@Test
	public void test3(){
		Object obj = new Date();
		String str = (String)obj;
	}
	
	//IndexOutOfBoundsException
	@Test
	public void test2(){
		//ArrayIndexOutOfBoundsException
//		int[] arr = new int[10];
//		System.out.println(arr[10]);
		//StringIndexOutOfBoundsException
		String str = "abc";
		System.out.println(str.charAt(3));
	}
	
	//NullPointerException
	@Test
	public void test1(){
		
		String str = "abc";
		str = null;
		System.out.println(str.charAt(0));
		
	}
}

三、Java 异常处理机制:

3.1 简介:

  1. Java 提供的是异常处理的抓抛模型

    过程一:”抛”:

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

    过程二:”抓”:

    可以理解为异常的处理方式:① try-catch-finally ② throws

  2. 异常对象的生成:

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

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

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

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

3.2 Java 异常处理机制之一:try-catch-finally

  1. 语法格式:
	try{
 		//	可能出现异常的代码
	}catch(异常类型1 变量名1){
 		//	处理异常的方式1
	}catch(异常类型2 变量名2){
		//	处理异常的方式2
	}catch(异常类型3 变量名3){
 		//	处理异常的方式3
	}
	....//	注意:省略号表示其他catch语句,而不是所有语句
	finally{
		//	一定会执行的代码(finally可选)
	}
  1. 说明:

    (1)finally 是可选的。

    (2)使用 try 将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配。且 try 里出现异常语句后的语句不再执行。

    (3)一旦 try 中的异常对象匹配到某一个 catch 时,就进入 catch 中进行异常的处理。一旦处理完成,就跳出当前的 try-catch 结构(在没有写finally的情况),继续执行其后的代码。

    (4)catch 中的异常类型如果没有子父类关系,则声明顺序无所谓。

    catch 中的异常类型如果满足子父类关系,则要求子类一定声明在父类的提前声明。否则会报错。

    (5)常用的异常对象处理的方式:

    ① String getMessage()

    ② printStackTrace()

    (6)在 try 结构中声明的变量,再出了 try 结构以后,就不能再被调用.

    (7)try-catch-finally 结构可以嵌套。

    (8)try-catch-finally 结构一定是连在一起的。比如 try{} 、catch{}、finally{} 之间不能有其他的语句。

    (9)finally中声明的是一定会被执行的代码。即使catch中又出现异常了,try中有return语句,catch中有return语句等情况。

    (10)像数据库连接、输入输出流、网络编程Socket等资源,JVM是不能自动的回收的,我们需要自己手动的进行资源的释放。此时的资源释放,就需要声明在finally中。

  2. 例子1:

public class FinallyTest {
	
	@Test
	public void test(){
		FileInputStream fis = null;
		try {
			File file = new File("hello.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-fianlly里可以嵌套使用
			try {
				if(fis != null)
					fis.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	
	@Test
	public void testMethod(){
		int num = method();
		System.out.println(num);
	}
	
	public int method(){
		
		try{
			int[] arr = new int[10];
			System.out.println(arr[10]);
			return 1;
		}catch(ArrayIndexOutOfBoundsException e){
			e.printStackTrace();
			return 2;
		}finally{
			System.out.println("一定会被执行");
			return 3;	// 当没有此语句是num输出为 2,当有此语句时输出为 3。
		}	
	}
	
}

体会:

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

开发中,由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally了。针对于编译时异常,我们说一定要考虑异常的处理。

例子2:

public class ReturnExceptionDemo {
	
    static void methodA() {
		try {
			System.out.println("进入方法A");
			throw new RuntimeException("制造异常");	// 创建了一个异常对象,但没被捕获和输出
		}finally {
			System.out.println("调用A方法的finally");
		}
	}
    
	static void methodB() {
		try {
			System.out.println("进入方法B");
			return;
		} finally {
			System.out.println("调用B方法的finally");
		}
	}
    
	public static void main(String[] args) {
		try {
			methodA();		// 输出:进入方法A && 调用A方法的finally
		} catch (Exception e) {
			System.out.println(e.getMessage());	// 输出:制造异常
        }
        	methodB();	// 输出:进入方法B && 调用B方法的finally
        }
}

/*	运行结果:
进入方法A
用A方法的finally
制造异常
进入方法B
调用B方法的finally
*/

3.3 Java 异常处理机制之二:throws + 异常类型

  1. 语法格式:
public void method() throws 异常类型{
		
}
  1. 说明:

(1) “throws + 异常类型”写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足 throws 后异常类型时,就会被抛出。异常代码后续的代码,就不再执行

(2)try-catch-finally:真正的将异常给处理掉了。

throws的方式只是将异常抛给了方法的调用者。并没有真正将异常处理掉。

(3)开发中如何选择使用 try-catch-finally 还是使用 throws ?

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

  • 执行的方法 a 中,先后又调用了另外的几个方法,这几个方法是递进关系执行的。我们建议这几个方法使用 throws 的方式进行处理。而执行的方法 a 可以考虑使用 try-catch-finally 方式进行处理。

四、手动抛出异常(throw)

注意:throw 后没有 s。

public class StudentTest {
	
	public static void main(String[] args) {
		try {
			Student s = new Student();
			s.regist(-1001);
			System.out.println(s);
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
	}
}


class Student{
	
	private int id;
	
	public void regist(int id) throws Exception {
		if(id > 0){
			this.id = id;
		}else{
//			System.out.println("您输入的数据非法!");
			//	手动抛出异常对象
			throw new RuntimeException("您输入的数据非法!");
//			或者 throw new Exception("您输入的数据非法!");

		}	
	}
}

简述 throw 和 throws 的区别:

throw:表示抛出一个异常类的对象,生成异常对象的过程。声明在方法体内。

throws:属于异常处理的一种方式,声明在方法声明处。

五、用户自定义异常类

如何自定义异常类?

  1. 继承于现有的异常结构:RuntimeException、Exception。

  2. 提供全局常量:serialVersionUID。(序列化版本号,用来唯一识别自定义异常类)。

  3. 提供几个重载的构造器。

  4. 自定义异常最重要的是异常类的名字,当异常出现时,可以根据名字判断异常类型。

例子:

//自定义异常类
public class EcDef extends Exception {

	static final long serialVersionUID = -33875164229948L;	// 序列化版本号

	public EcDef() {	// 重载的构造器
	}

	public EcDef(String msg) {	// 重载的构造器
		super(msg);
	}
}
------------------------------------------------------------------------------------------------------------
public class EcmDef {
	public static void main(String[] args) {
		try{
			int i = Integer.parseInt(args[0]);
			int j = Integer.parseInt(args[1]);
			
			int result = ecm(i,j);
			
			System.out.println(result);
		}catch(NumberFormatException e){
			System.out.println("数据类型不一致");
		}catch(ArrayIndexOutOfBoundsException e){
			System.out.println("缺少命令行参数");
		}catch(ArithmeticException e){
			System.out.println("分母不能为0");
		}catch(EcDef e){
			System.out.println(e.getMessage());
		}
		
	}
	
	public static int ecm(int i,int j) throws EcDef{
		if(i < 0 || j < 0){
			throw new EcDef("分子或分母不能负数");
		}
		return i / j;
	}
}