Java中的异常机制

吴云  2018-05-27 23:13:05   1评论  489浏览
图片来源于网络o_O

写在前边:

最近在练科三,没有太多时间去想下一篇文章该写啥。这篇文章是博主当初学异常类的时候遇到一些问题,虽然有一些钻牛角尖的问题……….-_-|||

程序运行的过程中,可能会出现一些错误,这些错误会导致程序的终止。Java的异常处理机制,可以让程序在遇到了错误后,不会影响后边的代码并继续执行。try-catch异常处理机制可以让程序在运行过程中即使出现错误也可以不必终止,从而继续运行后续代码。


正文:

    Java中最大的异常类是Throwable,它有两个很大的子类分别是ExceptionError。其中Exception中的异常是鼓励程序员捕获的,而Error是一些不可预料的错误,这类错误十分严重,设计合理的程序是不会捕获这类错误的。所以,我说了这么多都是废话罢了,上图吧!


Throwable的子类Error用来描述JVM发生的错误,这类错误是程序正常运行本不应该遇到的情况,一旦出现程序就应该停止,而不是去捕获它。尽管Error能被捕获到,但是我们在写代码的时候尽量不要去捕获这类错误。

Throwable的子类Exception用来描述可以被处理的异常。而Exception的子类RuntimeException类描述的是运行时异常,其他的子类描述的是检查型异常。RuntimeException和检查型异常最大的区别就是检查型异常是程序员必须进行处理的,否则编译不会通过,而RuntimeException是不可预料的异常,编译器并不强制要求捕获这类异常。这篇文章会着重的讲解关于Exception类的一些知识。

运行时异常( RuntimeException ):

常见的运行时异常有:

NullPointerException----->空指针异常

ArrayIndexOutOfBoundsException---->数组下标越界异常

IllegalArgumentException---->非法参数异常

这些异常编译器并不会去检查,也就是说一个可能发生异常的语句i=2/0;编译器不会报错,而出现异常之后,异常对象由Java虚拟机抛出,运行时异常的发生通常是程序的逻辑错误。

请看这段代码:

int i = 0;
System.out.println("started");
i = 1/0;
System.out.println(" running ");
System.out.println("i = "+i);

我们在小学学除法的时候就知道除数不能为0,这段代码虽然编译可以通过,但是程序会因为出现了异常而终止运行。运行结果如图所示:



    ArithmeticException---->是算数异常,程序输出了异常并打印了错误原因/by zero

    我们看到异常之后的代码就没有再执行了,这是因为异常的发生让主线程终止了

为了让程序继续下去,我们可以加try-catch语句捕获异常,请看如下代码:


int i = 0;
System.out.println(" started ");
try{
i = 1/0;
System.out.println(" running ");
}catch(ArithmeticException e){
	System.out.println("除数不能为零");
}
System.out.println("i = "+i); 

运行结果:

 


 程序出现异常时会抛出一个异常对象,这个异常对象被catch捕获到后,try块的出现异常之后语句将不会执行,但是程序不会结束,catch捕获到异常会执行catch块的内容,然后继续运行try-catch之后的代码。(尽管这些很基础,但是还是想再说一下,如果都懂的话,可以直接划到后边。)

注意:try-catch代码块定义的变量,作用域只在括号里,超出作用范围就不好使了

    try-catch不仅仅可以只捕获一种类型的异常,我们可以写很多的catch代码块,程序发生异常时按顺序匹配异常对象和catch中异常类型,匹配到了那个就执行那个catch块中的语句,如果后边还有其他的catch块,也都将不会执行。而catch中的异常顺序也是有一定的说法的,子类异常的catch块必须在父类异常的catch块前边,否则子类异常就会变成一个摆设,永远都不能执行,而编译不会通过的。请看如下代码:

try{
i=1/0;
}catch(NullPointerException npe){
	System.out.println("抓到了NullPointerException");
}catch(ArrayIndexOutOfBoundsException aiofbe){
	System.out.println("抓到了ArrayIndexOutOfBoundsException");
}catch(ArithmeticException e){
	System.out.println("抓到了ArithmeticException");
}catch(RuntimeException rex){
	System.out.println("抓到了RuntimeException");
}catch(Exception ex){
	System.out.println("抓到了Exception");
}

运行结果:

抓到了ArithmeticException

 

    其实我们还可以直接用一个catch(Exception e)来捕获,Exception是他们的父类,可以捕获到它的所有子类的异常对象。如果你只想捕获某一种异常类对象,可以单独捕获某种异常对象。比如str = null ;int i=Integer.parseInt(str);就会报NumberFormatException,数字格式话异常,如果我们想在发生该异常的时候处理这种情况,应该单独捕获这类异常,并做出对异常进行处理。比如,捕获到这数字格式化异常就让i等于一个缺省的值。而不是仅仅打印出异常的类型和轨迹了事。

