Wednesday, October 26, 2011

Passing Java arrays to Scala classes - generics compatibility issue

Imagine that you got a library class written in Scala (2.8.0). The class holds an array of Strings.
class LibraryClass(array : Array[String]) {

  override def toString = "Array size: " + array.length.toString

}
Now you want to instantiate this class from Scala code:
object ScalaClient extends Application {
  var libraryClass = new LibraryClass(Array[String]("foo"))
  println(libraryClass)
}
You can do the same from Java code:
public class JavaClient {

  public static void main(String[] args) {
    LibraryClass libraryClass = new LibraryClass(new String[]{"foo"});
      System.out.println(libraryClass);
  }

}
No problems. Everything compiles and runs smoothly.

Now modify the LibraryClass to be parametrized:
class LibraryClass[T](array : Array[T]) {

  override def toString = "Array size: " + array.length.toString

}
Try to run it from Scala code:
object ScalaClient extends Application {
  var libraryClass = new LibraryClass(Array[String]("foo"))
  println(libraryClass)
}
That works fine. Now try to call the parametrized version of LibraryClass from Java code:
public class JavaClient {

  public static void main(String[] args) {
    LibraryClass<String> libraryClass = new LibraryClass<String>(new String[]{"foo"});
      System.out.println(libraryClass);
}

}

Exception in thread "main" java.lang.NoSuchMethodError: LibraryClass.<init>([Ljava/lang/Object;)V
 at JavaClient.main(JavaClient.java:6)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:601)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Yes, we've just created the code that is runnable under Scala but fails if called from Java. To make our LibraryClass compatible with both Scala and Java clients we need to narrow its parameter type definition to classes extending from the java.lang.Object:
class LibraryClass[T<:Object](array : Array[T]) {

  override def toString = "Array size: " + array.length.toString

}
Tip of a day - if you intend to use your Scala classes from Java code declarate their type parameters as [T<:Object] instead of [T].

No comments:

Post a Comment