java学习之路----捕获异常

1.捕获异常
如果某个异常发生的时候没有在任何地方进行捕获,那程序就会终止执行,并在控制台上打印出异常信息,其中包括异常的类型和堆栈的内容。对于图形界面程序(applet和应用程序),在捕获异常之后,也会打印出堆栈的信息,但程序将返回到用户界面的处理循环中。
要想捕获一个异常,必须设置try/catch语句块。最简单的try语句块如下所示:

 try
   {
     code 
     more code
     more code
     }
     catch (ExceptionType e)
     {
         handle  for  this  type
         }

如果在try语句块中的任何代码抛出了一个在catch子语句中说明的异常类,那么
1)程序将跳过try语句的其余代码。
2)程序将执行catch子句中的处理器代码
如果在try语句中的代码没有抛出任何异常,那么程序将跳过catch子句。
如果方法中的任何代码抛出了一个在catch子句中没有声明的异常类型,那么这个方法就会立刻退出(希望调用者为这种类型的异常设计了catch子句)。
例:

public  void read(String  filename)
{
   try
   {
       InputStream  in=new  FileInputStream(filename);
       int  b;
       while((b=in.read())!=-1)
       {
            process  input
            }
}
catch (IOException  exception)
{
    exception.printStackTrace();
    }
}

try语句中的大多数代码都很容易理解:读取并处理字节,直到遇到结束符位置,read方法可能抛出一个IOException异常。在这种情况下,将跳出整个while循环,进入catch子句,生成一个栈轨迹。还有其他选择吗?
通常最好的选择是什么也不做,而是将异常传递给调用者。如果read方法出现了错误,就让read方法的调用者去操心!如果采用这种方法,就必须声明可能会抛出一个IOException。

  public  void  read(String  filename)  throws  IOException
   {
      InputStream  in=new FileInputStream(filename);
      int b;
      while((b=in.read())!=-1)
      {
         process  input
         }
}

通常应该捕获那些知道如何处理的异常,而将那些不知道怎么样处理的异常继续传递。如果传递一个异常,就必须在方法的首部添加一个throws说明符,以便告知调用者这个方法可能会抛出异常。
仔细阅读以下Java API文档,以便知道每个方法可能会抛出哪种异常,然后再决定是自己处理,还是添加到throws列表中。
如果编写一个覆盖超类的方法,而这个方法又没有抛出异常,那么这个方法就必须捕获方法代码中出现的每一个受查异常。不允许在子类的throws说明符中出现过超类方法所列出的异常类范围。

2.捕获多个异常

在一个try语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理。可以按照下列方式为每个异常类型使用一个单独的catch子句:

  try
  {
      code that  might  throw  exceptions
      }
      catch (FileNotFoundException  e)
      {
          emergency   action  for  missing  files
          }
    catch  (UncknowHostException  e)
    {
        emergency    action  for  unknown  hosts
        }
        catch (IOExcception  e)
        {
           emergency   action  for  all  other  I/O  problems
           }

异常对象可能包含与异常本身有关的信息。要想获得对象的更多信息,可以试着使用

e.getMessage()

在Java SE 7中,同一个catch子句中可以捕获多个异常类型。例如,假设对应缺少文件和未知主机异常的动作是一样的,就可以合并catch子句:

try
{
   code  that  might  throw  exceptions
   }
   catch (FileNotFoundException | UnknowHostException  e)
   {
      emergency   action  for  missing  files  and  unknown hosts
      }
      catch(IOException  e)
      {
        emergency  action  for  all  other  I/O  problems
     }

只有当捕获的异常类型彼此之间不存在子类关系时才需要这个特性。

3.再次抛出异常与异常链

在catch子句中可以抛出一个异常,这样做的目的是改变异常的类型。如果开发了一个供其他程序员使用的子系统,那么,用于表示子系统故障的异常类型可能会产生多种解释。
ServeletException就是这样一个异常类型的例子。执行servlet的代码可能不想知道发生错误的细节原因,但希望明确地知道servlet是否有问题。
下面给出捕获异常并将它再次抛出的基本方法:

   try
   {
      access  the   database
  }
  catch(SQLException  e)
  {
      throw  new  ServletException("database  error:"+e.getMessage());
      }

