JAVA SE(十七)—— JAVA 异常Exception(异常结构图、throw关键字、throws关键字、try...catch...finally捕获异常)

一、Java异常层次结构图

在 Java 中,所有的异常都有一个共同的祖先 Throwable(可抛出),Throwable: 有两个重要的子类:Exception(异常)和 Error(错误)

二、Java异常分类

如上图,Java异常可分为两类:错误Error和异常Exception,其中异常Exception又分为编译时异常和运行时异常RunTimeException。

1、错误Error

  • 错误,是程序无法处理的错误,表示运行应用程序中较严重问题。
  • 大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。它可能源于程序的bug,但一般更可能源于环境问题,如内存耗尽。
  • 错误在程序中无须处理,而有运行环境处理。
  • 常见异常如:
    OutOfMemoeyErroe:Java heap space 堆内存溢出 一般常见于系统错误,没法解决,只能修改代码。
    StackOverLowError 栈内存溢出。

2、异常Exception
异常Exception是程序本身可以处理的异常。Exception 类有一个重要的子类 RuntimeException表示运行时异常,RuntimeException 类及其子类表示“JVM 常用操作”引发的错误。
(1)运行时异常

  • 运行时异常都是RuntimeException类及其子类异常,这些异常一般是由程序逻辑错误引起的,这意味着程序存在bug,如数组越界,0被除,入参不满足规范… 这类异常需要更改程序来避免,Java编译器强制要求处理这类异常,程序应该从逻辑角度尽可能避免这类异常的发生。
  • 运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常, 即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

(2)编译时异常

  • 编译时异常(Java.lang.Exception),是RuntimeException以外的异常,类型上都属于Exception类及其子类。一般是由于程序正确,但因为外在的环境条件不满足引发。
  • 例如:用户错误及I/O问题----程序试图打开一个并不存在的远程Socket端口。 这不是程序本身的逻辑错误,而很可能是远程机器名字错误(用户拼写错误)。 对商用软件系统,程序开发者必须考虑并处理这个问题。
  • Java编译器强制要求处理这类异常,如果不捕获这类异常,程序将不能被编译。

(3)编译时异常和运行时异常的联系

  • Java.lang.Exception和Java.lang.Error继承自Java.lang.Throwable。
  • Java.lang.RuntimeException继承自Java.lang.Exception。

三、Java异常处理

1、抛出异常(throw、throws)
将异常抛给调用者,让调用者处理。
当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统, 异常对象中包含了异常类型和异常出现时的程序状态等异常信息。 运行时系统负责寻找处置异常的代码并执行。
(1)throw
① throw用于方法的内部,任何Java代码都可以抛出异常,throw语句抛出异常。throw后面跟异常类对象。
② 使用格式:

throw  new 异常类名(“异常信息”); 
例如:
throw new NullPointerException("空指向异常");

throw总是出现在函数体中,用来抛出一个Throwable类型的异常,程序会在throw语句后立即终止,它后面的语句执行不到。
③ throwDemo1

public class throwDemo1{
	public static void main(String[] args) {
		int[] arr = null; //注意这里的定义,现在是null
		int j = getArr(arr, 3);
		System.out.println(j);
	}
	//返回int[]数组,下标值又用户传入参数的元素+1
	public static int getArr(int[] arr,int index) {
		if( arr == null ) {
			throw new NullPointerException("空指向异常");
		}else if ( arr.length < index ) {
			throw new ArrayIndexOutOfBoundsException("下标越界异常");
		}
		int i = arr[index];
		return i + 1;
	}
}

int[] arr = null;语句中,数组定义为null,在getAr()方法中,if判断结构检测到数组arr为null,执行相应的代码块:throw new NullPointerException(“空指向异常”); 抛出异常NullPointerException,并在输出结果中显示“空指向异常”,所以输出结果为:

