【Java SE基础 五】Java异常处理机制

在了解了面向对象的相关技术后,我们就开始正式合规的写Java代码了,但是实际上,在写代码的过程种会经常有一些错误抛出来,我们称这些运行时抛出的错误为异常,我们要做的就是捕获并处理。

异常定义

异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。要理解Java异常处理是如何工作的,你需要掌握以下三种类型的异常:

  • 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。非运行时异常
  • 运行时异常: 运行时异常是可能被程序员避免的异常。例如用户输入了非法数据RunTimeException运行时异常
  • 错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。错误

异常指不期而至的各种状况,如:文件找不到、网络连接失败、除0操作、非法参数等。异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程

综合日常的实战,我可以这么定义异常处理:异常处理通常和日志紧密配合,在可能出现问题的地方捕获系统抛出的异常然后打出对应的日志,方便系统稳定运行和程序员排查问题,搞明白了基础定位和作用之后,再来详细看看异常体系怎么运作。

异常分类

所有的异常类是从 java.lang.Exception 类继承的子类。Exception 类是 Throwable 类的子类。除了Exception类外Throwable还有一个子类Error 。
在这里插入图片描述
Throwable分成了两个不同的分支,一个分支是Error,它表示不希望被程序捕获或者是程序无法处理的错误。另一个分支是Exception,它表示用户程序可能捕捉的异常情况或者说是程序可以处理的异常。其中异常类Exception又分为运行时异常(RuntimeException)和非运行时异常

  • Error,错误类对象由 Java 虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。例如,Java虚拟机运行错误(Virtual MachineError),当JVM不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。处理不了
  • Exception:Exception异常分为两类,一类时运行时异常,一类是非运行时异常。
    • RuntimeException(运行时异常),该类型的异常自动为你所编写的程序定义ArrayIndexOutOfBoundsException(数组下标越界)、NullPointerException(空指针异常)、ArithmeticException(算术异常)、MissingResourceException(丢失资源)、ClassNotFoundException(找不到类)等异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生不处理,运行会报错
    • 非运行时异常,类型上属于Exception类及其子类,从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。不处理,编译不能通过

还有依据是否是受检查异常而做的一个分类:什么是检查异常呢?在正确的程序运行过程中,很容易出现的、情理可容的异常状况,在一定程度上这种异常的发生是可以预测的,并且一旦发生该种异常,就必须采取某种方式进行处理。

  • 检查异常:除了RuntimeException及其子类以外,其他的Exception类及其子类都属于检查异常,当程序中可能出现这类异常,要么使用try-catch语句进行捕获,要么用throws子句抛出,否则编译无法通过。
  • 不受检查异常:包括RuntimeException及其子类和Error。

总结而言,不受检查异常为编译器不要求强制处理的异常,检查异常则是编译器要求必须处置的异常
这里写图片描述

异常处理

上文我们定位到异常有哪些,而且确定不处理错误,不推荐处理运行时异常(因为运行时异常一般为逻辑错误,程序应该从逻辑角度尽可能避免这类异常的发生),必须处理非运行时异常,如何处理异常呢?Java的异常处理本质上是抛出异常捕获异常,有五个关键字来搭配使用进行异常处理:

  • try,用于监听异常。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
  • catch,用于捕获异常。catch用来捕获try语句块中发生的异常。每个try后边可能有一个或多个catch,当异常发生时,程序会中止当前流程,依据获取异常类型去执行相应catch代码块。
  • finally,finally语句块总是会被执行。它主要用于回收在try块里打开的物理资源(如数据库连接、网络连接和磁盘文件)。只有finally块执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
  • throw, 用于抛出异常。
  • throws ,用在方法签名中,用于声明该方法可能抛出的异常。

接下来详细的了解下异常处理的语法

try-catch-finally

try-catch-finally为一个完整的异常捕获到处理的流程,一个完整的异常处理和捕获的范例代码如下:

try{
  // 程序代码
}catch(异常类型1 异常的变量名1){
  // 程序代码
}catch(异常类型2 异常的变量名2){
  // 程序代码
}finally{
  // 程序代码
}

以下为有无异常的执行流程:
在这里插入图片描述
在发现语句1出错,后边的语句不会执行,也就是说语句2不会被执行了,而是执行finally之后的 try-catch之外的其它语句

语法示例

try-catch-finally分别就检查异常和运行时异常举个例子:

public class Fileexception {

