Why does this method call fail? (Generics & wildcards)

118 views Asked by At

I am getting the below error:

'call(ContainsMonitor)' cannot invoke 'call(? extends webscout.Monitor)' in 'WebScoutCallable'

Monitor.java

WebScoutCallable<? extends Monitor> handler;

public setCallable(WebScoutCallable<? extends Monitor> callable) {
     this.handler = callable;
}

WebScoutCallable.java

public interface WebScoutCallable<T extends Monitor> {
     public void call(T caller);
}

ContainsMonitor.java

public class ContainsMonitor extends Monitor {
     public void handleDocument() {
          handler.call(this);
     }
}

I'll freely admit that I'm new to generics and still quite new to Java itself. I find the error message confusing as it looks like it should work (method declaration expects a Monitor or subclass, I'm passing in a subclass). Any help (+explanation) would be greatly appreciated!

Thanks!

2

There are 2 answers

1
rgettman On BEST ANSWER

You have a wildcard in the type parameter of your handler variable. The compiler doesn't know what the exact type of this type parameter is, only that it's either Monitor or a subclass.

The call method takes a T, which is matched on the wildcard. But there is no guarantee that the wildcard type is a ContainsMonitor. It could be a Monitor, or it could be MonitorSubtypeThatDoesntExistYet. Because the compiler doesn't know the actual type, it cannot allow you to pass anything except null, because with any non-null argument, it can't guarantee type safety.

You can get around this by removing the wildcard, and replacing that concept with a type parameter on the Monitor class.

class Monitor<T extends Monitor<T>>
{
    WebScoutCallable<T> handler;

    public void setCallable(WebScoutCallable<T> callable) {
         this.handler = callable;
    }
}

The interface WebScoutCallable changes a little in response:

interface WebScoutCallable<T extends Monitor<T>> {
    public void call(T caller);
}

The subclass feeds its own name as the type argument when extending Monitor.

class ContainsMonitor extends Monitor<ContainsMonitor> {
     public void handleDocument() {
          handler.call(this);
     }
}

Now, T will be a known type, and ContainsMonitor defines it to be itself, so it's now legal for it to pass itself to call.

1
assylias On

? extends Monitor means: a specific subclass of Monitor, but we don't know which one. So it may be a ContainsMonitor or not and handler may or may not be able to accept a ContainsMonitor. The compiler can't decide and shows an error.

One way to solve your problem is to use specific types, for example:

class Monitor<T extends Monitor<T>> {
  WebScoutCallable<T> handler;

  public setCallable(WebScoutCallable<T> callable) {
    this.handler = callable;
  }
}

class ContainsMonitor extends Monitor<ContainsMonitor> {
  public void handleDocument() {
    handler.call(this);
  }
}