Java中的异常处理:try-catch语句与throw/throws关键字

在程序中为什么要有异常处理

异常:
程序在运行期间发生的不正常事情,它会打断指令的正常流程;
异常都是出现在程序的运行期,编译出现的问题叫语法错误。

程序很难做到完美,会存在各种bug,为了解决这些异常,我们需要知道知道异常发生的原因;
当程序运行发生错误的时候,就会抛出异常,这时我们可以设置一个捕捉异常的“处理器”对异常情况进行监视处理;

异常类型分为未检查异常和已检查异常,对于已检查异常,编译器会检查是否设置了处理器;
但是未检查异常,需要我们设置“处理器”以便使程序更好地运行。
下面在异常的分类中会详细介绍两种异常。

如何设置异常处理

c语言中的异常处理是通过“return”语句函数返回值实现的,因此我们需要很长时间才能找到程序错误的原因。
而在python,C++,Java等面对对象的语言中,我们有更复杂的异常处理机制。
当异常发生的时候,java会抛出(throw)代表当前状况的对象,然后该线程停止,逐层退出方法调用,可以理解为一种局部返回机制,直到有异常处理器可以捕捉(catch)的异常对象的位置。
Java的的异常处理语句分为两部分:

try{
	包含可能发生异常的语句;
}

catch(ExceptionSubClass1 e) {
	……
}

catch(ExceptionSubClass2 e) {
	……
}

异常处理器监视try后面的程序块,catch用于捕捉异常,括号中的参数对应它能够捕捉的类型及衍生类。
因为try中的内容不一定只含有一种错误类型。所以一个try语句可以对应许多catch语句。
所以我们把可能出现异常,需要监视的程序段放入try中,而在catch中设计应对异常的方案。
异常对象可以调用如下方法得到或输出有关异常的而信息:

public String getMessage();
public void printStackTrace();
public String toString();

事实上,每个catch参数中的异常类都是Exception的某个子类,表明try部分可能发生的异常,这些子类之间不能有父子关系,否则保留一个含有父类参数的catch即可,因为子类的异常类型也被包括在父类当中。

我们还可以在try语句和catch语句后面添加finally语句,无论是否发生异常,其内部的程序都会被执行:

finally{
	System.out.println("即使发生异常,我也要出来耍耍");
	System.out.println("我通常出来打扫卫生,清理资源");
}

下面的例子c在赋值时出现了NumberFormatException,我们通过toString()语句输入产生异常赋值的内容。

public class example {
	public static void main(String args[]){
		int a=0,b=0,c=6666;
		try {
			a=Integer.parseInt("1234");
			b=Integer.parseInt("ye99");
			c=5678;
		}
		catch(NumberFormatException e) {
			System.out.println("发生异常:"+e.toString());
		}
		finally{
			System.out.println("a="+a+",b="+b+",c="+c);
			System.out.println("b的赋值异常导致程序中断,c没有被赋值");
			System.out.println("但是我仍然会被执行");
		}
	}
}

运行结果:
在这里插入图片描述

异常有哪几种类型

两种异常及可采取的处理方式:
未检查异常(unchecked)/运行时异常(RuntimeException)
处理方式:

  1. 捕获
  2. 抛出
  3. 不处理

检查异常(checked Exception):

  1. 捕获
  2. 抛出

(未检查异常可以不处理)

异常的处理机制:

1.当程序在运行时出现了异常,JVM自动创建一个该类型的异常对象。同时把这个异常对象交给运行时系统。(抛出异常)
2.运行时系统接收到一个异常对象时,它会在产生异常的代码附近查找相应的处理方式。
3.异常的处理方式有两种:
(1)用try/catch/finally语句。
捕获并处理:在异常的代码附近显示用try/catch语句处理。
可以和throw(与throws不同)配套使用。
(2)用throws关键字,向外声明。 …方法名(参数列表) throws 异常类型1,异常类型2
查看发生的异常的方法是否有向上声明异常,有向上声明异常,向上级查询处理语句;
如果没有向上声明,JVM中断程序的运行并处理,用throws向外声明,声明本方法可能会抛出的异常列表。
throws可以声明方法可能会抛出一个或多个异常,异常之间用’,'隔开。
向上声明是告诉本方法的调用者,在使用本方法时,应该对这些异常进行处理。
如果声明的可能会抛出的异常是非受检的,该方法的调用者可以处理也可以不处理。
如果声明的可能会抛出的异常是受检的,该方法的调用者必须进行处理。
在这里插入图片描述

Java中的异常类型都继承于Trowable类,所有Trowable的对象都可以被抛出(throw);

Throwable对象可以分为两组。

