java中的异常机制

本文深入讲解Java中的异常处理机制,包括异常的概念、处理方式、关键字的理解与应用、类继承时异常抛出的原则等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以下是本文的目录大纲:

  • 什么是异常

  • Java中如何处理异常

  • 深刻理解try,catch,finally,throws,throw五个关键字

  • 在类继承的时候,方法覆盖时如何进行异常抛出声明

  • 异常处理和设计的几个建议


一,首先知道什么是异常  ?  ?  ?

  异常的英文单词是exception,字面翻译就是“意外、例外”的意思,也就是非正常情况。事实上,异常本质上是程序上的错误,包括程序逻辑错误和系统错误。


        比如使用空的引用、数组下标越界、内存溢出错误等,这些都是意外的情况,背离我们程序本身的意图。错误在我们编写程序的过程中会经常发生,包括编译期间和运行期间的错误,在编译期间出现的错误有编译器帮助我们一起修正,然而运行期间的错误便不是编译器力所能及了,并且运行期间的错误往往是难以预料的。


        假若程序在运行期间出现了错误,如果置之不理,程序便会终止或直接导致系统崩溃,显然这不是我们希望看到的结果。因此,如何对运行期间出现的错误进行处理和补救呢?Java提供了异常机制来进行处理,通过异常机制来处理程序运行期间出现的错误。通过异常机制,我们可以更好地提升程序的健壮性。


Throwable 下面的 error 和 exception
 * exception 下又分为 检查时异常 和非检查时异常
 * @author  cy
 *
 *两种方式 一种在方法调用者后面 告知有可能出现的异常  

 *另一种 用try catch(如图所示)


在Java中,所有异常类的父类是Throwable类,Error类是error类型异常的父类,Exception类是exception类型异常的父类,RuntimeException类是所有运行时异常的父类,RuntimeException以外的并且继承Exception的类是非运行时异常。


        典型的RuntimeException包括NullPointerException、IndexOutOfBoundsException、IllegalArgumentException等。


        典型的非RuntimeException包括IOException、SQLException等。



二,知道了异常 接下来就是 如何处理这些异常?

两种方式去处理 第一种使用try..catch(在Java中如果需要处理异常,必须先对异常进行捕获,然后再对异常情况进行处理。) 或者throw throws ( 这是一种处理异常的方式。在Java中还提供了另一种异常处理方式即抛出异常,顾名思义,也就是说一旦发生异常,我把这个异常抛出去,让调用者去进行处理,自己不进行具体的处理,此时需要用到throw和throws关键字。)

1、对代码块用try..catch进行异常捕获处理;

2、在该代码的方法体外用throws进行抛出声明,告知此方法的调用者这段代码可能会出现这些异常,你需要谨慎处理。此时有两种情况:

    如果声明抛出的异常是非运行时异常,此方法的调用者必须显示地用try..catch块进行捕获或者继续向上层抛出异常。

    如果声明抛出的异常是运行时异常,此方法的调用者可以选择地进行异常捕获处理。


3、在代码块用throw手动抛出一个异常对象,此时也有两种情况,跟2)中的类似:

    如果抛出的异常对象是非运行时异常,此方法的调用者必须显示地用try..catch块进行捕获或者继续向上层抛出异常。

    如果抛出的异常对象是运行时异常,此方法的调用者可以选择地进行异常捕获处理。

  (如果最终将异常抛给main方法,则相当于交给jvm自动处理,此时jvm会简单地打印异常信息)

有一些代码片段 throw 和throws 的区别

package one;

import java.io.FileNotFoundException;

/**
 * throw 和throws 的区别
 * 
 * @author Administrator , throws表示异常的一种可能出现的异常 方法的声明中 throw表示一定会抛出的异常 方法体重
 */
public class Throw {
	public static void main(String[] args) {
		/**
		 * throw的用法
		 */
		String str = "abc";
		if (!"bc".equals(str)) {
			throw new NumberFormatException();
		} else {
             System.out.println(str);
		}
	}
	/**
	 * throws的用法
	 * 坑你会抛出异常
	 */
	
	public void say() throws FileNotFoundException {
		System.out.println("throws 的异常");
	}
	
}

三,深刻理解 try ..catch finally  throws throw

 try关键字用来包围可能会出现异常的逻辑代码,它单独无法使用,必须配合catch或者finally使用。Java编译器允许的组合使用形式只有以下三种形式:

  try...catch...;       try....finally......;    try....catch...finally...

  当然catch块可以有多个,注意try块只能有一个,finally块是可选的(但是最多只能有一个finally块)。


  三个块执行的顺序为try—>catch—>finally。


  当然如果没有发生异常,则catch块不会执行。但是finally块无论在什么情况下都是会执行的(这点要非常注意,因此部分情况下,都会将释放资源的操作放在finally块中进行)。


  在有多个catch块的时候,是按照catch块的先后顺序进行匹配的,一旦异常类型被一个catch块匹配,则不会与后面的catch块进行匹配。


  在使用try..catch..finally块的时候,注意千万不要在finally块中使用return,因为finally中的return会覆盖已有的返回值。下面看一个例子:


package one;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

/**
 * try catch finally 
 * @author Administrator
 *try 有且只能有一个
 *catch 可以有多个 catch   有一个try 就有一个catch   (最好的爱情不过如此)
 *finally 可有可无 当有的时候最多只能有一个
 *执行顺序 try catch finally
 *
 *无论 try catch 执行 finally 肯定会执行
 */
public class Finally {
public static void main(String[] args) {
	 String str=new Finally().openFIle();
	 System.out.println(str);
}
	