Exception in thread "main" java.lang.NullPointerException: 空指向异常
	at com.woniuxy.onclass0401.Test1.getArr(Test1.java:11)
	at com.woniuxy.onclass0401.Test1.main(Test1.java:5)

④ throwDemo2

public class throwDemo2{
	    public static void main(String[] args) {
		int[] arr = {}; //注意这里的定义
		int j = getArr(arr, 3);
		System.out.println(j);
	}
	//返回int[]数组,下标值又用户传入参数的元素+1
	public static int getArr(int[] arr,int index) {
		if( arr == null ) {
			throw new NullPointerException("空指向异常");
		}else if ( arr.length < index ) {
			throw new ArrayIndexOutOfBoundsException("下标越界异常");
		}
		int i = arr[index];
		return i + 1;
	}
}

代码同Demo1一样,只是在语句int[] arr = {};中,数组定义为{},所以在getAr()方法中,if判断结构检测到数组arr为{},执行相应的代码块:throw new ArrayIndexOutOfBoundsException(“下标越界异常”); 抛出异常ArrayIndexOutOfBoundsException,并在输出结果中显示“下标越界异常”,所以输出结果为:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 下标越界异常
	at com.woniuxy.onclass0401.Test1.getArr(Test1.java:13)
	at com.woniuxy.onclass0401.Test1.main(Test1.java:5)

这里需要注意的是:在语句throw抛出异常的语句中,如throw new NullPointerException(“空指向异常”);和throw new ArrayIndexOutOfBoundsException(“下标越界异常”); new后面接的异常类型可以自己随意设置,这个可以是API里面有的,也可以是自定义的,括号里面的内容也是自己定义的。

(2)throws
① 如果一个方法可能会出现异常,但没有能力处理这种异常,可以在方法声明处用throws子句来声明抛出异常。 方法名后的throws Exception1,Exception2,…,ExceptionN 为声明要抛出的异常列表,多个异常使用逗号分割。
② 使用格式:

方法名()  throws 异常类名;
例如:
public static void main(String[] args) throws Exception {

}

③ throwsDemo

public class throwsDemo{
	//计算圆的面积,如果半径是0或者负数,抛出异常
	public static void main(String[] args) throws Exception {
		System.out.println("输入x的值:");
		Scanner sc = new Scanner(System.in);
		double x = sc.nextDouble();
		double s1 = area(x);
		System.out.println(s1);
	}
	public static double area(double x) throws Exception {
		if (x == 0) {
			throw new Exception("x的值不能为0");
		} else if ( x < 0 ) {
			throw new Exception("x的值不能为0负数");
		}
		double s = 3.14 * x * x;
		return s;
	}
}

throws语句用在方法定义时声明该方法要抛出的异常类型,如果抛出的是Exception异常类型,则该方法被声明为抛出所有的异常。

当方法抛出异常列表的异常时,方法将不对这些类型及其子类类型的异常作处理,而抛向调用该方法的方法,由他去处理。
④ throws后面跟异常类。规则如下:

  • 如果是不可查异常(unchecked exception),即Error、RuntimeException或它们的子类, 那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。
  • 必须声明方法可抛出的任何可查异常(checked exception)。 即如果一个方法可能出现受可查异常,要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误。
  • 仅当抛出了异常,该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出。
  • 调用方法必须遵循任何可查异常的处理和声明规则。若覆盖一个方法,则不能声明与覆盖方法不同的异常。 声明的任何异常必须是被覆盖方法所声明异常的同类或子类。

2、捕捉异常(try…catch…finally)
1、概念
捕捉异常是在发生异常的地方直接处理,通过try-catch语句或者try-catch-finally语句实现。

2、处理格式:
(1) 单个异常时

try {
    可能会出异常的代码(受监测代码);
} catch ( 异常类 变量名) {
    捕获到异常后执行的代码;  
} finally {      
    必须执行的内容(没有可不写);
}

(2)多个异常时
如果出现多个异常时,两个异常之间存在继承关系,则父类必须放在后面

