异常

223 阅读6分钟
  1、所有的异常都是由Throwable继承而来,但在下一层立即分解为两个分支:Error和Exception。
     Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误。
     Exception类层次结构又分为两个分支:一个分支派生于RuntimeException;另一个分支包含其他异常。
          划分两个分支的规则是: 由程序错误导致的异常属于RuntimeException;而程序本身没有问题,但由于像I/O错误这类问题导致的异常属于其他异常。
    
    
     派生于RuntimeException的异常包含下面几种情况:
      1> 错误的类型转换
      2> 数组访问越界
      3> 访问null指针
      
      不是派生于RuntimeException的异常包括:
       1> 试图在文件尾部后面读取数据
       2> 试图打开一个不存在的文件
       3> 试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在
       
       
       Java语言规范将派生于Error类或RuntimeException类的所有异常称为非受查(unchecked)异常,所有其他的异常称为受查(checked)异常。
      
    创建异常类
         如 :定义一个派生于IOException的类,定义的类应该包含两个构造器,一个是默认的构造器;另一个是带着详细描述信息的构造器
         class FileFormatException extends IOException {
             public FileFormatException() {}
             public FileFormatException(String gripe) {
                 super(gripe);
             }
         }
         
    
    捕获异常
         要想捕获一个异常,必须设置try/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();
             }
         }
         
         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说明符,以便告知调用者这个方法可能会抛出异常。
         
         
    
    
    再次抛出异常与异常链
        
         在catch子句中可以抛出一个异常,这样做的目的是改变异常的类型。
         捕获异常并将它再次抛出的基本方法:
         
             try { 
                  access the database
             } catch (SQLException e) {
                 throw new ServletException("database error : " + e.getMessage());
             }
         
         
         一种更好的异常处理方式,并且将原始异常设置为新异常的“原因”:
         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;
             }
             
             
             
             
     finally  子句 
     
     不管是否异常被捕获,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子句中捕获的异常。在这种情况下,程序将执行try语句块中的所有代码,直到发送异常为止。此时,将跳过try语句块中的剩余代码
        ,转去执行与该异常匹配的catch子句中的代码,最后执行finally子句中的代码。
        如果catch子句没有抛出异常,程序将执行try语句块之后的第一条语句。在这里
        执行标注1、3、4、5、6处的语句
        
        3> 代码抛出了一个异常,但这个异常不是由catch子句捕获的。在这种情况下,程序将执行try语句
        块中的所有语句,直到有异常被抛出为止。此时,将跳过try语句块中的剩余代码,然后执行finally
        子句中的语句,并将异常抛给这个方法的调用者。执行标注1、5处的语句。
        
        
    当finally子句包含return语句时,将会出现一种意想不到的结果。假设利用return语句从try语句块中退出。在方法返回前,finally子句的内容将被执行。如果finally子句中也有一个return语句,这个返回值将会覆盖原始的返回值。
         
         public static int f(int n) {
             try {
                 int r = n * n;
                 return r;
             } finally {
                 if (n == 2) return 0;
             }
         }
         
         
     分析堆栈轨迹元素
        
         堆栈轨迹(stack trace)是一个方法调用过程的列表,它包含了程序执行过程中方法调用的
         特定位置。
         可以调用Throwable类的printStackTrace方法访问堆栈轨迹的文本描述信息。
         当Java程序正常终止,而没有捕获异常时,这个列表就会显示出来。
             Throwable t = new Throwable();
             StringWriter out = new StringWriter();
             t.printStackTrace(new PrintWriter(out));
             String description = out.toString();
             
         一种更灵活的方法是使用getStackTrace方法,它会得到StackTraceElement对象的一个数组。如:
             Throwable t = new Throwable();
             StackTraceElement[] frames = t.getStackTrace();
             for (StackTraceElement frame : frames)
                analyze frame
                
          
          静态的Thread.getAllStackTrace方法,可以产生所有线程的堆栈轨迹。
          
             Map<Thread, StackTraceElement[]> map = Thread.getAllStackTraces();
             for (Thread t : map.keySet()) {
                 StackTraceElement[] frames = map.get(t);
                 analyze frames
             }
             
            
            
            
    
    
    
    记录日志
    
    
      可以很容易地取消全部日志记录,或者仅仅取消某个级别的日志,而且打开或关闭这个操作很容易。
      可以很简单地禁止日志记录的输出,因此,将这些日志代码留在程序中的开销很小。
      日志记录可以被定向到不同的处理器,用于在控制台中显示,用于存储在文件中等。
      日志记录可以采用不同的方式格式化,如:纯文本 XML
      在默认情况下,日志系统的配置由配置文件控制。
      
      
      可以调用getLogger方法创建或获取记录器:
      private static final Logger myLogger = Logger.getLogger("com.mycompany.myapp");
      
      未被任何变量引用的日志记录器可能会被垃圾回收。为了防止这种情况发生,要像上面一样,用一个静态变量存储日志记录器的一个引用。
      
      日志记录器的父与子之间将共享某些属性。
      
      
      有以下7个日志记录器级别:
      1> SEVERE
      2> WARNING
      3> INFO
      4> CONFIG
      5> FINE
      6> FINER
      7> FINEST