资讯专栏INFORMATION COLUMN

Java编程思想学习录(连载之:异常)

pkhope / 484人阅读

摘要:系列博文目录编程思想学习录连载之一切都是对象编程思想学习录连载之初始化与清理编程思想学习录连载之内部类编程思想学习录连载之异常本篇文章将讲述关于异常的相关知识注本文首发于公众号,可长按或扫描下面的小心心来订阅基本概念使用异常来提供一致性的错

Thinking in java系列博文目录:

Java编程思想学习录(连载之:一切都是对象)

Java编程思想学习录(连载之:初始化与清理)

Java编程思想学习录(连载之:内部类)

Java编程思想学习录(连载之:异常)

本篇文章将讲述关于异常的相关知识

注: 本文首发于 My 公众号 CodeSheep ,可 长按扫描 下面的 小心心 来订阅 ↓ ↓ ↓

基本概念

Java使用异常来提供一致性的错误报告模型;且可集中错误处理;且任务代码与异常代码分割开来,易于理解和维护

虽然异常处理理论有终止模型恢复模型两种,但恢复模型很难优雅地做到,∴并不实用,实际中大家都是转向使用终止模型代码

一个异常抛出后发生的两件事:① 使用new在堆上创建异常对象;② 异常处理机制开始接管流程(当前的执行流程被终止)

标准异常类均有两个ctor:① default ctor; ② 带字符串参数的ctor

Throwable是异常类型的根类

catch异常时,try中抛出的是子类异常,但catch的是基类异常也是OK,但若catch子类异常和基类异常的子句同时存在时,应将基类catch子句放在后面避免“屏蔽”现象发生

抛出异常 + 捕获异常

抛出异常(throw):

if( t==null )
  throw new NullPointerException(); // 异常对象用new创建于堆上

捕获异常(try+catch):

try {
  ...
} catch( Type1 id1 ) { 
  // 处理Type1类型的异常代码
} catch( Type2 id2 ) {
  // 处理Type2类型的异常代码
}

虽然上面的id1和id2在处理异常代码中可能用不到,但不能少,必须定义

异常发生时,异常机制搜寻参数与异常类型相匹配的第一个catch子句并进入

创建自定义异常

创建不带参数ctor的自定义异常类:

// 自定义异常类(default ctor)
class SimpleException extends Exception {}
------------------------------------------------------------

// 客户端代码
public class UseException {
  public void fun throws SimpleException {
    System.out.println( "Throw SimpleExcetion from fun" );
    throw new SimpleException();
  }

  public static void main( String[] args ) {
    UseException user = new UseException();
    try {
      user.fun(); 
    } catch( SimpleException e ) {
      System.out.println("Caught it !");
    }
  }
}
------------------------------------------------------------
// 输出
Throw SimpleExcetion from fun
Caught it !

创建带参数ctor的自定义异常类

// 自定义异常类(有参ctor)
class MyException extends  Exception {
  public MyException() { }
  public MyException( String msg ) { super(msg); }
}
------------------------------------------------------------

// 客户端代码
public class UseException {
  
  pubilc static void f() throws MyException {
    System.out.println( "Throwing MyException from f()" )
    throw new MyException();
  }
  public static void g() throws MyException {
    System.out.println( "Throwing MyException from g()" )
    throw new MyException("Originated in g()");
  }

  publib static void main( String[] args ) {
    try {
      f();
    } catch( MyException e ) {
      e.printStackTrace( System.out );
    }

    try {
      g();
    } catch( MyException e ) {
      e.printStackTrace( System.out );
    }
  }

}
------------------------------------------------------------

// 输出
Throwing MyException from f()
MyException
      at ...
      at ...
Throwing MyException from g()
MyException: Originated in g() // 此即创建异常类型时传入的String参数
      at ...
      at ...
捕获所有异常
try {
  ...
} catch( Exception e ) { // 填写异常的基类,该catch子句一般置于末尾
  ...
}

Exception类型所持有的方法:

String getMessage()

String getLocalizedMessage()

String toString()

void printStackTrace()

void printStackTrace( PrintStream )

void printStackTrace( javo.io.PrintWriter )

注意:从下往上每个方法都比前一个提供了更多的异常信息!

栈轨迹

printStackTrace()方法所提供的栈轨迹信息可以通过getStackTrace()方法来Get,举例:

try {
  throw new Exception();
} catch( Exception e ) {
  for( StackTraceElement ste : e.getStackTrace() )
    System.out.println( ste.getMethodName() );
}

这里使用getMethodName()方法来给出异常栈轨迹所经过的方法名!

重抛异常
try {
  ...
} catch( Exception e ) {
  throw e;   // 重新抛出一个异常!
}

若只是简单地将异常重新抛出,则而后用printStackTrace()显示的将是原异常抛出点的调用栈信息,而非重新抛出点的信息,欲更正该信息,可以使用fillInStackTrace()方法:

try {
  ...
} catch( Exception e ) {
  throw (Exception)e.fillInStackTrace(); // 该行就成了异常的新发生地!
}
异常链

