3

I noticed that overriding of the cancel method does not have effect if thenApply is called on the future. The derived future is not aware of the fact that the original had such an override. The code below demonstrates the issue.

Is there any way to correctly override the method without losing possibility to transform the future?

import java.util.concurrent.CompletableFuture;

public class Main {
    public static void main(String[] args){
        CompletableFuture<Object> unmodified = new CompletableFuture<>(){
            @Override
            public boolean cancel(boolean mayInterruptIfRunning) {
                System.out.println("----> cancelling unmodified");
                return super.cancel(mayInterruptIfRunning);
            }
        };
        unmodified
                .cancel(true);


        CompletableFuture<Object> modified = new CompletableFuture<>(){
            @Override
            public boolean cancel(boolean mayInterruptIfRunning) {
                System.out.println("----> cancelling modified");
                return super.cancel(mayInterruptIfRunning);
            }
        };
        unmodified
                .thenApply(x -> x)
                .cancel(true);
        System.out.println("----> end");
    }
    // OUTPUT
    // > Task :Main.main()
    // ----> cancelling unmodified
    // ----> end
}

nkdm
  • 1,220
  • 1
  • 11
  • 26

2 Answers2

3

You need Java 9 or newer. Then, all methods creating a new dependent future will call the factory method newIncompleteFuture(), which you can override:

public static void main(String arg[]) {
    class MyFuture<T> extends CompletableFuture<T> {
        @Override
        public boolean cancel(boolean mayInterruptIfRunning) {
            System.out.println("customized cancel");
            return super.cancel(mayInterruptIfRunning);
        }

        @Override
        public <U> CompletableFuture<U> newIncompleteFuture() {
            return new MyFuture<>();
        }
    }
    MyFuture<Object> future = new MyFuture<>();

    System.out.print("direct: ");
    future.cancel(true);

    System.out.print("indirect: ");
    future.thenApply(x -> x).cancel(true);

    System.out.print("even longer chain: ");
    future.thenApply(x -> x).exceptionally(t -> null).cancel(true);

    System.out.println("end");
}
direct: customized cancel
indirect: customized cancel
even longer chain: customized cancel
end

But keep in mind that for CompletableFuture, a cancellation is not different to an exceptional completion with a CancellationException. So, someone could call completeExceptionally(new CancellationException()) instead of cancel(...), to the same effect.

Holger
  • 285,553
  • 42
  • 434
  • 765
1

There is no direct way of doing what you want, as thenApply returns a new CompletableFuture object.
However, if you really need to do this, you could try to Override the CompletableFuture class itself.

Let me explain!
CompletableFuture.thenApply() method further calls the uniApplyStage private method of the CompletableFuture class where the new CompletableFuture() instance is created to be returned. (I decompiled the Java source code to check this.)

You cannot override this method as it is a private method. However, if you;

  1. Decompile the class CompletableFuture
  2. Copy the code.
  3. Make changes to the uniApplyStage method to @Override the cancel method at time of new CompletableFuture<V>() instance creation.
  4. Then at runtime, follow steps mentioned in https://stackoverflow.com/a/6680889/11226302 to reload your class at Runtime using a "Custom ClassLoader".

Theoratically, you should be able to do all this.

But of course, the question remains. How desperately do you want to do this done? :D

In my honest opinion, consider this answer as a sort of last resort.

Shivam Puri
  • 1,578
  • 12
  • 25