从头开始学java-错误处理

从头开始学java--异常处理



一.异常的定义

异常是程序在运行时发生的不正常情况。

java的面向对象实在是忒牛了!不正常情况也被封装成了一个对象。

异常有两种,一种严重的,用Error类进行描述;一种是不是太严重的,用Exception类进行描述。对于Error,就已经是无药可救的错误了,所以我们不进行处理,直接交给JVM处理。我们所做的异常处理是针对Exception进行处理。


一个异常如下:

class ExceptionDemo1
{
	int div(int a, int b)
	{
		return a / b;
	}
}

public class ExceptionTest1 {
	public static void main(String[] args)
	{
		ExceptionDemo1 demo1 = new ExceptionDemo1();
		int result = demo1.div(2,0);
		System.out.println(result);
	}
}
结果:

从头开始学java-错误处理

一个异常,JVM默认的异常处理机制是停止运行报错。如果不想停机,那么我们就需要自己对异常进行处理,而不是给JVM处理,因为JVM的处理有点儿“狠”。


那么这样一个“低级的”错误,为什么不用一个if语句直接判断然后输出“除数不能为0呢”?

原因有以下几个:

1.曾经看过一份数据,一个程序中有一半以上是为了处理程序的异常的!这么多的处理条件,如果都和功能代码写在一起,那么代码必定会难于理解,而用异常处理机制会将异常处理的代码封装起来,使程序更容易理解。

2.异常处理机制提供抛出异常的机制,如果一段代码中的异常处理不了,可以将这个异常抛出,让调用模块处理异常。这样在我们移植代码时会省去不少麻烦。

3.异常处理有很大一部分是为了我们调试方便,我们用异常处理这个机制可以提供异常的一些详细信息,便于调试。

4.异常毕竟是少数情况,大部分的错误判断语句都是用不上的,有了异常,我们直接用一些java为我们提供的异常类,也可以让我们少写一些代码。

综上,异常处理机制比错误判断要好得多。使用异常处理机制的代码更加健壮,程序更稳定,安全。

下面就看一下怎么使用异常处理:


二.异常处理三部曲

异常处理三部曲,try,catch,finally

try

{

被检测的代码;

}

catch(异常类  异常变量)

{

处理异常的代码;

}

finally

{

一定会执行的语句;

}


一个异常处理的例子:

class ExceptionDemo1
{
	int div(int a, int b)
	{
		return a / b;
	}
}

public class ExceptionTest1 {
	public static void main(String[] args)
	{
		ExceptionDemo1 demo1 = new ExceptionDemo1();
		//异常处理第一步,try被检测的代码
		try
		{
			int result = demo1.div(2,0);
			System.out.println(result);
		}
		//异常处理第二部,catch,通过基类Exception的多态性接收子类的各种异常
		catch(Exception e)
		{
			//打印异常的一些信息
			System.out.println(e.getMessage());
			//打印异常的名称及信息
			System.out.println(e.toString());
			//打印异常的详细信息
			e.printStackTrace();
			//终止程序
			return;
		}
		//异常处理第三步,finally中的内容一定会执行,不管怎么样,即使异常处理终止了程序,finally中的代码也会执行
		finally
		{
			System.out.println("finally!");
		}
		
		//写在这里的代码,如果异常处理终止了程序,这里的代码是不会执行的。
	}
}
结果:

从头开始学java-错误处理

要点:

1.try块中只要发生了异常,就抛出一个异常,发生异常的语句下面的语句都不运行了。

2.catch块接受try块中产生的异常,如果没有catch那么虚拟机会接收这个异常(虚拟机的处理方式很残暴,直接罢工)。catch块中根据异常,写出处理异常的代码。

3.finally块中的代码,一定会执行。有时异常处理会终止程序,那么下面的程序将不被执行,一些资源释放的代码就不能执行,将会引起资源不足。所以将这些代码放在finally块中,即使程序终止,finally块中的代码也会执行。

4.异常处理是在异常的时候才会执行catch块中的内容,在try块中没有抛出异常的时候,程序正常运行,但是finally块中的内容也会执行。

5.所有的异常都继承Exception类,包括以后我们自己定义的异常。


三.声明异常&抛出异常

当我们编写一个功能,给其他人调用时,如果这个功能中有可能有异常,如果调用者不进行异常处理,那么会导致停机,所以我们需要声明一下异常,强制调用者进行异常处理。声明的关键字为throws
例如:
class ExceptionDemo2
{
	//同throws关键字声明一个异常
	int div(int a, int b) throws Exception
	{
		return a / b;
	}
}

public class ExceptionTest2 {
	public static void main(String[] args)
	{
		ExceptionDemo2 demo2 = new ExceptionDemo2();
		//未处理异常
		int result = demo2.div(4, 1);
	}
}
结果:
从头开始学java-错误处理


如果有throws声明的异常,而我们没有对异常进行处理或者继续抛出,编译会失败。

处理方法:
1.用异常处理方式处理
class ExceptionDemo2
{
	//同throws关键字声明一个异常
	int div(int a, int b) throws Exception
	{
		return a / b;
	}
}