检查型异常:

    在Java中有一类是,Exception类的子类中除RuntimeException之外的异常,这类异常是检查型异常,就是说编译器会看着你一定要让你处理。要不用try-catch捕获,要不就用throws抛给上层调用者,当然主方法是不建议throws的,这些异常应该是程序员处理的,而不是抛给JVM,(这样相当于抛给了用户,用户一看就会说什么破软件,卸载掉)检查型异常的发生并非程序员的代码逻辑有问题,而是外部因素不确定性导致的,比如FileNotFoundException文件没有找到,数据库操作错误(SQLExcepition),算法没有找到(NoSuchAlgorithmException

请看如下代码:

File file = new File("C:\\a.txt");
try{
	FileInputStream fis=new FileInputStream(file);
}catch(FileNotFoundException e){
	System.out.println("没有该文件");
}


    这样写编译是可以通过,代码第三行可能会抛出一个检查型异常(非RuntimeException子类)但是如果把第三行注释掉,catch语句会无法通过编译(没有发现会抛出该类型的异常语句),只把try-catch删掉程序也会报错(检查型的异常必须被捕获或着throws抛出)。所以,Java还是很严谨的。

    如果我们不知道这么用try-catch处理该异常,还可以一层一层向上抛给调用者,调用者可以选择继续向上层调用者抛出或者try-catch捕捉处理。

public void readFileContext() throws FileNotFoundException{//多种异常可以使用逗号分隔开
File file = new File("C:\\a.txt");
FileInputStream fis = new FileInputStream(file);
}

    方法可以抛出多种异常,其中抛出检查型异常,方法调用者必须进行try-catch或throws继续抛出,而且必须抛出和被调用方法抛出异常的一样的异常类或者父类才行。如果为RuntimeException,调用者可以不进行处理,编译器是不会检查的。也就是说调用抛出RuntimeException的方法,可以不必捕获或继续向上层调用者抛出。那么,这个异常对象是怎么产生的呢?Java中还有一个throw用于手动抛出异常,我们阅读的一些api的时候常常会有这样的语句throw new XXXXException();请继续往下看。

使用throw语句来抛出异常对象:

    异常对象不会凭空产生,程序在运行中出现,throw new XXXException();会有异常对象被实例化,当程序运行到throw语句时,程序会终止运行。只有该方法调用者使用catch抓住它才会使程序不至于在throw 出一个异常对象之后终止运行。

    Java支持自定义异常,此时我们只需要继承Exception类就可以throw 一个自定义异常对象了,注意:throw只能抛出异常类对象,也就是Throwable及其子类,Throwable又译为可抛出的。

public class Test5 extends Exception{
	private String str;
	public Test5(String str){
		super(str);
	 }
}
public class Test3 {
	public static void main(String[] args){
try {
	     throw new Test5("自定义exception");
	      //由于是Exception的子类必须捕获该异常或向上抛出该异常才行
} catch (Test5 e) {
	      e.printStackTrace( );
}
	}
}



     Exception的子类包含检查型的异常所以直接继承Exception的异常自定义的异常也属于检查型异常它必须被捕获。还可以让Test5继承RuntimeException这样就Test5就变成了运行时异常的子类,编译器可以不去检查这类异常,我们就不用必须捕获或者抛出该异常了~

在重写方法时异常该如何抛出?

     

Ⅰ父类方法抛出运行时异常。

①子类可以不抛出异常。

②子类如果抛出,一定是运行时异常,不能抛出检查型异常或包含检查型异常的异常类。

Ⅱ父类方法抛出了检查型异常或包含检查异常的父类(Exception ,  Throwable

①子类可以不抛任何检查型异常但是可以抛出任意运行时的异常。

②子类如果想要抛出检查型异常必须是父类抛出异常的子类或和父类一样否则就编译失败。


try-catch-finally的使用

     finally也可以加在catch后边,不过无论try中是否发生异常,finally一般都会执行,finally作为一个统一出口,常用于关闭流操作,都是一定要执行的,除非是finally里有异常,或者try-catch里有System.exit(0);或者电脑关机,总之只要这个线程还存活,就会执行finally块。finally通常用来关闭资源。测试程序代码如下:


try{
	i=10/0;
}catch(ArithmeticException e){
	System.out.println(" 抓住了ArithmeticException ");
}finally{
	System.out.println(" 我是Finally ");
}	

运行结果:

抓住了ArithmeticException

我是Finally

try{
	i=10/2;
}catch(ArithmeticException e){
	System.out.println(" 抓住了ArithmeticException ");
}finally{
	System.out.println(" 我是Finally ");
}

运行结果:

我是Finally

    可以看出程序无论是否能捕获到异常,finally都会执行,还有一点try-catch-finally都是可以嵌套的,也就是说try块,catch块、finally可以加入try-catch-fianlly代码,catch都会监控和它匹配的try块的代码。

    那么如果try-catch里有return程序又会怎样执行?请看以下代码:

	public static void main(String[] args) {
		try {
			System.out.println("fun()="+fun());
		} catch (Exception e) {
			System.out.println("catch in main ");
		} finally {
			System.out.println("finally in main");
		}
	}

	public static String fun(){
		String str = "";
		try {
			System.out.println("try in fun");
			int i = 1 / 0;
			return str += "try ";
		} catch (Exception e) {
			System.out.println("catch in fun");
			return str += "catch ";
		} 
		//return str += "??";//因为try-catch必然返回其中一个,这里返回就多余了
	} 

    fun()方法返回值为String,所以需要有个返回值。如果在fun()方法倒数第二行没有return,那就必须在try-catch里有return语句~运行结果如下:

try in fun

catch in fun

fun()=catch

finally in main


这是一个很简单的例子,(感觉这都是废话)但是如果我们把fun()方法改成这样:

	public static String fun(){
		String str = "";
		try {
			System.out.println("try in fun");
			int i = 1 / 0;
			return str += "try ";
		} catch (Exception e) {
			System.out.println("catch in fun");
		    return str += "catch ";
		} finally {
			System.out.println("finally in fun");
		}
        }



思考:大家觉得这段代码会如何执行?

    try,catch和finally块都存在return,刚才说了finally没有特殊情况一定会执行的,可是try、catch里有return,大家都知道方法执行中遇到return就会结束了,try-catch里的return是必然执行其中一个的,那程序会不会结束,就不执行finally的代码了。那当然不可能了~finally只要不是上边说的那些情况都一定要执行的,只不过在try-catch里遇到return不会结束,而是先执行完finally后再结束。所以运行结果如下:

try in fun

catch in fun

finally in fun

fun()=catch

finally in main

好八~我们继续改代码

现在我们又把fun()方法改成了这样:

	public static String fun(){
		String str = "";
		try {
			System.out.println("try in fun");
			int i = 1 / 0;
			return str += "try ";
		} catch (Exception e) {
			System.out.println("catch in fun");
		    return str += "catch ";
		} finally {
			System.out.println("finally in fun");
            return str += "finally";
		}
}

看起来没有很多变化只是在finally加了一个return语句

     现在有三个return,那么会执行哪一个return呢?刚才我们发现,当try-catch里遇到return时会执行finally,然后再执行return退出fun()。可是如果finally里有return的话,方法就会提前退出,让我们看看输出了啥:

try in fun

catch in fun

finally in fun

fun()=catch finally

finally in main

    我们从fun()=catch finally ,可以发现尽管程序出现异常后没有在catch里退出方法,但是却把catch块的return 的表达式结果算出来了。这时候str = “catch”,finaly块再次连接“finally”字符串在finally里退出了,程序即使不发生异常也是一样的,只不过fun()的返回值就成了try finally了。所以,总结为一句话,在finally中有return语句会让程序在finally里提前退出,但是会计算catch块或try块里return后边地表达式。

 对于finally中有return的,还有一种情况,现在我们把fun()方法改成了这样:

	public static String fun(){
		String str = "";
		try {
			System.out.println("try in fun");
			int i = 1 / 0;
			return str += "try ";
		} catch (Exception e) {
			System.out.println("catch in fun");
			throw e;//抛出这个异常
			//return str += "catch ";
		} finally {
			System.out.println("finally in fun");
			return str += "finally";
		}
	}

在catch块里把捕获到的异常抛出去了,(这时候就不能return了,因为throw后的代码不会执行,编译器不允许throw后还有代码。)我们在main方法里来捕获这个异常,让我们看看运行结果:

try in fun

catch in fun

finally in fun

fun()=finally

finally in main

main方法竟然没有捕获到异常,也没有报异常。那我们刚才抛出去的e哪去了?

    因为throw会退出程序,可是还有finally没有执行,就会在throw之前先执行finally,可是finally里含有return语句,导致程序提前退出,没有把异常抛出去,也就是说finally的return语句覆盖了catch里的异常,只有把finally里的return屏蔽掉,才可以抛出catch块里的异常,不知道这是不是Java的一个Bug(代码的运行环境是jdk1.8)。从这些例子里,我们发现finally里是不应该写return语句的,写了编译器也会报warning。不过听说程序员通常是不看warning的。

总结:

总结就是,写了这么多字真的好累呀!!!

1.Throwable是异常类最大的父类,有两个子类分别是Exception和Error,其中,Exception描述的是程序上的问题是鼓励程序员捕获的,而Error描述的是虚拟机的问题,程序员没有必要解决这类问题。

2.Exception 的子类RuntimeException有不确定性,编译器不强制程序员捕获,Exception的其他子类是检查型异常程序员必须捕获。

3.finally块是try-catch的统一出口,常常用于关闭资源。

4.finally块里边最好不要存在return语句。

[如果有了return会怎么执行呢??????Your turn to show!]