异常处理
认识异常
首先看下面的代码:
class User
{
private String uname;
private int age;
public void setUname(String uname)
{
this.uname = uname;
uname = new String(uname.getBytes("8859_1"));
}
public String getUname()
{
return uname;
}
public void setAge(String age)
{
this.age = Integer.parseInt(age);
}
public int getAge()
{
return age;
}
}
应该说这是一段非常普通的代码,所以没有给任何注释。你可能还没有发现里面的问题,如果你对这段代码进行编译,会差生下面的错误:
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
Unhandled exception type
UnsupportedEncodingException
错误产生在setUname方法中的编码转换的代码,这个代码我们经常会用到。如果你使用的是集成开发环境,在编写的时候系统就会提示。
因为String的getBytes方法的定义中声明了UnsupportedException异常,也就是说在调用这个方法的时候可能会产生这样的异常,我们必须对可能差生的异常进行处理。
先不考虑怎样解决这个问题,先把这行代码注释,然后我们编写主类来调用另外一个方法,代码如下:
package com.li.basic;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
public class ExceptionTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
User u = new User();
u.setAge("111");
System.out.println(u.getAge());
}
}
运行结果为:
111
如果把setAge中的“111”改成“aaa”,然后再运行,会得到下面的结果:
![](https://p-blog.csdn.net/images/p_blog_csdn_net/JavaEETeacher/4cfac30970a54d6094f9ac5800faeb99.png)
错误是在setAge方法中产生的,根源在于把字符串“aaa”转换成数字的时候出现了错误,因为“aaa”不能转换成数字,Integer.parseInt(String str)方法在被调用的时候可能会产生NumberFormatException,但是因为我们没有进行任何的处理,所以程序在运行过程中就死掉了。
接下来,我们分析一下这两种异常,先说第一种“UnsupportedEncodingException”,因为String的getBytes方法的作用是根据用户指定的编码方式得到字符串,如果用户指定的编码方式系统识别不了,这时候就没有办法给用户返回字符串了,只能告诉用户系统不支持用户指定的编码,也就是上面看到的“UnsupportedEncodingException”。所以可以认为异常实际上是程序运行时的一种状态,通常都是方法执行过程中产生的,所以也可以把异常理解为方法的一种返回值。就像上面的方法正常情况下返回字符串对象,而不正常的时候就返回“UnsupportedEncodingException”对象。
再看“NumberFormatException”,它是在把字符串转换成数字的时候产生的,是Integer的parseInt方法产生的,如果我们给的是数字组成的字符串,可以把字符串转换成数字返回给用户,但是如果我们给的不是数字组成的字符串,就像上面的例子中给的,这时候转换方法就不能正常工作了,不能给我们返回字符串,所以给我们返回了“NumberFormatException”。
所以对于异常我们可以这样理解:异常是方法的一种特殊的返回值,我们在调用这个方法的时候,如果这个方法有这样的特殊的返回值,我们就需要处理这种特殊的返回值。
基本的异常处理
所有的异常都是Exception类的派生类。Java是面向对象的语言,所有的处理代码都应该在方法中,所以异常的产生和异常的处理都在方法中进行。
在方法中处理异常通常有两种做法:第一种是直接处理异常,第二种是通过异常的声明把异常抛给该方法的调用者。
异常处理
要进行异常处理,首先应该确定要处理的异常是什么,这里假设要处理的异常是MyException,
异常处理的基本格式:
try
{
可能会产生异常的代码
}catch(可能会产生的异常类 对象名)
{
异常产生时的处理代码
}
把可能会出错的代码放在try语句里面,catch中写可能产生的异常类的对象,在catch后面的扩号中编写异常产生后的处理代码。如果不产生异常,catch后面的代码不会执行,如果try语句中有异常产生,并且异常类型与catch中类型匹配,则执行catch后面扩号中的代码。
例:现在使用异常处理对本章开头所描述的异常进行处理。
分析:产生异常的代码是:
uname = new String(uname.getBytes(“8859_1”));
String类的getBytes方法可能会产生的异常是:
UnsupportedEncodingException
所以上面的方法setUname的代码可以改为:
public void setUname(String uname)
{
this.uname = uname;
try {
uname = new String(uname.getBytes("8859_1"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
catch后面的异常类是可能会产生的异常类,e是对象的名字,这个名字与普通方法中的参数相同。后面的大扩号中的代码是异常产生时的处理代码。
现在再使用下面的代码调用这个setUname方法:
package com.li.basic;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
public class ExceptionTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
User u = new User();
u.setUname("中文");
System.out.println(u.getUname());
}
}
运行的结果为:
中文
如果把setUname中的“8859_1”改成“88591”,如下:
public void setUname(String uname)
{
this.uname = uname;
try {
uname = new String(uname.getBytes("88591"));
} catch (UnsupportedEncodingException e) {
System.out.println("编码方式写错了!");
}
}
再运行上面的程序,结果如下:
编码方式写错了!
中文
因为编码88591不存在,所以在运行过程中产生了异常,这个异常与catch中的异常匹配,所以执行catch后面的异常处理代码。输出了上面的结果。
如果把catch中的UnsupportedEncodingException改为Exception,如下面的代码:
public void setUname(String uname)
{
this.uname = uname;
try {
uname = new String(uname.getBytes("88591"));
} catch (Exception e) {
System.out.println("编码方式写错了!");
}
}
修改完之后,运行的结果没有发生变化。因为UnsupportedEncodingException是Exception的派生类,所以在catch可以写异常类的父类或者祖先类。
如果在try语句中可能会产生多种类型的异常,假设是Exception1和Exception2,可以使用两个catch语句,分别捕获这两种类型的异常,看下面的代码:
try
{
//可能会产生异常的代码
}
catch(Exception1 e1)
{
//产生Exception1类型的异常时的处理代码
}
catch(Exception2 e2)
{
//产生Exception2类型的异常时的处理代码
}
注意:如果Exception1是Exception2的父类或者祖先类,后面的catch语句将不起作用,因为第一个catch语句也能够捕获第二种类型的异常。但是可以把子类放在前面,把父类放在后面。
声明异常
另外一种处理异常的方式就是声明异常,基本格式如下:
方法修饰符 方法返回值 方法名(参数列表) throws 异常列表
{
可能会产生异常的代码
}
就是在方法的定义后面通过throws声明可能会产生的异常,如果在方法体中可能产生AException,在throws后面写AException就可以了,如果在方法体中可能会产生多种异常,就在throws后面把多个异常都写上,中间用分号割开。
基本思路就是:A方法在调用B方法的时候可能会产生异常,但是没有处理,所以相当于A方法在执行过程中也可能会产生异常,所以在A方法的后面声明异常就可以了。让调用A方法的方法再去处理异常。
例:我们使用这个方法对本章开头的第二种异常进行处理。
分析:可能会产生的异常是NumberFormatException,只需要在方法的后面使用throws声明这个异常就可以了。方法的代码如下:
public void setAge(String age) throws NumberFormatException
{
this.age = Integer.parseInt(age);
}
两种方式的比较
使用try…catch对异常进行处理,可以在调用方法的时候对异常进行处理,但是也丢失了一些信息,也就是说对于方法调用者来说根本不知道在方法的执行过程中是否有异常产生,是否完成了相应的功能。
在上面的例子中,使用try…catch对异常进行了处理,但是对于主函数来说根据不知道在方法setUname中是否产生了异常。也就是丢失了信息。
使用异常声明的方式处理异常,虽然没有对异常处理,但是对于方法的调用者来说知道可能会发生什么异常。从这一点上来说,声明异常的方式要比try…catch处理异常的方式好。但是声明异常的方式不能无限制的声明,总是有处理这个异常的地方。最终还是需要使用try…catch对异常进行处理。
finally
我们在使用try…catch进行异常处理的时候,如果try语句是由多条语句组成的,如果产生异常,在可能会产生异常的代码之后的代码就不会执行了,如果不产生异常,catch中的代码就不会执行了,有时候我们希望不管是否产生异常,有些代码都会执行,这时候就可以使用finally。基本结构如下:
try
{
//可能会产生异常的代码
}
catch(Exception e)
{
//异常处理代码
}
finally
{
//不管是否产生异常都要执行的代码
}
异常的产生
前面介绍了怎样对异常进行处理,但是异常是怎样产生的呢?异常可以认为是方法的一种特殊的返回值,所以异常是在方法的执行过程中产生的。
方法中如果有返回值类型,必须使用return语句返回执行的结果,如果在方法执行过程中产生异常,不能返回正常的结果,这时候应该返回一个异常,通过关键字throw来返回这个异常,通常叫做抛出异常。
如果想抛出某种类型的异常,throw后面跟该异常类型的对象即可。例如,想抛出一个Exception类型的异常,可以使用:
throw new Exception(“产生异常了!”);
括号中的信息是异常信息。
如果在方法中通过throw关键字抛出了一个异常,那么在方法的定义中必须声明这种异常,与前面介绍的异常处理中的异常声明方式是相同的。
下面通过例子来看一下具体的用法。再回到我们上面的例子,对年龄赋值的setAge方法,上面通过声明的方式对把字符串转换成数字可能会产生的异常进行了处理,仔细察看这个方法,如果用户输入的是字符串“333”,按照上面的代码,程序执行不会有问题,但是我们想一下,谁的年龄能到333岁,我还没有听说过有哪个人,只是听说过某些动物和植物能够或那么常。所以这里的赋值应该认为是不成功的,不应该赋值,应该告诉用户输入的年龄太大了。下面是修改后的代码:
public void setAge(String age) throws NumberFormatException,Exception
{
int temp = Integer.parseInt(age);
if(temp>0&&temp<120)
this.age = temp;
else
throw new Exception("年龄应该在0到120之间!");
}
主函数中的代码修改如下:
package com.li.basic;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
public class ExceptionTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
User u = new User();
try
{
u.setAge("abc");
//u.setAge("230");
//u.setAge("20");
System.out.println("年龄为:"+u.getAge());
}
catch(NumberFormatException e1)
{
System.out.println("应该输入数字");
}
catch(Exception e2)
{
System.out.println("应该输入0到120的数字!");
}
//u.setUname("中文");
//System.out.println(u.getUname());
}
}
如果输入“abc”,运行结果如下:
应该输入数字
如果输入“230”,运行结果如下:
应该输入
0
到
120
的数字!
如果输入“20”,运行结果如下:
年龄为:
20
注意:我们这里假设年龄的合法值是0到120,如果你的系统有特殊的要求,应该根据你的实际情况来确定。
自定义异常
系统提供了很多种异常,前面提到的都是系统的异常,但是有时候为了方便,需要自己来定义异常。
自定义异常,就是根据需要创建自己的异常类。例如,我们前面的例子中对年龄赋值的时候,对年龄进行了判断如果年龄不合适就抛出异常,使用的是系统提供的Exception类,可能我们在其它地方使用的时候还会抛出Exception类型的异常,但表示的不是年龄不合适,而是其它类型的异常,这样就没有办法区分这两种类型的异常,所以需要创建自己的异常类来表示各种不同类型的异常。
自定义异常类,需要从Exception类或者它的子类派生。下面是自定义的异常,这个异常表示年龄不合适:
package com.li.basic;
public class InvalidateAge extends Exception {
public InvalidateAge(String msg)
{
super(msg);
}
}
这样在setAge方法中就可以使用这个异常了:
public void setAge(String age) throws NumberFormatException,InvalidateAge
{
int temp = Integer.parseInt(age);
if(temp>0&&temp<120)
this.age = temp;
else
throw new InvalidateAge("年龄应该在0到120之间!");
}
这样在调用这个方法的时候,就知道确切的异常类型了:
package com.li.basic;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
public class ExceptionTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
User u = new User();
try
{
//u.setAge("abc");
u.setAge("230");
//u.setAge("20");
System.out.println("年龄为:"+u.getAge());
}
catch(NumberFormatException e1)
{
System.out.println("应该输入数字");
}
catch(InvalidateAge e2)
{
System.out.println("应该输入0到120的数字!");
}
}
}
Web应用中的异常处理
在Web应用中基本上所有的异常都可以通过Java中基本的异常处理机制来完成。但是在Web应用中有一些特殊的地方,JSP和Servlet是容器来调用的,它们在调用过程中也可能会产生异常,如果我们不对这些异常进行处理,用户将看到系统的异常堆栈信息,对用户来说是不友好的,并且会使用户对系统失去信心,但是这些异常我们不能使用try…catch来处理,但是Web应用提供了相应的机制。
JSP文件在运行的时候系统会使用try…catch来处理页面产生的异常,如果产生异常,系统会抛出一个异常对象exception,这就是JSP提供的几个内部对象之一,这个异常对象我们必须编写异常处理文件来接收这个异常对象,然后对异常进行处理。但是异常处理文件和产生异常的文件不会自动关联,需要我们来处理。
这些比较特殊的异常主要是通过配置来完成的。有两种类型:为某个网页指定异常处理文件;为某种类型的异常指定异常处理文件。
为某个网页指定异常处理的文件
这种方式需要为每个需要进行异常处理的文件指定异常处理文件,这样当文件在运行过程中产生异常了,就会调用异常处理文件对异常进行处理,用户就看不到系统出现的错误堆栈信息,可以通过异常处理文件给用户比较友好的提示。
编写异常处理文件
通过page标签的isErrorPage属性来设置某一个页面为异常处理页面,假设error.jsp为异常处理文件,则error.jsp文件中应该有这样一句话标记该文件为异常处理文件:
<%@ page isErrorPage=”true” %>
只有设置了这个属性的文件才是异常处理文件,才能够访问exception中的信息。访问exception中的异常信息可以通过下面的几个方法:
toString(),把异常信息转换成字符串。
getMessage(),获取异常信息,同样是以字符串的形式返回。
printStackTrace(),使用标准输出流输出异常信息。
为页面指定异常处理文件
如果希望error.jsp能够对当前文件的异常进行处理,需要通过errorPage指定error.jsp文件为异常处理文件,在该文件的上面增加这样一行代码:
<%@ page errorPage=”error.jsp”%>
这样在页面执行过程中,如果产生的异常没有处理,这个异常会抛给容器,然后容器根据所配置的errorPage属性查找到error.jsp,调用error.jsp。
为某种类型的异常指定异常处理文件
前面的方式是为每个文件指定异常处理文件,这样需要在每个文件中进行设置,不方便,另外有些异常是不能通过这种方式处理的,例如用户在客户端输入了一个不存在的网页,通常会报下面的错误:
404
错误(图片)
这个错误就是系统在找不对请求文件的时候报的错,不管在什么文件中都无法设置。另外一种方式就是设置统一的异常处理文件,为不同类型的异常设置异常处理文件,这个设置需要在web.xml中设置。
把异常信息显示给用户
系统在处理的过程中可能会产生各种不同类型的异常,用户输入的信息不合法,用户的请求在执行过程中产生异常了,这些信息应该让用户知道,否则用户根据不清楚自己请求的处理是否成功,对用户来说是不友好的。异常信息是我们在处理过程中产生的,但是给用户显示异常信息的是JSP文件,怎样把异常信息显示给用户呢?我们前面介绍过信息页面之间的信息共享,可以采用这种方式,先把信息保存在request对象中,然后在JSP页面中提取信息并显示出来。
异常信息的传递
把异常信息显示给管理员
通常与用户相关的异常信息可以通过页面返回给用户,但是有时候异常是因为系统的原因,这时候应该让管理员知道异常信息,而不是让用户知道异常信息,例如系统的数据库连接不上,这样的错误应该通过管理员,而不是让用户知道数据库有问题,可以告诉用户系统正在维护。
因为管理员可能不能一直在服务器面前,所以通常需要使用日志的方法把异常记录到日志文件中,然后管理员通过察看日志文件去察看异常信息。