有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

在Java中编写未经处理的清理代码时出现异常

假设我的类有一个获取资源的start()方法和一个释放资源的stop()方法。类的start方法可以调用成员对象的start()方法。如果某个成员对象的start()引发异常,我必须确保对start()成功的所有成员对象调用stop()

class X {
    public X ()
    {
        a = new A();
        b = new B();
        c = new C();
        d = new D();
    }

    public void start () throws Exception
    {
        try {
            a.start();
        } catch (Exception e) {
            throw e;
        }

        try {
            b.start();
        } catch (Exception e) {
            a.stop();
            throw e;
        }

        try {
            c.start();
        } catch (Exception e) {
            b.stop();
            a.stop();
            throw e;
        }

        try {
            d.start();
        } catch (Exception e) {
            c.stop();
            b.stop();
            a.stop();
            throw e;
        }
    }

    public void stop ()
    {
        d.stop();
        c.stop();
        b.stop();
        a.stop();
    }

    private A a;
    private B b;
    private C c;
    private D d;
}

注意清理代码的二次增长。进行清理的最佳方式(最少的代码量)是什么?在C语言中,我可以很容易地通过函数底部的清理代码和“goto”跳转到适当的位置来实现这一点,但是Java没有goto。请注意,不允许对尚未启动()的对象调用stop()——我正在寻找与上述代码完全相同但更短的代码

到目前为止,我找到的唯一解决方案是使用布尔函数来记住启动的内容,如下所示:

public void start () throws Exception
{
    boolean aStarted = false;
    boolean bStarted = false;
    boolean cStarted = false;
    boolean dStarted = false;

    try {
        a.start();
        aStarted = true;
        b.start();
        bStarted = true;
        c.start();
        cStarted = true;
        d.start();
        dStarted = true;
    } catch (Exception e) {
        if (dStarted) d.stop();
        if (cStarted) c.stop();
        if (bStarted) b.stop();
        if (aStarted) a.stop();
        throw e;
    }
}

我知道“finally”和“try with resources”,但这两个词在这里似乎都不适用,因为如果没有例外,就不应该发布资源

顺便说一句,这不是关于我使用异常的问题,也不是关于我的程序设计的问题。这是专门针对初始化代码中出现故障时的清理


共 (4) 个答案

  1. # 1 楼答案

    如果您对线性展开的代码没有问题,那么可以使用如下结构的start方法:

    public void start () throws Exception
    {
        a.start();
        try {
            b.start();
            try {
                c.start();
                try {
                    d.start();
                } catch (Exception e) {
                    c.stop();
                    throw e;
                }
            } catch (Exception e) {
                b.stop();
                throw e;
            }
        } catch (Exception e) {
            a.stop();
            throw e;
        }
    }
    

    如果要启动/停止的项目确实不止几个,请像其他人建议的那样使用List

  2. # 2 楼答案

    将你开始的东西添加到堆栈中,然后当你需要停止东西时,将所有东西从堆栈中弹出并停止

    private Deque<Stoppable> toStop = new ArrayDeque<Stoppable>();
    
    public void start() throws Exception {
      try {
        start(a);
        start(b);
        start(c);
        start(d);
      } catch (Exception e) {
        stop();
        throw e;
      }
    }
    
    private void start(Stoppable s) throws Exception {
      s.start();
      toStop.push(s);
    }
    
    public void stop() {
      while (toStop.size > 0) {
        toStop().pop().stop();
      }
    }
    

    这要求您开始通过接口或子类拥有某种公共stop(),但我认为它们很可能已经拥有了

  3. # 3 楼答案

    public class X
    {
        private final List <Stoppable> stoppables = 
            new ArrayList <Stoppable> ();
    
        private void start (StartStoppable x)
        {
            x.start ();
            stoppables.add (x);
        }
    
        public void startAll ()
        {
            try
            {
                start (a);
                start (b);
                start (c);
                start (d);
            }
            catch (Throwable ex)
            {
                stopAll ();
                ex.printStackTrace ();
            }
        }
    
        public void stopAll ()
        {
            for (Stoppable s: stoppables)
            {
                try
                {
                    s.stop ();
                }
                catch (Throwable ex)
                {
                    ex.printStackTrace ();
                }
            }
        }
    }
    
  4. # 4 楼答案

    虽然我很欣赏给出的所有想法,但我发现它们中没有一个适合广泛使用我的代码。特别是,基于堆栈/列表的方法存在问题,原因有两个:

    1. start()包装器不允许将参数传递给它调用的对象的start方法
    2. 一切都必须实现像Stoppable这样的接口。这是有问题的,因为该技术需要为外部提供的类和函数工作,可能没有start()方法,但有些不同

    即使对象没有启动,也可以调用stop()的想法也不适用于相同的原因——接口可能超出了程序员的控制范围

    最后,我解决了这个问题,我发现它需要最少的样板文件。另一个好处是,即使对象没有启动,也可以调用生成的stop()方法(但这并不会使方法毫无意义,因为成员的start和stop函数可能不受程序员的控制)

    class X {
        public X ()
        {
            a = new A();
            b = new B();
            c = new C();
            d = new D();
        }
    
        public void start () throws Exception
        {
            assert(state == 0);
            try {
                a.start();
                state = 1;
                b.start();
                state = 2;
                c.start();
                state = 3;
                d.start();
                state = 4;
            } catch (Exception e) {
                stop();
                throw e;
            }
        }
    
        public void stop ()
        {
            if (state >= 4) d.stop();
            if (state >= 3) c.stop();
            if (state >= 2) b.stop();
            if (state >= 1) a.stop();
            state = 0;
        }
    
        private int state;
        private A a;
        private B b;
        private C c;
        private D d;
    }