LinkinPark
当你的才华撑不起你野心时,那么请潜下心继续学习,心无旁骛,愿多年以后你们我都能成为自己想象的模样。
  • Java异常

异常机制已经成为判断一门编程语言是否成熟的标准。对于java而言,将异常分为了2种:Checked异常和Runtime异常。java认为Checked异常都是可以在编译阶段被处理的异常,所以它强制程序处理所有的Checked异常,而Runtime异常则无需处理。Checked异常可以提醒程序员需要处理所有可能发生的异常,当时Checked异常也给编程带来了一些繁琐之处,特别是在有的时候并不需要去处理异常,因为这个异常一直不可能发生,所以Checked异常也是java领域一个备受争议的话题。
异常(Exception)就是程序在运行时出现的不正常情况。我们的写的程序不可能一帆风顺,若异常产生,却没进行正确的处理。则可能导致程序的中断,造成损失,所以我们在开发中要考虑到各种异常的发生,并对其作出正确的处理,确保程序的正常执行。

关于异常处理机制,我们要正确的理解它的核心:使得程序中的异常处理代码和正常业务代码分离,保证程序代码更加优雅,并可以提高程序的健壮性。


  • java为什么要有异常处理机制?

当初我第一次接触异常的时候,就产生了一个问题:我可以用if和else来逻辑控制呀,为毛还要引入这种复杂的异常处理机制呢?原因很简单:过多的分支会导致程序的代码加长,可读性差,因此采用异常机制。所以Java采用异常处理机制,将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁,并易于维护。疯狂java里面说的一句话很好的:对于构造大型,健壮,可维护的应用而言,错误处理是整个应用需要考虑的重要方面,曾经有一个教授说过:我们国内的程序员做开发时,往往只做了“对”的事情!他这句话有着深深的遗憾--程序员开发程序过程中,是一个创造的过程,这个过程需要有全面的考虑,仅做“对”的事情是远远不够的。


  • 异常的体系


JAVA将所有的错误封装成为一个对象,其根本父类为Throwable。异常处理可以提高我们系统的容错性。

Throwable 有两个子类:Error 和Exception。


  Error:一般是底层的不可恢复的错误。通常指JVM出现重大问题如:运行的类不存在或者内存溢出等。不需要编写针对代码对其处理,程序无法处理。

  Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,例如:空指针访问,试图读取不存在的文件,网络连接中断。


           Object 
             ↑ 
          Throwable 
             ↑ 
        ┌---------┐
     Error      Exception 
                   ↑                非RuntimeException
           ┌-----------------------┬---------------┐ 
        RuntimeException   InterruptedException  IOException
              ↑ 
        ┌----------------------┬-----------...

   NullpointerException  ArrayIndexOutOfBoundsException


  • Throwable。Throwable 类是 Java 语言中所有错误或异常的超类,顶级的,这里大致了解下他的异常链机制就好了,注意他其中的几个方法。
 
  • 运行时异常和编译时异常
java的异常分为2大类,Check异常和Runtime异常,所有的RuntimeException类及其子类的实例都称为Runtime异常,不是RuntimeException类及其子类的异常则被称为Checked异常。只有java语言提供了Checked异常,Checked异常体现了java的设计哲学:没有完善的错误处理的代码根本就不会被执行。对于Checked异常的处理机制有2种:要不捕获,要不抛出。而Runtime异常则在编译的时候不用管。所以推荐使用Runtime异常。

  • 常见异常

RuntimeException:错误的类型转换,数组下标越界,空指针访问,IOExeption,从一个不存在的文件中读取数据,越过文件结尾继续读取,连接一个不存在的URL。

java.lang.ArithmeticException           //如:分母为0; 
java.lang.NullPointerException          //如:空指针操作; 
java.lang.ArrayIndexoutofBoundsExceptio //如:数组越界;数组没有这元素;
java.lang.ClassCastException            //如:类型转换异常; 


  • 异常处理机制
Java提供的是异常处理的抓抛模型。Java程序的执行过程中如出现异常,会自动生成一个异常类对象,该异常对象将被提交给Java运行时系统,这个过程称为抛出(throw)异常。如果一个方法内抛出异常,该异常会被抛到调用方法中。如果异常没有在调用方法中处理,它继续被抛给这个调用方法的调用者。这个过程将一直继续下去,直到异常被处理。这一过程称为捕获(catch)异常。如果一个异常回到main()方法,并且main()也不处理,则程序运行终止。程序员通常只能处理Exception,而对Error无能为力。异常处理是通过try-catch-finally语句实现的。



  • 异常处理的5个关键字  try ;catch;finally,throw,throws。