这里,ServeleException用带有异常信息文本的构造器来构造。不过更好的是将原始异常设置为新异常的“原因”:

 try
  {
     access  the  database
     }
catch(SQLException  e)
{
  Throwable  se=new  ServletException("database   error");
  se.initCause(e);
  throw   se;
}

当捕获到异常时,就可以使用下面这条语句重新得到原始异常:

Throwable  e=se.getCause();

有时候你可能只想记录一个异常,再将它重新抛出,而不做任何改变:

try
{
   access  the  database
   }
catch(Exception  e)
{
  logger.log(level,message,e);
  throw  e;
}

4. finally子句

当代码抛出一个异常时,就会终止方法中剩余代码的处理,并退出这个方法的执行。如果方法获得了一些本地资源,并且只有这个方法自己知道,如果这些资源在退出方法之前必须被回收,那么就会产生资源回收问题。
Java中有解决方案,就是finally子句。如果使用Java编写数据库程序,就需要使用同样的技术关闭与数据库的连接。当发生异常时,恰当地关闭所有数据库的连接是非常重要的。
不管是否有异常被捕获,finally子句中的代码都会被执行。这个例子,程序将在所有情况下关闭文件:

   InputStream  in=new FileInputStream(......);
   try
   {
      //1
      code that  might  throw  exceptions
      //2
  }
  catch(IOException  e)
  {
     //3
     show  error  message
     //4
  }
  finally
  {
    //5
  in.close();
  }
  //6

上面这段代码中,三种情况会执行finally子句:

  1. 代码没有异常。这种情况下,程序首先执行try语句块中的全部代码,然后执行finally子句中的代码。随后继续执行try语句之后的第一条语句。也就是1->2->5->6
  2. 抛出一个在catch子句中捕获的异常。上例中是IOException异常。这种情况下,程序将执行try语句开中所有代码,直到发生异常。此时将跳过try语句块中的剩余代码,转去执行与该异常匹配的catch子句中的代码,最后执行finally子句中的代码。1->3->4->5->6
  3. 代码抛出了一个异常,但这个异常不是由catch子句捕获的。这种情况下,程序将执行try语句块中的所有语句,直到有异常被抛出为止。此时将跳过try语句块中的剩余代码,然后执行finally子句中的语句,并将异常抛给这个方法的调用者。

try语句可以只有finally语句,而没有catch子句。

   InputStream in=.....;
   try
   {
      code  that  might  throw   exceptions
      }
    finally
    {
       in.close();
  }

无论在try语句块是否遇到异常,finally子句中的in.close()语句都会被执行。当然如果真的遇到一个异常,这个异常将会被重新抛出,并且必须由另一个catch子句捕获。
在这里,强烈建议解耦合try/catch和try/finally语句块,这样可以提高代码的清晰度。

  InputStream   in=......;
  try
  {
     try
        {
            code  that might  throw  exceptions
      }
      finally
      {
         in.close();
         }
}

catch(IOException  e)
{
   show  error  message
}

内层的try语句块只有一个职责,就是确保关闭输入流。外层的try语句块也只有一个职责,就是确保报告出现的错误。
有时候,finally子句也会带来麻烦。例如,清理资源的方法也有可能抛出异常。假设希望能够确保在流处理代码中遇到异常时将流关闭。

  InputStram  in=.....;
  try
  {
  code  thart  might  throw  exceptions
  }
  finally
  {
    in.close();
  }

5.带资源的try语句

对于以下代码模式:

open a  resource 
try
{
   work  with  the  resource
   }
finally
{
  close the resouce
  }

假设资源属于实现了AutoCloseable接口的类,AutoCloseable接口有一个方法:

  void  close()  throws   Exception

带资源的try语句(try-with-resources)的最简形式为:

try(Resource res=...)
{
   work  with  res
}

try块退出时,会自动调用res.close()。下面例子读取一个文件中的所有单词:

try (Scanner  in=new Scanner(new  FileInputStream("/usr/share/dict/words")),  "UTF-8")
{
   while(in.hasNext())
      System.out.println(in.next());
}

