Easy Java delegates with Lombok

Ever wanted to override a method of an object instance in Java?

Let’s say you want to implement a custom Scheduler hook for Project Reactor. You start with Schedulers.addExecutorServiceDecorator, implement a BiFunction accepting Scheduler’s instance and ScheduledExecutorService and you need to return another ScheduledExecutorService instance where schedule* methods wrapped with your code.

A straightforward approach would be to create a new class, implement all methods of ScheduledExecutorService interface, and delegate most of them to the instance of ScheduledExecutorService we received as an argument. This pattern is called “Delegation”.

But if you check the interface, you will see that you have to implement 17 methods, while you want to override only 4 of them. Is it not only error-prone, but a lot of boilerplate!
Well, 17 is fairly okay, imagine you have to delegate TransferQueue (36 methods)? No, I’m too lazy and not getting paid for the number of lines I wrote!

Project Lombok to the rescue!

When I hear “Java” and “boilerplate” in one sentence, I usually think of Project Lombok. What I like about it is that you don’t have to change your language/tooling/IDE/buildsystem to remove a lot of boilerplate from your Java code. Definitelly check it if you never did before!

There are many features in Lombok, but we will focus on @Delegate annotation. It looks somewhat like this:

class ExcludesDelegateExample {
  long counter = 0L;

  private interface Add { // (2)
    boolean add(String x);
    boolean addAll(Collection<? extends String> x);
  }

  @Delegate(excludes=Add.class) // (1)
  private final Collection<String> collection = new ArrayList<String>();

  public boolean add(String item) { // (3)
    counter++;
    return collection.add(item);
  }

  public boolean addAll(Collection<? extends String> col) { (4)
    counter += col.size();
    return collection.addAll(col);
  }
}

No, wait! This is not the end of the article yet!:)

What we have here?
(1): Here we define @Delegate annotation on top of the field. All methods of the Collection interface will be delegated to this field, except the excludes. It will also add Collection interface to the ExcludesDelegateExample class’ signature.
(2): This interface defines which methods Lombok should not delegate. You will have to implement them, otherwise you get a compilation error.
(3): Our custom add method increments a counter before calling the original method.

Not bad, huh? I could end the article here, but there is one trick I use to avoid writing interfaces for the excludes property.

Override it!

While excludes work for simple examples, I feel uncomfortable having to create another interface, look up for the methods, copy them into it, etc…
Maybe one day Lombok will automatically detect implemented methods. Until that happens, there is (IMO) a much cleaner approach (unless you feel bad about creating more classes).

First, simply create a dummy delegate, without any exclusions:

@RequiredArgsConstructor
class DelegatingScheduledExecutorService implements ScheduledExecutorService {

  @Delegate
  private final ScheduledExecutorService delegate;

}

Now we can use this class to override any method we want, with Java’s standard method overriding:

class WrappingScheduledExecutorService extends DelegatingScheduledExecutorService {

  public WrappingScheduledExecutorService(ScheduledExecutorService delegate) {
    super(delegate);
  }

  @Override
  public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
    command = wrap(command);
    return super.schedule(command, delay, unit);
  }
}

Another good thing about this approach is that you can reuse DelegatingScheduledExecutorService in case you have multiple versions of such delegate.

comments powered by Disqus