1,try 捕获异常的第一步是用try{…}语句块选定捕获异常的范围,将可能出现异常的代码放在try语句块中。

try块必须和 catch块或和finally同在,不能单独存在,二者必须出现一个。
2,catch (Exceptiontype e)在catch语句块中是对异常对象进行处理的代码。每个try语句块可以伴随一个或多个catch语句,用于处理可能产生的不同类型的异常对象。如果明确知道产生的是何种异常,可以用该异常类作为catch的参数;也可以用其父类作为catch的参数。可以用ArithmeticException类作为参数,也可以用RuntimeException类作为参数,或者用所有异常的父类Exception类作为参数。但不能是与ArithmeticException类无关的异常,如NullPointerException,那么,catch中的语句将不会执行。捕获异常的有关信息:与其它对象一样,可以访问一个异常对象的成员变量或调用它的方法。这几个方法继承与Throwable。

getMessage( ) :用来得到有关异常事件的信息。

printStackTrace( ):用来跟踪异常事件发生时执行堆栈的内容。
3,finally语句是可选的。finally捕获异常的最后一步是通过finally语句为异常处理提供一个统一的出口,使得在控制流转到程序的其它部分以前,能够对程序的状态作统一的管理。除非在try块,catch块中调用了退出虚拟机的方法(System.exit(1)),否则不论在try、catch代码块中是否发生了异常事件,finally块中的语句都会被执行。

注意了:尽量避免在finally块里使用return和throw等导致方法终止的语句,否则可能出现一些很奇怪的现象,比如说会导致try块,catch块中的return,throw语句失效。