异常链:在捕获一个异常后抛出另一个异常,并希望将原始的异常信息保存下来!

解决办法:

在异常的ctor中加入cause参数

使用initCause()方法

注意:Throwable子类中,仅三种基本的异常类提供了待cause参数的ctor(Error、Exception、RuntimeException),其余情况只能靠initCause()方法,举例:

class DynamicFieldsException extends Exception { }

public Object setField( String id, Object value ) throws DynamicFieldsException {

  if( value == null ) {
    DynamicFieldsException dfe = new DynamicFieldsException();
    dfe.initCause( new NullPointerException() ); 
    throw dfe;
  }

  Object result = null;
  try {
    result = getField(id);
  } catch( NoSuchFieldException e ) {
    throw new RuntimeException( e );
  }

}
Java标准异常

看这个图需要明确:程序员一般关心Exception基类型的异常

由图中可知,Error、RuntimeException都叫做“Unchecked Exception”,即不检查异常,程序员也无需写异常处理的代码,这种自动捕获

若诸如RuntimeException这种Unchecked异常没有被捕获而直达main(),则程序在退出前将自动调用异常的printStackTrace()方法

使用finally进行清理
try {
  ...
} catch(...) {
  ...
} finally { // finally子句总是会被执行!!!
  ...
}

使用时机:

当需要把内存之外的资源(如:文件句柄、网络连接、某个外部世界的开关)恢复到初始状态时!

try {
  ...
} catch(...) {
  ...
} finally { // finally子句总是会被执行!!!
  sw.off(); // 最后总是需要关掉某个开关!
}

在return中使用finally

public static void func( int i ) {
  
  try {
    if( i==1 )
      return;
    if( i==2 )
      return;
  } finally {
    print( "Performing cleanup!" ); // 即使上面有很多return,但该句肯定被执行
  }

}

finally存在的缺憾:两种情况下的finally使用会导致异常丢失!

前一个异常还未处理就抛出下一个异常

// 异常类
class VeryImportantException extends Exception {
  poublic String toString() {
    return "A verfy important exception!";
  }
}

class HoHumException extends Exception {
  public String toString() {
    return "A trivial exception!";
  }
}
------------------------------------------------------------------
// 使用异常的客户端
public class LostMessage {
  void f() throws VeryImportantException {
    throw new VeryImportantException();
  }

  void dispose() throws HoHumException {
    throw new HoHumException();
  }

  public static void main( String[] args ) {
    try {
      LostMessage lm = new LostMessage();
      try {
        lm.f();
      } finally {
        lm.dispose(); // 最后只会该异常生效,lm.f()抛出的异常丢了!
      }
    } catch( Exception e ) {
      System.out.println(e);
    }
  }
}
-----------------------------------------------------------------
// 输出
A trivial exception!

finally子句中的return

public static void main( String[] args ) {
  try {
    throw new RuntimeException();
  } finally {
    return; // 这将会掩盖所有的异常抛出
  }
}
继承基类、实现接口时的异常限制
// 异常类
class A extends Exception { }
class A1 extends A { }
class A2 extends A { }
class A1_1 extends A1 { }

class B extends Exception { }
class B1 extends B { }
-------------------------------------------------
// 用了异常类的基类
abstract class Base {
  public Base() throws A { }
  public void event() throws A { }                   // (1)
  public abstract void atBat throws A1, A2;
  public void walk() { }
}
-------------------------------------------------
// 用了异常类的接口
interface Interf {
  public void event() throws B1;
  public void rainHard() throws B1;
}
-------------------------------------------------
// 继承基类并实现接口的客户端类
public class Ext extends Base implements Interf {

  public Ext() throws B1, A { }            // (2)
  public Ext( String s ) throws A1, A {}   // (2)
  public void walk() throws A1_1 { }       // (3) 编译错误!
  public void rainHard() throws B1 {}      // (4)
  public void event() { }                  // (5)
  public void atBat() throws A1_1 { }      // (6)

  public static void main( String[] args ) {
  
    try {
      Ext ext = new Ext();
      ext.atBat();
    } catch( A1_1 e ) {
      ...
    } catch( B1 e ) {
      ...
    } catch( A e ) {
      ...
    }

    try {
      Base base = new Ext();
      ext.atBat();
    } catch( A2 e ) { // 这里的catch必须按照Base中函数的异常抛出来写
      ...
    } catch( A1 e ) {
      ...
    } catch( B1 e ) {
      ...
    } catch( A ) {
      ...
    }
    
  }
}

上面的例子可以总结如下:【注意对应数字标号】

(1) 基类的构造器或者方法声明了抛出异常,但实际上没有,这里相当于为继承类写了一个异常抛出规范,子类实现时安装这个规范来抛异常

(2) 从这两个ctor看出:异常限制对ctor不生效,子类ctor可以抛出任何异常而不管基类ctor所抛出的异常

(3) 基类函数没抛异常,派生类重写时不能瞎抛!

(4) 完全遵守基类的抛出,正常情况

