在施工期间抛出异常后清理

时间:2018-05-12 09:45:27

标签: java

我正在构建我的构造函数中的一些对象,这些对象需要在完成后调用.close()。如果在该序列中有一个对象抛出异常,那么如何清理到目前为止已经分配的对象。这提出了一个问题,即调用函数(即使它使用try-finally)永远不会得到一个引用来调用.close作为对象永远不会完成构造。

我有过的想法:

  • 捕获基本的Exception类,并在非空的所有内容上调用close(但是你不应该捕获Exception类)
  • 使用终结器(被视为终结者的不良做法是not a 'second chance' to clean up)。
  • 在“load”或“start”方法中设置这些对象(类的使用者需要知道调用此方法,并且还意味着对象在构造和调用此方法之间的时间上处于不完整状态)

示例代码:

class MyClass implements AutoCloseable {
  private EarthConnection earth;
  private SolarConnection solar;
     public MyClass() {
        earth = new EarthConnection();
        solar = new SolarConnection(); // exception thrown by this connection
     }

     public close() {
        if (earth != null) { 
            earth.close();
        }
        if (solar != null) {
            solar.close();
        }
    }
}

   // Caller
   try (MyClass myClass = new MyClass()) {
       // do work - note if MyClass wasn't fully constructored it can't call the close method on it.
   }

在上面的示例中,如果SolarConnection抛出异常,如何清理分配的EarthConnection?

5 个答案:

答案 0 :(得分:1)

try-with-resources怎么样(假设您使用的是Java 7或更高版本)?如果您在try部分中创建对象,那么它将自动关闭而不会捕获异常。这种方法的唯一缺点是你仍然可以在try部分之外创建对象,然后它将不会被关闭。

答案 1 :(得分:0)

依赖于从构造函数中捕获异常是不好的做法。我建议在那里使用工厂方法一个catch异常:try ... catch with recources

答案 2 :(得分:0)

保持简单,一个类应该对自己的状态负责,而不是MyClass做其他类的资源。这还将确保将来MyClass更少的代码更改,以及其他新类。

IMO,你可以尝试一下:

class EarthConnection implements AutoCloseable {
    @Override
    public void close(){
        /* TO DO */
    }
}

class SolarConnection implements AutoCloseable {
    @Override
    public void close(){
        /* TO DO */
    }
}

class MyClass {
    private EarthConnection earth;
    private SolarConnection solar;
    public MyClass(EarthConnection earth, SolarConnection solar) {
        this.earth = earth;
        this.solar = solar;
    }
    /* TO DO */
}

try(EarthConnection earth = new EarthConnection()){
    try(SolarConnection solar = new SolarConnection()){ /* exception thrown by this connection*/
        MyClass myClass = new MyClass(earth,solar);
        /* TO DO */
    }
}

答案 3 :(得分:0)

我个人会这样做:而不是构造函数,一个静态方法来包装丑陋的代码,让你的构造函数变得简单;

在你的情况下,你不必捕获所有异常,只有那些在第一次初始化之后出现的异常:solar是在earth之后创建的(在宇宙中可能不是这种情况:) ),所以只有它失败了,你才需要彻底清除earth

您不能在此处使用 try-with-resources ,因为您将关闭不是您想要的资源。

class MyClass implements AutoCloseable {
  private EarthConnection earth;
  private SolarConnection solar;
     private MyClass(EarthConnection earth, SolarConnection solar) {
        earth = new EarthConnection();
        solar = new SolarConnection(); // exception thrown by this connection
     }

     public static MyClass newMyClass() {
       EarthConnection earth = new EarthConnection();
       try {
         SolarConnection solar = new SolarConnection(); 
         return new MyClass(earth, solar);
       } catch (SolarException e) {
         earth.close(); // may throw, you can ignore it.
         throw e;
       }       
     }
...
}

如果您有earth以及solar,例如marsvenus,则可能需要使用包含List的类AutoCloseable class MyClass implements AutoCloseable { private final EarthConnection earth; private final VenusConnection venus; private final MarsConnection mars; private final SolarConnection solar; private final AutoCloseable cl; private MyClass( final EarthConnection earth, final VenusConnection venus, final MarsConnection mars, final SolarConnection solar, final AutoCloseable ac ) { this.earth = earth; this.venus = venus; this.mars = mars; this.solar = solar; this.cl = cl; } public static MyClass newMyClass() { AutoCloseables cl = new AutoCloseables<>(); try { EarthConnection earth = cl.register(new EarthConnection()); VenusConnection venus = cl.register(new VenusConnection ()); MarsConnection mars = cl.register(new MarsConnection()); SolarConnection solar = cl.register(new SolarConnection()); return new MyClass(earth, venus, mars, solar, cl); } catch (EarthException | VenusException | MarsException | SolarException e) { cl.close(); throw e; // or new MyClassException(e); } } @Override public void close() { cl.close(); } ,但是你需要以相反的顺序关闭对象(非常类似于以相反的构造顺序调用C ++析构函数)。

class AutoCloseables {
  private final List<AutoCloseable> list;
  public <E extends AutoCloseable> E register(E ac) {list.add(ac); return ac;}
  @Override
  public void close() {
    Collections.reverse(list); // destroy in reverse order
    for (AutoCloseable ac : list) {
      try {ac.close();}
      catch (Exception e) {
        // IGNORED or you may use supressedException https://docs.oracle.com/javase/7/docs/api/java/lang/Throwable.html#addSuppressed(java.lang.Throwable)
      } 
    }
  }
}

使用:

spark-shell --packages maven-coordinates of the package

答案 4 :(得分:0)

抓住Exception并重新抛出应该这样做。这允许调用者查看原始异常,但允许类中所需的清理来关闭资源。

public MyClass() {
    try {
        earth = new EarthConnection();
        solar = new SolarConnection();
    } catch(Exception e) {
        close();
        throw e;
    }
}