I have a mixed Scala/Java project. I'm trying to write a Scala function that would accept either Scala maps (from Scala code) or Java maps (from Java code) - java.util.HashMap for a start.
Here's what I came up with (Scala code):

def test[M <: scala.collection.Map[String, Any] with java.util.HashMap[String, Any]] (m: M): Int = { ??? }

and trying to call it from Java code like this:

HashMap m = new HashMap<String, Character>();
m.put("key", 'V');
ScalaCode.test(m);

which gives me a compile-time error:

[javac] /home/username/test/JavaCode.java:79: error: method test in class ScalaCode cannot be applied to given types;
[javac]                         ScalaCode.test(m);
[javac]                                  ^
[javac]   required: M
[javac]   found: HashMap
[javac]   reason: inferred type does not conform to declared bound(s)
[javac]     inferred: HashMap
[javac]     bound(s): HashMap<String,Object>,Map<String,Object>
[javac]   where M is a type-variable:
[javac]     M extends HashMap<String,Object>,Map<String,Object> declared in method <M>test(M)

Edit: I changed the assignment to

HashMap<String, Character> m = new HashMap<String, Character>();

trying to appease Java type inference, no luck - now it says:

[javac]   reason: inferred type does not conform to declared bound(s)
[javac]     inferred: HashMap<String,Character>
[javac]     bound(s): HashMap<String,Object>,Map<String,Object>  

Edit: Subtyping from java.util.Map does not help:

def test[M <: scala.collection.Map[String, Any] with java.util.Map[String, Any]] (m: M): Int = { ??? }  

produces

[javac]   reason: inferred type does not conform to declared bound(s)
[javac]     inferred: HashMap<String,Character>
[javac]     bound(s): scala.collection.Map<String,Object>,java.util.Map<String,Object>

What is wrong with a type bound for M?

有帮助吗?

解决方案 2

It might be a better idea to have the function accept the lowest common denominator, in this case java.util.Map, so that your Java code doesn't have to jump through hoops to use it. Scala maps can be converted automatically with a simple import of scala.collection.JavaConversions._.

With the following Scala:

def test(map: java.util.Map[String, Character]) = ???

Your Java code can call the method normally:

test(new HashMap<String, Character>());

And any other Scala code can use JavaConversions for automatic conversion:

import scala.collection.JavaConversions._
test(Map.empty[String, Character])

其他提示

You can use JavaConverters to achieve this:

import scala.collection.JavaConverters._
import java.util.{ HashMap => JMap }
import scala.collection.mutable.{ Map => MMap }

Now for a better approach:

implicit class JavaMutableConverters[K, V](map: MMap[K, V]) extends AnyRef {
  final def asMutableJavaMap: JMap[K, V] = {
    val hash = new JMap[K, V]()
    for ( (k, v) <- map) { hash.put(k, v) }
    hash
  }
}

This will work with any K, V combo.

object TestMaps {

  def test(map: JMap[String, Any]): JMap[String, Any] = {
    map.put("string", "test")
    map
  }

  def test(map: MMap[String, Any]): JMap[String, Any] = {
    test(map.asMutableJavaMap)
  }
}

Never do casts or use instanceof, in the interop case the rule of thumb is to stick to the lowest common denominator.

 val x = new JMap[String, Any]();
 x.put("test1", "test2")

 val y = MMap[String, Any]("test1" -> "test2")

 println(TestMaps.test(x))
 println(TestMaps.test(y))
object ScalaCode {
    def test(map: AnyRef) {
        if (map.isInstanceOf[java.util.HashMap]) {
            //your code here
        } else if (map.isInstanceOf[scala.collection.Map]) {
            //your code here
        }
     }
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top