javascalatype-conversionscala-java-interopbounded-wildcard

Compilation error with bounded wildcards using Java classes in Scala


In Java, we have defined an ObservableCollection.java like this:

public class ObservableCollection<T> implements Collection<T> {

   public SubscriptionHandle onElementAdded(Consumer<T> onAdded) {
     // ... 
   }
}

And an AgentService.java that returns an ObservableCollection:

public interface AgentService {

    ObservableCollection<? extends Agent> getAgents();

}

Now, I am trying to use this ObservableCollection.java in a Scala project like this:

  def test(service: AgentService): Unit = {
    val onAdded: Consumer[_ <: Agent] = ???
    service.getAgents.onElementAdded(onAdded)
  }

Trying this results in the following compilation error:

type mismatch;
 found   : java.util.function.Consumer[_$1] where type _$1 <: com.xxxx.xx.xx.agent.Agent
 required: java.util.function.Consumer[?0] where type ?0 <: com.xxxx.xx.xx.agent.Agent
    service.getAgents.onElementAdded(onAdded)
                                     ^
one error found

This does not make much sense to me. Is there a way I can get this running?

Edit: Using a Cosumer[Agent] results in the following error:

type mismatch;
 found   : java.util.function.Consumer[com.xxxx.xx.xx.agent.Agent]
 required: java.util.function.Consumer[?0] where type ?0 <: com.kuka.cc.si.agent.Agent
Note: com.xxxx.xx.xx.agent.Agent >: ?0, but Java-defined trait Consumer is invariant in type T.
You may wish to investigate a wildcard type such as `_ >: ?0`. (SLS 3.2.10)
    service.getAgents.onElementAdded(onAdded)
                                     ^
one error found

Solution

  • The thing is not in Scala-Java interop. The following Scala code doesn't compile either

    import java.util.function.Consumer
    import java.util
    
    trait Agent
    trait SubscriptionHandle
    trait AgentService {
      def getAgents: ObservableCollection[_ <: Agent]
    }
    trait ObservableCollection[T] extends util.Collection[T] {
      def onElementAdded(onAdded: Consumer[T]): SubscriptionHandle
    }
    
    def test(service: AgentService): Unit = {
      val onAdded: Consumer[_ <: Agent] = ???
      val agents: ObservableCollection[_ <: Agent] = service.getAgents
      agents.onElementAdded(onAdded)
    //                      ^^^^^^^
    }
    
    //type mismatch;
    // found   : java.util.function.Consumer[_$2] where type _$2 <: App.Agent
    // required: java.util.function.Consumer[_$3]
    

    You misuse existential types (wildcard generics). The following code can't compile

    trait X[T]
    trait Y[T] {
      def foo(x: X[T]) = ???
    }
    val x: X[_] = ???
    val y: Y[_] = ???
    y.foo(x) // doesn't compile
    

    Both x and y have existential types but foo accepts x of type X[T], where T must be the same as T in the type of y, i.e. Y[T], so you can't guarantee that T are the same.

    One way to fix compilation is to add generics to AgentService

    trait Agent
    trait SubscriptionHandle
    trait AgentService[T <: Agent] {
      def getAgents: ObservableCollection[T]
    }
    trait ObservableCollection[T] extends util.Collection[T] {
      def onElementAdded(onAdded: Consumer[T]): SubscriptionHandle
    }
    
    def test[T <: Agent](service: AgentService[T]): Unit = {
      val onAdded: Consumer[T] = ???
      val agents: ObservableCollection[T] = service.getAgents
      agents.onElementAdded(onAdded)
    }
    

    or to its method

    trait Agent
    trait SubscriptionHandle
    trait AgentService {
      def getAgents[T <: Agent]: ObservableCollection[T]
    }
    trait ObservableCollection[T] extends util.Collection[T] {
      def onElementAdded(onAdded: Consumer[T]): SubscriptionHandle
    }
    
    def test[T <: Agent](service: AgentService): Unit = {
      val onAdded: Consumer[T] = ???
      val agents: ObservableCollection[T] = service.getAgents
      agents.onElementAdded(onAdded)
    }