public class ExceptionTest2 {
	public static void main(String[] args)
	{
		ExceptionDemo2 demo2 = new ExceptionDemo2();
		//通过try捕获异常
		try
		{
			int result = demo2.div(4, 0);
			System.out.println(result);
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
}

2.继续抛出
class ExceptionDemo2
{
	//同throws关键字声明一个异常
	int div(int a, int b) throws Exception
	{
		return a / b;
	}
}

public class ExceptionTest2 {
	//继续用throws关键字声明异常,即抛出去,供下一个调用它的函数处理,最终到虚拟机
	public static void main(String[] args) throws Exception
	{
		ExceptionDemo2 demo2 = new ExceptionDemo2();
	
			int result = demo2.div(4, 0);
			System.out.println(result);
	}
}

要点:
1.用throws关键字声明异常,调用这个方法的方法就必须对这个异常声明作出反应,要么进行处理,要么继续声明,让下个调用方法的方法处理,直到JVM,不过最好自己处理,尽量少抛。
2.如果没有以上两种处理,则编译不会通过,是java强制进行异常处理的一种措施,保证了程序的稳定性与安全性。


四.多异常处理

之前的例子一直抛出的是基类Exception类异常,但是在实际中会有很多种异常,所以我们可以根据不同情况抛出不同的异常,这些异常都是派生于Exception类。大致分为两种异常,Check类型异常(受控类异常,throws抛出的异常为受控类异常),RunTime异常(可以随时用throw抛出异常对象)。


class ExceptionDemo2
{
	//同throws关键字声明两个异常,一个越界异常,一个除零异常
	int div(int a, int b) throws ArithmeticException,ArrayIndexOutOfBoundsException
	{
		int[] arr = new int[a];
		System.out.println(arr[100]);
		return a / b;	
	}
}

public class ExceptionTest2 {
	//继续用throws关键字声明异常,即抛出去,供下一个调用它的函数处理,最终到虚拟机
	public static void main(String[] args) 
	{
		ExceptionDemo2 demo2 = new ExceptionDemo2();
		try
		{
			int result = demo2.div(4, 0);
			System.out.println(result);
		}
		//捕获ArithmeticException异常
		catch(ArithmeticException e)
		{
			//异常处理代码
			e.printStackTrace();
		}
		//捕获ArrayIndexOutOfBoundsException异常
		catch(ArrayIndexOutOfBoundsException e)
		{
			//异常处理代码
			e.printStackTrace();
		}
	}
}

要点:
1.声明异常throws后面可以跟若干个异常,最好声明具体的异常。前面声明了几个异常,后面就要对应几个catch块。
2.异常处理捕获的异常,根据不同的异常,选择不同的catch块运行。但是注意,如果有一个catch块捕获了异常,处理后,后面的catch就不会执行了。
3.catch处理异常时,需要具体给出异常处理代码,不要简单的只是输出异常信息。
4.catch处理异常时,如果异常类之间有继承关系,父类的异常一定要放在后面。因为父类异常会接收所有子类异常(多态性),下面的子类异常处理就会失去意义。

五.自定义异常

虽然JDK中为我们定义了很多很多异常类,但是毕竟不同的程序对应不同的异常,我们仍需要自己定义一些异常来使用。

class FushuException extends Exception
{
	//无参数构造函数,没有异常信息
	FushuException()
	{
		
	}
	//有参数构造函数,参数为异常信息,又throw时赋值
	FushuException(String msg)
	{
		super(msg);
	}
}

class ExceptionDemo
{
	//注意用throw抛出异常的时候,也要注意在函数后用throws声明异常
	public static void div(int a, int b) throws FushuException
	{
		//判断抛出异常的条件
		if (b < 0)
			//注意new
			throw new FushuException("除数小于0");
		System.out.println(a / b);
	}
}

public class ExceptionTest3
{
	public static void main(String[] args)
	{
		try
		{
			ExceptionDemo.div(2, -1);
		}
		catch(FushuException e)
		{
			e.printStackTrace();
		}
	}
}


要点:
1.一般函数内有异常需要throw时,函数上也要用throws声明异常。Runtime异常除外。
2.自定义异常类继承Exception类,可以在自定义异常类中定义一些信息。
3.需要抛出异常的时候用throw关键字,先判断条件,如果符合抛出异常的条件,那么就throw new 异常类。注意new关键字,异常是一个对象。
4.注意throw和throws的区别,throws是声明异常,在函数上,可以接若干个异常类;throw是抛出一个异常,在函数内,后面跟一个函数对象(new出来的)。


六.Runtime异常

Exception中有一种特殊的异常,RuntimeException运行时异常。
之所以特殊,是因为在抛出异常的函数上可以不声明异常,即不用throws关键字。在有异常时,可以不进行异常处理也可以编译通过。说白了,就是发生这个异常的时候,就是想让这个异常暴露出来,不用异常处理,而是修改代码。

class RunTime
{
	//runtime异常可以不声明异常
	public static void div(int a, int b)
	{
		if (b == 0)
			//抛出一个Runtime异常
			throw new ArithmeticException();
		System.out.println(a / b);
	}
}

public class ExceptionTest4 
{
	public static void main(String[] args)
	{
		//runtime异常可以不进行异常处理
		RunTime.div(3, 0);
	}
}

结果:
从头开始学java-错误处理

要点:
1.运行时异常可以不声明,可以不处理,就是要JVM将程序停掉,然后修改代码。
2.自定义异常如果也想达到以上效果,那么可以让其继承Runtime类。

七.关于异常要注意的

1.在中间层组件中抛出异常,在界面层组件中捕获异常
2.在底层组件中捕获JVM 抛出的“只有程序员能看懂的”异常,转换为中间层的业务逻辑异常,再由界面层捕获以提供有意义的信息。
3.自身能够处理的异常,不要再向外界抛出。
4.尽可能地在靠近异常发生的地方捕获并处理异常。
5.尽可能地捕获最具体的异常类型,不要在中间层用catch(Exception)“吃掉”所有异常
6.在开发阶段捕获并显示所有异常信息,发布阶段要移除部分代码,以避免“过于专业”的异常信息困扰用户,特别地,系统发布之后,不要将服务端异常的详细信息发给客户端,以免被黑客利用。





其他知识点:嵌套异常捕获,父类子类异常处理的关系


从头开始学java-错误处理