try {
    可能会出异常的代码(受监测代码);
} catch ( 异常类 变量名) {
    捕获到异常后执行的代码;  
}  catch ( 异常类 变量名) {
    捕获到异常后执行的代码;  
}...... 

finally {      
    必须执行的内容(没有可不写);
}

(3)其他格式

try {
    可能会出异常的代码(受监测代码);
} finally {      
    必须执行的内容(没有可不写);
}

(4)图解执行顺序

  • 当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句。
  • 当try捕获到异常,catch语句块里没有处理此异常的情况: 此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行。
  • 当try捕获到异常,catch语句块里有处理此异常的情况: 在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到catch语句块, 并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行, 而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句。
  • finally的作用:主要用于断开连接或关流。

(5)捕获异常示例

public class Demo{
	//异常的处理:try...catch...finally
	public static void main(String[] args) throws Exception  {
		//对异常进行处理
		try {
			double avg = area( 1,2,3,4,5,6 );		//可能会出现异常的代码(受监测的代码)
			System.out.println( "平均值=" + avg );
		} catch (Exception e) {
			System.out.println(e);		//捕获到异常后执行的代码
			System.out.println( "捕获到异常" );
		} finally {	//必须执行的代码(没有可以不写)
			System.out.println( "必须执行的代码" );
		}
		System.out.println( "结束代码" );
	}
	public static double area(double...arr) throws Exception  {
		double  sum = 0;
		for( double a : arr ) {
			if ( a < 0 ) {
				throw new Exception("x的值不能为0负数");
			} 
			sum = sum + a;
		}
		return  sum / arr.length;
	}
}

四、自定义异常

(1)用户自定义异常类,只需继承Exception类即可。

(2)自定义步骤:

  • 创建自定义异常类。
  • 在方法中通过throw关键字抛出异常对象。
  • 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作。
  • 在出现异常方法的调用者中捕获并处理异常。

(3)自定义异常示例
① 首先创建一个Student类,其拥有属性姓名,年龄,分数。在Student的有参构造函数里面判断传入的年龄和分数,根据条件抛出相应的异常,我们知道人的年龄不可能小于0,一般也不可能大于200,分数也不能小于0,所以在这里自定义两个异常NotAgeException和IgnoralScoreException。

//创建一个Student类,拥有属性姓名、年龄、分数等。
public class Student {
	String name;
	int age;
	double score;

	//无参构造函数
	public Student() {
		super();
	}
	//有参构造函数
	public Student(String name, int age, double score) throws NotAgeException, IgnoralScoreException {
    //判断年龄,抛出相应异常
		if ( age <= 0 || age > 200 ) {
			throw new NotAgeException("年龄不合法");
		}else if ( score < 0 ) {
			throw new IgnoralScoreException("分数错误");
		}
		this.name = name;
		this.age = age;
		this.score = score;
	}
}

② 因为NotAgeException和IgnoralScoreException是我们自己定义的异常,在API中是没有的,所以需要新建这两个异常类去继承Exception。

//NotAgeException 类
public class NotAgeException extends  Exception{
	public NotAgeException() {}
	public NotAgeException(String message) {
		super(message);
	}
}

//IgnoralScoreException 类
public class IgnoralScoreException extends Exception{
	public IgnoralScoreException( ) { }
	public IgnoralScoreException(String s) {
		super(s);
	}
}

③ 最后,创建main函数,通过传参和调用实现功能。挡在主函数中传入的年龄和分数满足Student类中的判断条件时,就会抛出相应的异常。

//main 函数类
public class Demo{
	public static void main(String[] args) throws NotAgeException, IgnoralScoreException {
		Student s = new Student("张三",18,30);
		System.out.println("name = " + s.name + "  age = " + s.age + "  score = " + s.score );
	}
}
发布了40 篇原创文章 · 获赞 0 · 访问量 357

猜你喜欢

转载自blog.csdn.net/baidu_27414099/article/details/104425212
今日推荐