	public static void main(String[] args) {
		FileInputStream in = null;  //一定要定义在try外边,记住作用域是在大括号之间

		try {
			in = new FileInputStream("myfile.txt");
			int b;
			b = in.read();
			while (b != -1) {
				System.out.print((char) b);
				b = in.read();
			}
//一定要先写FileNotFoundException,因为FileNotFoundException是IOException的子类,因为写catch是先小后大,
//否则,捕获底层异常类的catch子句将可能会被屏蔽
		} catch (FileNotFoundException e) {   
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println(e.getMessage());

		} finally {
			try {
				in.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}
}

以下为一个运行时算数异常,不推荐使用,要在逻辑执行时避免。

public class TestException {  
    public static void main(String[] args) {  
        int a = 1;  
        int b = 0;  
        try { // try监控区域               
            if (b == 0) throw new ArithmeticException(); // 通过throw语句抛出异常  
            System.out.println("a/b的值是:" + a / b);  
            System.out.println("this will not be printed!");
        }  
        catch (ArithmeticException e) { // catch捕捉异常  
            System.out.println("程序出现异常,变量b不能为0!");  
        }  
        System.out.println("程序正常结束。");  
    }  
}  

嵌套try

try语句可以被嵌套。也就是说,一个try语句可以在另一个try块的内部。

class NestTry{
    public static void main(String[] args){
        try{
            int a = args.length;
            int b = 42 / a;
            System.out.println("a = "+ a);
            try{
                if(a == 1){
                a = a/(a-a);
                }
                if(a == 2){
                    int c[] = {1};
                    c[42] =99;
                }
            }catch(ArrayIndexOutOfBoundsException e){
                System.out.println("ArrayIndexOutOfBounds :"+e);   //数组越界异常
            }    
        }catch(ArithmeticException e){
            System.out.println("Divide by 0"+ e);    //除0异常
        }
    }
}

输出结果为

a=0:Divide by 0java.lang.ArithmeticException: / by zero
a=1:Divide by 0java.lang.ArithmeticException: / by zero
a=2:ArrayIndexOutOfBounds :java.lang.ArrayIndexOutOfBoundsException

每次进入try语句,异常的前后关系都会被推入堆栈。如果一个内部的try语句不含特殊异常的catch处理程序,堆栈将弹出,下一个try语句的catch处理程序将检查是否与之匹配。

  • a=0时,外面的try块将产生一个被0除的异常。
  • a=1时,嵌套的try块产生一个被0除的异常,由于内部的catch块不匹配这个异常,它将把异常传给外部的try块,在外部异常被处理。
  • a=2时,内部try块产生一个数组边界异常

整个过程将继续直到一个catch语句被匹配成功,或者是直到所有的嵌套try语句被检查完毕。如果没有catch语句匹配,Java运行时系统将处理这个异常。

try-catch-finally组合情况

我们都说这个组合来处理异常,那么这几个关键字都是必须的么?关键字组合有如下几种:

  1. try+catch后有没有finally无所谓,try+catch+finally可以使用;try+catch可以使用
  2. try必不可少,try+finally可以,try+catch可以
  3. 三个关键字不能单独使用任何一个即使是try如果不加catch也必须有finally(声明了异常就一定要处理,不管是在catch还是finally中)

总结就是必须有try+(catch,finally,catch+finally)三个组合中的一种

return的执行时机

我们知道不管有没有出现异常,finally块中代码都会执行,但是如果说在以上三种关键字所管辖的块内包含return,代码该按照何种顺序执行呢?

finally没有return

finally里没有return的场景,但try或catch里有,finally仍然会执行,因此在return返回时不是直接返回变量的值,而是复制一份,然后返回,因此,对于基本类型的,finally的改变没有影响,对于引用类型的就有影响了

package test;
/
 * @author 田茂林
 * @data 201796日 下午9:46:42
 */

public class TestFinally{
    //基本类型
	public static int testFinally1(){ 
		int result =1;
		try {
			result = 2;
			return result;
		} catch (Exception e) {
			return 0;
		}finally{
			result =3;
			System.out.println("execult Finally1");
		}
	}
	   //引用类型
	public static StringBuffer testFinally2(){       
		StringBuffer s = new StringBuffer("Hello");
		try {
			return s;
		} catch (Exception e) {
			return null;
		}finally{
			s.append("world");
			System.out.println("execult Finally2");
		}
	}
	public static void main(String[] args) {
              int result1 = testFinally1();
              System.out.println(result1);
              StringBuffer result2 = testFinally2();
              System.out.println(result2);
		
	}
}

输出结果

execult Finally1
2
execult Finally2
Helloworld

对于finally块中没有return语句的情况,方法在返回之前会先将返回值保存在局部变量表中的某个slot中,然后执行finally块中的语句,之后再将保存在局部变量表中某个slot中的数据放入操作数栈的栈顶并进行返回,因此对于基本数据类型而言,若在finally块中改变其值,并不会影响最后return的值

finally里有return

finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。返回值是finally里的return返回的值

package test;

/**
 * @author 田茂林
 * @data 2017年9月6日 下午9:46:42
 */

public class TestFinally {
	@SuppressWarnings("finally")
	public static int testFinally() { // 基本类型
		int i = 1;
		try {
			++i;
			return i;
		} catch (Exception e) {

		} finally {
			return i++; // 2
			// return ++i; //3
		}

	}

	public static void main(String[] args) {
		int result1 = testFinally();
		System.out.println(result1);

	}
}

如果是return i++返回i的值,return ++i返回的是++i的值,也就是永远返回当前值,对于finally块中包含了return语句的情况,则在try块中的return执行之前,会先goto到finally块中,而在goto之前并不会对try块中要返回的值进行保护,而是直接去执行finally块中的语句,并最终执行finally块中的return语句,而忽略try块中的return语句,因此最终返回的值是在finally块中改变之后的值。

throws和throw

如果一个方法没有捕获到一个检查性异常,那么该方法必须使用 throws 关键字来声明。throws 关键字放在方法签名的尾部。也可以使用 throw 关键字抛出一个异常,一个throws子句列举了一个方法可能引发的所有异常类型。

import java.io.*;
public class className
{
  public void deposit(double amount) throws RemoteException
  {
    // Method implementation
    throw new RemoteException();
  }
  //Remainder of class definition
}

throws异常抛出示例

下面是用throws关键字抛出异常的举例,异常从f2()抛出并层层上抛,直到main方法处理异常

public class ManangeException {
    //直到抛到main方法里,不要再main方法里写throws,main方法会打印堆栈信息,但最好在main方法里处理异常
    //main方法调用了f2()方法,并监控和捕获可能抛出的异常
	public static void main(String[] args) {  
         ManangeException m=new ManangeException();
         try {
			m.f2();
		} catch (IOException e) {
			System.out.println("没有找到该文件");
		}
	}
    //f()方法调用了f2()方法,并抛出可能的异常
	public void f2() throws IOException{  //一级一级往外抛
		f();
	}
	//f2()方法抛出可能的异常
	public void f() throws FileNotFoundException,IOException{   //不处理只抛出
		FileInputStream in = null; 
		in = new FileInputStream("myfile.txt");
		int b;
		b = in.read();
		while (b != -1) {
			System.out.print((char) b);
			b = in.read();
		}
	}

如果在main方法中也不处理并继续向上抛的话,可以看到如下的异常堆栈信息:

Exception in thread "main" java.io.FileNotFoundException: myfile.txt (系统找不到指定的文件。)
	at java.io.FileInputStream.open0(Native Method)
	at java.io.FileInputStream.open(FileInputStream.java:195)
	at java.io.FileInputStream.<init>(FileInputStream.java:138)
	at java.io.FileInputStream.<init>(FileInputStream.java:93)
	at packageB.Person.f(Person.java:24)
	at packageB.Person.f2(Person.java:19)
	at packageB.Person.main(Person.java:14)

throw异常抛出示例

除了对可能的异常抛出在方法上声明,还可以通过指定异常抛出直接手动抛出异常

class TestThrow{
      static void proc(){
            try{
                throw new NullPointerException("demo");
            }catch(NullPointerException e){
                System.out.println("Caught inside proc");
                throw e;
            }
        }

        public static void main(String [] args){
            try{
                proc();
            }catch(NullPointerException e){
                System.out.println("Recaught outside ");
            }
        }
}

返回结果如下:

Caught inside proc
Recaught outside 

自定义异常

使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承Exception类即可。在程序中使用自定义异常类,大体可分为以下几个步骤:

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

自定义异常举例如下:

class MyException extends Exception {
	private int id;
	public MyException(String message, int id) {
		super(message);
		this.id = id;
	}
	public int getId() {
		return id;
	}
}

public class TestMyException {
    public static void main(String[] args) {
		TestMyException t=new TestMyException();
		t.m(1);
	}
     public void m(int i){
    	 try {
			regist(i);               //方法体里边不能再写方法,但是可以直接调用就好
		} catch (MyException e) {
			System.out.println("出现故障");
		}
     }
     
     public void regist(int num) throws MyException{
    	 if(num<0) 
    	     throw new MyException("不合法的人数", 1);    //这要是异常抛出,则不会打印下边的那句话,因为没有捕获处理
    	 System.out.println(num);
     }
    
   
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

存在morning

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值