0

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?

Alex
  • 82
  • 9
  • `X <: Y with Z` means "`X` is a subtype of **both** `Y` and `Z`". – Tobias Brandt Nov 12 '13 at 12:14
  • Any way to implement **OR** logic here? – Alex Nov 12 '13 at 12:32
  • There is the `Either` class in the scala standard library, but you need to explicitly tag whether your object is a `Y` or `Z`. It can't be any other way unless `Y` and `Z` have a common base type, because at some point you need to know which methods you can call. – Tobias Brandt Nov 12 '13 at 12:35

3 Answers3

3

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))
flavian
  • 28,161
  • 11
  • 65
  • 105
  • After trying it out: 1) toScala can be applied only if type parameters of the map are specified `def test (map: JMap[String, Any]) = { ??? }`; 2) This function does not accept instances of java.util.HashMap - expecting java.util.Map; 3) More importantly, it does not accept Scala maps. As stated in the question, I need this function to accept **either** Java or Scala maps - hence the attempt at defining a type bound. – Alex Nov 12 '13 at 11:17
  • not sure what you mean by overloading here. Can you provide an example? – Alex Nov 12 '13 at 11:23
  • Ok, that works for the maps of type [String, String], stops working when I put [String, Any] (I need that!) - when I pass a Java map [String, Int] it says that test expects Map[String, Any]. Also, I would prefer not to use this Java-ish solution, defining a type bound looks a lot cleaner, so I would like to understand what am I doing wrong with it. P.S. How can I put line breaks in comments? Double space isn't working. – Alex Nov 12 '13 at 11:41
  • Ok, that works (and when calling from Java, I need to declare HashMap as `HashMap` to get Java's auto-wrapping of primitives). I'd still prefer Scala-like solution with type parameter, any ideas why it isn't working? – Alex Nov 12 '13 at 12:04
1

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])
John Landahl
  • 1,041
  • 5
  • 8
  • Just a note, if you're going down the conversion route it's better to use `scala.collection.JavaConverters` and call `asJava` and `asScala` directly rather than loading up all those implicits. – waffle paradox Nov 12 '13 at 00:37
  • I considered mentioning `JavaConverters` as well, but went with the implicits from `JavaConversions` because that approach provides the kind of seamless integration he was looking for with in the original question. – John Landahl Nov 12 '13 at 01:55
  • I played around with JavaConversions._, but they do not seem to work in function arguments (at least when calling from Java code) - compiler just says that it expects another type. Can you give example of how to make it work here? – Alex Nov 12 '13 at 04:50
  • 1
    Implicits only work in Scala, which is why I suggested making your function accept `java.util.Map` -- your Java code could then call it without any extra steps, and your Scala clients only need to import the implicit converter from `ScalaConversions`. – John Landahl Nov 13 '13 at 04:04
  • Works like a charm, thanks. I didn't think to `import JavaConversions._` in **client** Scala code. – Alex Nov 14 '13 at 19:17
-4
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
        }
     }
}
Ayush Jain
  • 44
  • 6