(5) 基类函数抛了异常,派生类重写时不抛也是OK的

(6) 派生类重写基类函数时抛的异常可以是基类函数抛出异常的子类型

构造器中异常如何书写

对于在构造阶段可能会抛出异常并要求清理的类,安全的方式是使用嵌套的try子句:即在创建需要清理的对象之后,立即进入一个try-finally块,举例:

特别需要注意的是下面的例子里在ctor中对文件句柄的close应放置的合理位置!
// 需要清理的对象类
class InputFile {
  private BufferedReader in;
  
  InputFile( String fname ) throws Exception {  // 构造函数!
    try {
      in = new BufferedReader( new FileReader(fname) );
      // 这里放置可能抛出异常的其他代码
    } catch( FileNotFoundException e ) { // 若上面的FileReader异常,将会抛FileNotFoundException,走到这里,该分支无需in.close()的
      System.out.println( "Could not open " + fname );
      throw e;
    } catch( Exception e ) {
      // 走到这里其实说明in对象已经构建成功,这里是必须in.close()的
      try {
        in.close();   // 注意此处关闭动作多带带用try进行保障
      } catch( IOException e2 ) {
        System.out.println("in.close() unsuccessful");
      }
      throw e;
    } finally {
      // 注意in.close() 不要在此处关闭,因为try中假如BufferedReader构造失败,此时in对象未生成成功,是无需close()一说的!
    }
  }

  String getLine() {
    String s;
    try {
      s = in.readLine();
    } catch( IOException e ) {
      System.out.println( "readLine() unsuccessful!" );
      s = "failed";
    }
    return s;
  }

  void cleanup() {  // 提供手动的关闭文件句柄的操作函数
    try {
      in.close();
    } catch( IOException e ) {
      System.out.println( "in.close() failed !" );
    }
  }

}
----------------------------------------------------
// 客户端代码
public class Cleanup {

  public static void main( String[] args ) {
    
    try {
      InputFile in = new InputFile( "Cleanup.java" );
      try { // 上面InputFile构造完成以后立即进入该try-finally子句!
        String s = "";
        int i = 1;
        while( (s = in.getLine()) != null )
          System.out.println(""+ i++ + ": " + s);
      } catch( Exception e ) {
        e.printStackTrace( System.out );
      } finally {  // 该finally一定确保in能正常cleanup()!
        in.cleanup();
      } 
    } catch( Exception e ) {
      System.out.println( "InputFile ctor failed!" );
    }

  } // end main()
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/68469.html

相关文章

  • Java编程思想学习连载:内部类)

    摘要:非内部类通过一个特殊的链接到其外围类的对象,而类型的内部类无此引用。 showImg(https://segmentfault.com/img/remote/1460000012925199); 用thinkpad打字确实很爽啊! Thinking in java系列博文目录: Java编程思想学习录(连载之:一切都是对象) Java编程思想学习录(连载之:初始化与清理) Java...

    Meils 评论0 收藏0
  • Java编程思想学习连载:初始化与清理)

    摘要:注本文首发于公众号,可长按或扫描下面的小心心来订阅关于构造器与初始化无参构造器默认构造器自己未写编译器帮忙自动创建的若自行定义了构造器无论参数有否,编译器便停止默认创建动作类里的对象引用默认初始化为,基本类型初始化为构造器也是类的静态方法四 showImg(https://segmentfault.com/img/remote/1460000015723687); 注: 本文首发于 ...

    betacat 评论0 收藏0
  • 利用K8S技术栈打造个人私有云(连载:私有云客户端打造)

    摘要:前端技术栈还是非常庞大的,为了能够借助已经存在的轮子来造出一辆车,所以我选择了进行实践。状态的管理的状态管理依靠完成,用其来管理的所有组件状态。私有云客户端打造主页面首先是主页面,可以打开任何一个云主机系统的页面看,基本类似。 showImg(https://segmentfault.com/img/remote/1460000013930354); 【利用K8S技术栈打造个人私有...

    Jingbin_ 评论0 收藏0
  • 利用K8S技术栈打造个人私有云(连载:私有云客户端打造)

    摘要:前端技术栈还是非常庞大的,为了能够借助已经存在的轮子来造出一辆车,所以我选择了进行实践。状态的管理的状态管理依靠完成,用其来管理的所有组件状态。私有云客户端打造主页面首先是主页面,可以打开任何一个云主机系统的页面看,基本类似。 showImg(https://segmentfault.com/img/remote/1460000013930354); 【利用K8S技术栈打造个人私有...

    shiina 评论0 收藏0
  • 利用K8S技术栈打造个人私有云(连载:K8S资源控制)

    摘要:将用户命令通过接口传送给,从而进行资源的增删改等操作。要使用编写应用程序,当下大多语言都可以很方便地去实现请求来操作的接口从而控制和查询资源,但本文主要是利用已有的客户端来更加优雅地实现的资源控制。 showImg(https://segmentfault.com/img/remote/1460000013517345); 【利用K8S技术栈打造个人私有云系列文章目录】 利用K8S...

    Reducto 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<