	public String openFIle() {
		FileInputStream fileInputStream;
		try {
			fileInputStream = new FileInputStream("D:/go.txt");
			int ch=fileInputStream.read();
			System.out.println("ccc");
			return "001";
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			return "002";
		}
		catch (IOException e1) {
			e1.printStackTrace();
			return "003";
		}finally {
			System.out.println("finally");
			/*return "004";*/
		}
	}
}

可以看出    可以看出,在try块中发生FileNotFoundException之后,就跳到第一个catch块,打印"file not found"信息,并将"004"赋值给返回值,然后执行finally块,最后将返回值返回。

打印出来

ccc  finally   004

接下来将return 004上注释掉

打印出来

ccc   finally   001

从这个例子说明,无论try块或者catch块中是否包含return语句,都会执行finally块。如果方法有返回值,切忌不要再finally中使用return,这样会使得程序结构变得混乱。


四,在类继承的时候 如何方法覆盖抛出异常

 本小节讨论子类重写父类方法的时候,如何确定异常抛出声明的类型。下面是三点原则:

  1)父类的方法没有声明异常,子类在重写该方法的时候不能声明异常;


  2)如果父类的方法声明一个异常exception1,则子类在重写该方法的时候声明的异常不能是exception1的父类;


  3)如果父类的方法声明的异常类型只有非运行时异常(运行时异常),则子类在重写该方法的时候声明的异常也只能有非运行时异常(运行时异常),不能含有运行时异常(非运行时异常)。




五, 关于异常处理的一些杂项和一些建议

1.只在必要使用异常的地方才使用异常,不要用异常去控制程序的流程

  谨慎地使用异常,异常捕获的代价非常高昂,异常使用过多会严重影响程序的性能。如果在程序中能够用if语句和Boolean变量来进行逻辑判断,那么尽量减少异常的使用,从而避免不必要的异常捕获和处理。比如下面这段经典的程序:

public void useExceptionsForFlowControl() {  
 try {  
 while (true) {  
   increaseCount();  
   }  
 } catch (MaximumCountReachedException ex) {  
 }  
 //Continue execution  
}  
   
public void increaseCount() throws MaximumCountReachedException {  
 if (count >= 5000)  
   throw new MaximumCountReachedException();  
}

上边的useExceptionsForFlowControl()用一个无限循环来增加count直到抛出异常,这种做法并没有说让代码不易读,而是使得程序执行效率降低。


2.切忌使用空catch块

  在捕获了异常之后什么都不做,相当于忽略了这个异常。千万不要使用空的catch块,空的catch块意味着你在程序中隐藏了错误和异常,并且很可能导致程序出现不可控的执行结果。如果你非常肯定捕获到的异常不会以任何方式对程序造成影响,最好用Log日志将该异常进行记录,以便日后方便更新和维护。


3.检查异常和非检查异常的选择

  一旦你决定抛出异常,你就要决定抛出什么异常。这里面的主要问题就是抛出检查异常还是非检查异常。


  检查异常导致了太多的try…catch代码,可能有很多检查异常对开发人员来说是无法合理地进行处理的,比如SQLException,而开发人员却不得不去进行try…catch,这样就会导致经常出现这样一种情况:逻辑代码只有很少的几行,而进行异常捕获和处理的代码却有很多行。这样不仅导致逻辑代码阅读起来晦涩难懂,而且降低了程序的性能。


  我个人建议尽量避免检查异常的使用,如果确实该异常情况的出现很普遍,需要提醒调用者注意处理的话,就使用检查异常;否则使用非检查异常。

  因此,在一般情况下,我觉得尽量将检查异常转变为非检查异常交给上层处理。


4.注意catch块的顺序

        不要把上层类的异常放在最前面的catch块。比如下面这段代码:

try {
       FileInputStream inputStream = new FileInputStream("d:/a.txt");
       int ch = inputStream.read();
       System.out.println("aaa");
       return "step1";
   } catch (IOException e) {
        System.out.println("io exception");        
        return "step2";
   }catch (FileNotFoundException e) {
       System.out.println("file not found");          
       return "step3";
   }finally{
       System.out.println("finally block");
       //return "finally";
   }

        第二个catch的FileNotFoundException将永远不会被捕获到,因为FileNotFoundException是IOException的子类。


5.不要将提供给用户看的信息放在异常信息里

        比如下面这段代码:

public class Main {
   public static void main(String[] args) {
       try {
           String user = null;
           String pwd = null;
           login(user,pwd);
       } catch (Exception e) {
           System.out.println(e.getMessage());
       }
        
   }
    
   public static void login(String user,String pwd) {
       if(user==null||pwd==null)
           throw new NullPointerException("用户名或者密码为空");
       //...
   }
}



展示给用户错误提示信息最好不要跟程序混淆一起,比较好的方式是将所有错误提示信息放在一个配置文件中统一管理。


6.避免多次在日志信息中记录同一个异常

  只在异常最开始发生的地方进行日志信息记录。很多情况下异常都是层层向上跑出的,如果在每次向上抛出的时候,都Log到日志系统中,则会导致无从查找异常发生的根源。


7. 异常处理尽量放在高层进行

  尽量将异常统一抛给上层调用者,由上层调用者统一之时如何进行处理。如果在每个出现异常的地方都直接进行处理,会导致程序异常处理流程混乱,不利于后期维护和异常错误排查。由上层统一进行处理会使得整个程序的流程清晰易懂。


8. 在finally中释放资源

  如果有使用文件读取、网络操作以及数据库操作等,记得在finally中释放资源。这样不仅会使得程序占用更少的资源,也会避免不必要的由于资源未释放而发生的异常情况。


  • 作者:海子

  • 原文:http://www.cnblogs.com/dolphin0520/p/3769804.html




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值