这个块正常退出或者存在一个异常时,都会调用in.close()方法,就好像使用了finally块一样。
还可以指定多个资源:

try(Scanner  in=new Scanner(new FileInputStream("/use/share/dict/words/"),"UTF-8");
  PrintWriter  out=new  PrintWriter("out.txt")
{
   while(in.hasNext())
     out.println(in.next().toUpperCase());
}

不论这个块如何退出,in和out都会关闭。如果用常规方式手动编程,就需要两个嵌套的try/finally语句。
如果try块抛出一个异常,而且close方法也抛出一个异常,这就会带来一个难题。带资源的try语句可以很好地处理这个问题。原来的异常会重新抛出,而close方法抛出的异常会“被抑制”。这些异常将自动补货,并由addSuppressed方法增加到原来的异常。
只要需要关闭资源,就要尽可能使用带资源的try语句。

6.分析堆栈轨迹元素

堆栈轨迹(stack trace)是一个方法调用过程的列表,它包含了程序执行过程中方法调用的特定位置。当Java程序正常终止,而没有捕获异常时,这个列表就会显示。
使用getStackTrace方法,可以得到StackTraceElement对象的一个数组,可以在你的程序中分析这个对象数组,如:

Throwable  t=new Throwable();
StackTraceElement[]  frames=t.getStackTrace();
for(StackTraceElement  frame:frames)
    analyze  frame

StackTraceElement类含有能够获得文件名和当前执行的代码号的方法,同时,还含有能够获得类名和方法名的方法。toString方法将产生一个格式化的字符串,其中包含所获得的信息,
静态的Thread.getAllStackTrace方法,可以产生所有线程的堆栈轨迹:

 Map<Thread,StackTraceElement[]> map=Thread.getAllStackTrace();
 for(Thread  t:map.keySet())
 {
   StackTraceElement[]  frames=map.get(t);
   analyze  frames
}

例子:打印递归阶乘函数的堆栈情况。计算factorial(3),将会打印下列内容:
在这里插入图片描述
代码:
在这里插入图片描述
API java.lang.Throwable 1.0

  Throwable(Throwable  cause)  1.4
  Throwable(String  message,Throwable  cause)  1.4
  //用给定的“原因”构造一个Throwable对象
  Throwable  initCause(Throwable  cause)  1.4
  //将这个对象设置为“原因”。如果这个对象已经被设置为“原因”,则抛出一个异常。返回this引用
  Throwable  getCause()  1.4
  //获得设置为这个对象的“原因”的异常对象。如果没有设置“原因”,则返回null
  StackTraceElement[]  getStackTrace()  1.4
  //获得构造这个对象时调用堆栈的跟踪
  void  addSuppressed(Throwable  t)  7
  //为这个异常增加一个“抑制”异常”。这出现在带资源的try语句中。其中t是close方法抛出的一个异常
  Throwable[]  getSuppressed()  7
  //得到这个异常的所有“抑制”异常。一般是带资源的try语句中的close方法抛出的异常。

API java.lang.Exception 1.0

Exception(Throwable  cause)  1.4
Exception(String  message,Throwable  cause)
//用给定的“原因”创造一个异常对象

API java.lang.RuntimeException 1.0

RuntimeException(Throwable   cause)     1.4
RuntimeException(String  message,Throwable  cause)  1.4
//用给定的“原因”构造一个RuntimeException对象

API java.lang.StackTraceElement 1.4

String  getFileName()
返回这个元素运行时对应的源文件名。如果这个信息不存在,返回null
int   getLineNumber()
返回这个元素运行时对应的源文件行数。如果这个信息不存在,返回-1
String  getClassName()
返回这个元素运行时对应的类的完全限定名
String  getMethodName()
返回这个元素运行时对应的方法名。构造器名是<init>;静态初始化器名是<clinit>。这里无法区分同名的重载方法。
boolean   isNativeMethod()
如果这个元素运行时在一个本地方法中,则返回true
String  toString()
如果存在的话,返回一个包含类名,方法名,文件名和行数的格式化字符串
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值