4,throws 如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显式地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。
在可能出现异常的方法上声明抛出可能出现异常的类型:声明的时候尽可能声明具体的异常,方便更好的处理。当前方法不知道如何处理这种异常,可将该异常交给上一级调用者来处理(非RuntimeException类型的异常)。方法一旦使用throws声明抛出方法内可能出现的异常类型, 该方法就可以不再过问该异常了;一个方法调用另一个使用throws声明抛出的方法,自己要么try...catch , 要么也throws;
编写方法的时候,自身可能会引发异常,但是却不知道是否一定会发生异常,是否发生异常取决于调用者,怎么调用该方法,此时可以声明该方法不处理异常,抛给方法的调用者自行处理... 此时使用throws表面提示调用者使用该方法可能会出现问题,请小心使用!调用者有两种处理方式: 捕获异常或也声明抛出。子类方法中声明抛出的异常类型是父类方法声明抛出异常类型的子类或相同类;也就是说:子类方法不能抛出新的异常类型;子类方法可以同时声明抛出多个父类方法声明抛出异常类的子类;(RuntimeException例外。子类中方法与父类方法有相同的返回值类型;子类声明返回的类型也可以是父类声明返回类型的子类;子类中方法与父类方法有相同的方法签名;子类中方法的访问权限不能小于父类方法的访问权限;子类方法不能抛出新的异常类型;子类方法可以同时声明抛出多个父类方法声明抛出异常类的子类;(RuntimeException例外);自行抛出一个异常对象,抛出异常类的对象;若throw抛出的是Runtime异常:程序可以显示使用try...catch来捕获并处理,也可以不管,直接交给方法调用者处理;若throw抛出Checked异常:要么放在try里自己处理,要么放在一个throws声明的方法里面,交给调用者处理。

5,throw 很主观的一种动作,就是人为的把这个异常扔出去。throw语句抛出的不是异常类,而是一个异常实例,而且每次只能抛出一个异常实例。如果throw抛出的是一个Checked异常,那么就必须要处理,要是抛出的是一个Runtime异常,可以不用理会。

throws与throw的区别:说白就是:throws是一种状态属性,throw是一个动作属性。
throws用于在方法上声明该方法不需要处理的异常类型。后面跟异常类名,可以是多个异常类。
throw用在方法内,用于抛出具体异常类的对象。后面跟异常对象,只能是一个。

代码如下:

try
{
...... //可能产生异常的代码
}
catch( ExceptionName1 e )
{
...... //当产生ExceptionName1型异常时的处置措施,先捕获小异常再捕获大异常
}
catch( ExceptionName2 e )
{
...... //当产生ExceptionName2型异常时的处置措施,先捕获小异常再捕获大异常
}  
[ finally{
...... //无条件执行的语句

}  ]

  • 多异常处理
1,声明异常时尽可能声明具体异常类型,方便更好的处理。

2,方法声明几个异常就对应有几个catch块。

3,若多个catch块中的异常出现继承关系,父类异常catch块放在最后。

4,在catch语句块使用Exception类作为异常类型时:所有子类实例都可以使用父类接收(向上转型),即所有的异常对象都可以使用Exception接收。

注意:在java处理多异常时捕获小范围的异常必须放在大范围异常之前。也就是说先捕获小异常,在捕获大异常。


  • java7提供的多异常捕获

在java7以前,每个catch块只能捕获一种类型的异常,但从java7开始,一个catch快可以捕获多种类型的异常。使用一个catch块捕获多种类型的异常需要注意2个地方:

1,捕获多种类型的异常时,多种异常类型之间要用竖线“|”隔开

2,捕获多种类型的异常时,异常变量有隐形的final修饰,因此程序不能再对异常变量重新赋值

public class MultiExceptionTest
{
	public static void main(String[] args) 
	{
		try
		{
			int a = Integer.parseInt(args[0]);
			int b = Integer.parseInt(args[1]);
			int c = a / b;
			System.out.println("您输入的两个数相除的结果是:" + c );
		}
		catch (IndexOutOfBoundsException|NumberFormatException
			|ArithmeticException ie)
		{
			System.out.println("程序发生了数组越界、数字格式异常、算术异常之一");
			// 捕捉多异常时,异常变量默认有final修饰,
			// 所以下面代码有错:
			ie = new ArithmeticException("test");  //①
		}
		catch (Exception e)
		{
			System.out.println("未知异常");
			// 捕捉一个类型的异常时,异常变量没有final修饰
			// 所以下面代码完全正确。
			e = new RuntimeException("test");    //②
		}
	}
}

  • java7提供的自动关闭资源的try语句

java7增强了try语句的功能,它允许try后面紧跟一对圆括号,圆括号可以声明,初始化一个或多个资源,此处的资源值得是那些必须在程序结束时显示关闭的资源(比如数据库连接,网络连接),try语句在该语句结束时将自动关闭这些资源。注意这些资源实现类必须实现AutoCloseable或者Closeable接口,实现这2个接口就必须实现close()方法。java7把所有的资源类,包括文件IO的各种类,JDBC编程的Connection,statement等接口进行了改写,都实现了上面的2个接口。

import java.io.*;

public class AutoCloseTest
{
	public static void main(String[] args)  throws IOException
	{
		try (
			// 声明、初始化两个可关闭的资源
			// try语句会自动关闭这两个资源。
			BufferedReader br = new BufferedReader(new FileReader("AutoCloseTest.java"));
			PrintStream ps = new PrintStream(newFileOutputStream("a.txt")))
		{
			// 使用两个资源
			System.out.println(br.readLine());
			ps.println("LinkinPark。。。");
		}
	}
}



  • 声明抛出异常
声明抛出异常是Java中处理异常的第二种方式。如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显式地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。在方法声明中用 throws 子句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。声明抛出异常举例:
public void readFile(String file)  throws FileNotFoundException {
……
// 读文件的操作可能产生FileNotFoundException类型的异常
FileInputStream fis = new FileInputStream(file);
..……
     }

 
  • 人工抛出异常

Java异常类对象除在程序执行过程中出现异常时由系统自动生成并抛出,也可根据需要人工创建并抛出。首先要生成异常类对象,然后通过throw语句实现抛出操作(提交给Java运行环境)。IOException e =new IOException();throw e。可以抛出的异常必须是Throwable或其子类的实例。下面的语句在编译时将会产生语法错误:throw new String("want to throw")。


  • throw和catch同时使用

当异常出现在当前方法中,程序只对异常进行部分处理,还有一些处理需要在方法的调用者中才能处理完成,此时还应该再次抛出异常,这样就可以让方法的调用者也能捕获到异常。

  • 定义异常类

用户自定义异常类MyException,用于描述数据取值范围错误信息。用户自己的异常类必须继承现有的异常类。要是自定义的是Runtime异常的话,那么就继承RuntimeException就好了。

package linkin;
//最简单的一个异常,以后再调用的时候直接new 第3个构造器好了,传入一个字符串说明下异常原因
public class LinkinException extends Exception
{

	public LinkinException()
	{
		super();
	}

	public LinkinException(String message, Throwable cause)
	{
		super(message, cause);
	}

	public LinkinException(String message)
	{
		super(message);
	}

	public LinkinException(Throwable cause)
	{
		super(cause);
	}
	
	
}

package linkin;

/**
 *
 * @version 1L
 * @author  LinkinPark 
 * @since   2014-12-1
 * @motto   梦似烟花心似水,同学少年不言情
 * @desc    ^自定义的异常,这里继承Exception,所以包含了编译时异常。所以在throw的时候都要做处理,要不捕获,要不抛出。要是不想做处理,那么就继承RuntimeException
 */

public class LinkinException extends Exception
{
	private int idnumber;

	public LinkinException()
	{
		
	}
	
	public LinkinException(String message, int id)
	{
		super(message);
		this.idnumber = id;
	}

	public int getId()
	{
		return idnumber;
	}

	//上面定义的异常都继承Exception,所以要做处理
	//要是直接继承RuntimeException,这里就不用做处理
	public void regist(int num) throws LinkinException 
	{
		if (num < 0)
		{
			throw new LinkinException("人数为负值,不合理", num);
		}
		else
		{
			System.out.println("登记人数" + num);
		}
	}

	public void manager(int num)
	{
		try
		{
			regist(num);
		}
		catch (LinkinException e)
		{
			System.out.println("登记失败,出错种类[" + e.getId() + "]");
			e.printStackTrace();
		}
		System.out.println("本次登记操作结束");
	}

	public static void main(String args[])
	{
		LinkinException t = new LinkinException();
		t.manager(-100);
		t.manager(100);
	}
}

                 

  • java的异常跟踪栈
在面向对象的过程中,大多数复杂操作都会被分解成一系列方法调用。这是因为:实现更好的可重用性,将每个可重用的代码单元定义成方法,将复杂任务逐渐分解成更容易管理的子型小任务。由于一个大的业务功能需要由多个对象共同实现,在最终的编程模型中,很多对象将通过一系列方法调用来实现通信,执行任务。
面向对象的应用程序在运行时,经常会发生一系列的方法调用,从而形成“方法调用栈”,异常的传播则相反:只要异常没有被完全的捕获,异常从发生异常的方法逐渐向外传播,首先传给该方法的调用者,该方法的调用者再次传给其调用者,直到最后传给main方法,要是main方法还是没有处理这个异常的话,JVM会中止程序,并打印出异常的跟踪栈信息。

package linkin;

class SelfException extends RuntimeException
{
	SelfException(){}
	SelfException(String msg)
	{
		super(msg);
	}
}
//定义一个测试类,1-2-3这样子的调方法,然后再在测试1的时候,报异常了
//但是异常的堆栈打印信息却是3-2-1这样子输出的
public class Linkin
{
	public static void main(String[] args)
	{
		firstMethod();
	}
	public static void firstMethod()
	{
		secondMethod();
	}
	public static void secondMethod()
	{
		thirdMethod();
	}
	public static void thirdMethod()
	{
		throw new SelfException("自定义异常信息");
	}
	
}




  • 成功的异常处理应该实现如下4个目标:
1,是的程序代码混乱最小化
2,捕获并且保留诊断信息
3,通知合适的人员
4,采用合适的方式结束异常活动。
  • 具体的在开发中,要注意的是:
1,不要过度使用异常
2,不用使用过于庞大的try块
3,避免使用catch all语句
4,不要忽略捕获到的异常


  • 开发中的两个道理:
①如何控制 try 的范围:根据操作的连动性和相关性,如果前面的程序代码块抛出的错误影响了后面程序代码的运行,那么这个我们就说这两个程序代码存在关联,应该放在同一个 try 中。
② 对已经查出来的例外,有 throw(积极)和 try catch(消极)两种处理方法。对于 try catch 放在能够很好地处理例外的位置(即放在具备对例外进行处理的能力的位置)。如果没有处理能力就继续上抛。



posted on 2014-12-01 22:50  LinkinPark  阅读(210)  评论(0编辑  收藏  举报