How to generalize a method call in Java (to avoid code duplication)

Posted by dln385 on Stack Overflow See other posts from Stack Overflow or by dln385
Published on 2012-07-06T03:12:37Z Indexed on 2012/07/06 3:15 UTC
Read the original article Hit count: 173

I have a process that needs to call a method and return its value. However, there are several different methods that this process may need to call, depending on the situation. If I could pass the method and its arguments to the process (like in Python), then this would be no problem. However, I don't know of any way to do this in Java.

Here's a concrete example. (This example uses Apache ZooKeeper, but you don't need to know anything about ZooKeeper to understand the example.)

The ZooKeeper object has several methods that will fail if the network goes down. In this case, I always want to retry the method. To make this easy, I made a "BetterZooKeeper" class that inherits the ZooKeeper class, and all of its methods automatically retry on failure.

This is what the code looked like:

public class BetterZooKeeper extends ZooKeeper {

  private void waitForReconnect() {
    // logic
  }

  @Override
  public Stat exists(String path, Watcher watcher) {
    while (true) {
      try {
        return super.exists(path, watcher);
      } catch (KeeperException e) {
        // We will retry.
      }
      waitForReconnect();
    }
  }

  @Override
  public byte[] getData(String path, boolean watch, Stat stat) {
    while (true) {
      try {
        return super.getData(path, watch, stat);
      } catch (KeeperException e) {
        // We will retry.
      }
      waitForReconnect();
    }
  }

  @Override
  public void delete(String path, int version) {
    while (true) {
      try {
        super.delete(path, version);
        return;
      } catch (KeeperException e) {
        // We will retry.
      }
      waitForReconnect();
    }
  }
}

(In the actual program there is much more logic and many more methods that I took out of the example for simplicity.)

We can see that I'm using the same retry logic, but the arguments, method call, and return type are all different for each of the methods.

Here's what I did to eliminate the duplication of code:

public class BetterZooKeeper extends ZooKeeper {

  private void waitForReconnect() {
    // logic
  }

  @Override
  public Stat exists(final String path, final Watcher watcher) {
    return new RetryableZooKeeperAction<Stat>() {
      @Override
      public Stat action() {
        return BetterZooKeeper.super.exists(path, watcher);
      }
    }.run();
  }

  @Override
  public byte[] getData(final String path, final boolean watch, final Stat stat) {
    return new RetryableZooKeeperAction<byte[]>() {
      @Override
      public byte[] action() {
        return BetterZooKeeper.super.getData(path, watch, stat);
      }
    }.run();
  }

  @Override
  public void delete(final String path, final int version) {
    new RetryableZooKeeperAction<Object>() {
      @Override
      public Object action() {
        BetterZooKeeper.super.delete(path, version);
        return null;
      }
    }.run();
    return;
  }

  private abstract class RetryableZooKeeperAction<T> {

    public abstract T action();

    public final T run() {
      while (true) {
        try {
          return action();
        } catch (KeeperException e) {
          // We will retry.
        }
        waitForReconnect();
      }
    }
  }
}

The RetryableZooKeeperAction is parameterized with the return type of the function. The run() method holds the retry logic, and the action() method is a placeholder for whichever ZooKeeper method needs to be run. Each of the public methods of BetterZooKeeper instantiates an anonymous inner class that is a subclass of the RetryableZooKeeperAction inner class, and it overrides the action() method. The local variables are (strangely enough) implicitly passed to the action() method, which is possible because they are final.

In the end, this approach does work and it does eliminate the duplication of the retry logic. However, it has two major drawbacks: (1) it creates a new object every time a method is called, and (2) it's ugly and hardly readable. Also I had to workaround the 'delete' method which has a void return value.

So, here is my question: is there a better way to do this in Java? This can't be a totally uncommon task, and other languages (like Python) make it easier by allowing methods to be passed. I suspect there might be a way to do this through reflection, but I haven't been able to wrap my head around it.

© Stack Overflow or respective owner

Related posts about java

Related posts about methods