Search code examples
javagenericsinheritancegeneric-programming

java using this in generic method (inheritance)


I'm trying to get access to child class methods and fields, when creating lambda, which is used in parent class. Code explains it more easily:

class Parent {
    List<Processor<? extends Parent>> processors;

    private void doSmth() {
        for (Processor<? extends Parent> processor : processors) {
            processor.doJob(this);  //this lines gives compile error
        }
    }

    public void registerListeners(Processor<? extends Parent> ... subscribers) {
        this.subscriberList = Arrays.asList(subscribers);
    }
}

Where Processor is a FunctionalInterface.

public interface Processor<T extends Parent> extends BiFunction<T, Message,  Boolean> {
    AtomicReference<Boolean> ok = new AtomicReference<>(false);

    default void doJob(T client, Message message) {
        if (apply(client, message))
            ok.set(true);
    }

    default boolean isDone() {
        return ok.get();
    }
}

The example of wanted usage of these classes:

Child childInstance= new Child(); //where Child class extends Parent
childInstance.registerListeners((child, message) -> child.callSomeChildMethod());
childInstance.doSmth(message);

It would be really cool to create lambda without redundant specifying of parameter type like in this line:

childInstance.registerListeners((Processor<Child>) (child, message) -> child.callSomeChildMethod());

(because it always should be type for which I register the listeners)
The problem is that code doesn't compile with error
incompatible types: Parent cannot be converted to capture#1 of ? extends Parent
Which is quite logical (I understand the reason). Is there some way in java I can get this code working?
Thanks in advance!


Solution

  • Your idea of having List<Processor<? extends Parent>> processors; in Parent class is not suggestible. As you see, since you have not mentioned the type of processes the list has; wherever you call processor.doJob(anyObjectHere) an error gets thrown, one way or the other(unless you do explicit cast)

    Try doing something like this;

    1. Declare a Client instead of your Parent which holds the type of processors in List<Processor<? extends Parent>> processors;

      abstract class Client<T extends Client<T>> {
          List<Processor<T>> processors;
      
          public void doSmth(Message message) {
              for (Processor<T> processor : processors) {
                  processor.doJob(getThis(), message);  
              }
          }
      
          abstract T getThis();
      
          public void registerListeners(Processor<T> subscribers) {
              this.processors = Arrays.asList(subscribers);
          }
      }
      
    2. Change your Processor definition to incorporating Client rather that Parent

      interface Processor<T extends Client<T>> extends BiFunction<T, Message,  Boolean> {
          AtomicReference<Boolean> ok = new AtomicReference<>(false);
      
          default void doJob(T client, Message message) {
              if (apply(client, message))
                  ok.set(true);
          }
      
          default boolean isDone() {
              return ok.get();
          }
      }
      
    3. Now you can create your Child like this;

      class Child extends Client<Child> {
          boolean callSomeChildMethod() {
              return true;
          }
      
          @Override
          Child getThis() {
              return this;
          }
      }
      
    4. And call them the same way you did before;

          Child childInstance= new Child(); //where Child class extends Parent
          childInstance.registerListeners((child, message) -> child.callSomeChildMethod());
          childInstance.doSmth(message);
      

    This way you have neither compile errors nor warnings