第一组是图中绿色部分,异常处理机制往往不用于这组异常,包括:
Error类通常是指Java的内部错误以及资源耗尽的错误。当Error(及其衍生类)发生时,我们不能在编程层面上解决Error,所以应该直接退出程序。(无能为力)
Exception类有特殊的一个衍生类RuntimeException。RuntimeException(及其衍生类)是Java程序自身造成的,也就是说,.这类异常是编程人员的逻辑问题,应该承担责任,但是Java编译器不进行强制要求处理。这类异常再程序中,可以进行处理,也可以不处理。但是我们要最好通过修正Java程序避免。(可以避免)

第二是图中黄色部分中的checkedException,异常处理机制往往用于这组异常。
这类异常是程序和环境发生互动导致在运行时出错,即程序和用户之间交互时产生的异常。

文件系统和网络服务器是在Java环境之外的,并不是程序员所能控制的。
比如读取文件时,由于文件本身有错误,发生IOException。
再比如网络服务器临时更改URL指向,造成MalformedURLException。

但是程序员可以预期异常,并且利用异常处理机制来制定应对预案。
比如文件出问题时,提醒系统管理员。
再比如在网络服务器出现问题时,提醒用户,并等待网络服务器恢复。(未雨绸缪)

抛出异常throw与throws的比较

手动抛出一个异常:当程序逻辑不符合期望时,要中止后面代码的执行时。
1.位置不同,throw放在函数体中,throws放在方法函数头后面;throw是语句抛出一个异常,throws是方法抛出一个异常。
2.可能性不同,throw一定会抛出异常的对象,而throws仅仅代表此方法函数出现此类异常的可能性。
3.抛出,都是消极的异常处理方式,不是由此函数直接处理异常,而是由函数的上层调用处理。
4.throw不能单独使用,不是和try-catch-finally配套使用,就是与throws配套使用,因为它如果抛出了异常对象,就要得到处理;但throws可以单独使用,然后再由异常处理的方法捕获,因为它表明出现异常的可能。

注意:
如果手动抛出的是受检异常,那么本方法必须进行处理(应该采用向上声明这个异常);
如果手动抛出的是非受检异常,那么可以进行处理,也可以不处理。

下面这个例子是运用throw的,throw后面的语句需要注释掉,否则会出现代码不可达的错误,我们通过getMessage()语句输出发生异常的内容。

public class example {
	public static void main(String args[]){
		try{
			System.out.println("故意抛出I/O异常!");
			throw new java.io.IOException("我是故意的");
			//System.out.println("这个输出语句肯定没机会执行,必须注释,否则编译出错");
		}
		catch(java.io.IOException e){
			System.out.println("发生异常:"+e.getMessage());
		}
	}
}

下面这个例子是运用throws的,getHeight需要的是具体的身高数字,而“verytall”是英文,在赋值时出现了NumberFormatException,我们通过toString()语句输出异常的内容。

public class example {
	public static void main(String args[]){
		Person person=new Person();
		try{
			person.getHeight();
		}
		catch(NumberFormatException e){
			System.out.println(e.toString());
		}
	}
}

class Person{
	String height="verytall";
	void getHeight() throws NumberFormatException{
		System.out.println(Double.parseDouble(height));
	}
}

如何自定义异常

因为在程序设计中我们需要使程序符合生活逻辑:
比如杯子满了水就无法再加水了,手机电量百分之零的时候无法被使用。
但是程序并不知道,所以需要我们自定义异常来避免一些和特定业务相关的错误信息。
可以继承Exception来定义一个受检异常,也可以继承自RuntimeException或其子类来定义一个非受检异常。

举个例子:
我们在购买商品的时候,需要付钱然后售货员找零,但是有时候售货员找的钱比支付的还多,这就可以理解为一种异常。

public class example{
	public static void main(String args[]){
		Shopping shopping = new Shopping();
		try{
			shopping.money(100,-20);
		    shopping.money(200,-30);
		    shopping.money(50,-80);
		    shopping.money(50,-30);
		}
		catch(ShoppingException e){
			e.warnMessage();
		}	
	}
}
class Shopping{
	void money(int pay,int change) throws ShoppingException{
		if(pay<=0||change>0||pay+change<0){
			throw new ShoppingException();
		}
		int price=pay+change;
		System.out.printf("这次购买的商品的价格是:%d\n",price);
	}
}
class ShoppingException extends Exception{
	void warnMessage(){
		System.out.println("支付或找零错误!");
	}
}

这个程序的目的是通过付款和找零得到商品的价格,所以构思的第一步就是建立一个Shopping类,Shopping类中有money方法来计算商品的价格。第二步我们发现可能会找零有误的情况,这是“异常”,所以通过继承Exception类得到自定义的异常类ShoppingException,同时要在方法中抛出异常类:1.方法函数头后面添加"throws ShoppingException" 2.在函数体中,存在异常的情况下添加“throws new ShoppingException()”。第三步我们在主类中运用try-catch语句“监视”可能出现异常的部分并采取相应的处理措施。

猜你喜欢

转载自blog.csdn.net/qq_41027127/article/details/89738751
今日推荐