In this article we present several program samples combining Scala code with Java code.
A companion module (or companion object) of a Scala class is an object which has the same name as the class and is defined in the same scope and compilation unit. Conversely, the class is called the companion class of the module (see SLS 5.4).
// File companion.scala class Companion { def hello() { println("Hello (class)") } // [1] } object Companion { def hallo() { println("Hallo (object)") } // [2] def hello() { println("Hello (object)") } // [3] }
Calling a Scala method from Java is straightforward (cases [1] and [2]) unless it is defined both in a class and in its companion object (case [3]):
// File TestCompanion.java public class TestCompanion { public static void main(String[] args) { new Companion().hello(); // [1] Companion.hallo(); // [2] (static) Companion$.MODULE$.hello(); // [3] (hidden static) } }
Writing Java code for case [3] thus requires some knowledge of the compiler internals.
Note: Do not forget to add the standard Scala library to your classpath when invoking the
javac/java
command line tools.~$ scalac companion.scala ~$ javac -cp $SCALA_HOME/lib/scala-library.jar:. TestCompanion.java ~$ java -cp $SCALA_HOME/lib/scala-library.jar:. TestCompanion Hello (class) Hallo (object) Hello (object)
// File accessor.scala package example object Accessor { var size = 0 def main(args: Array[String]) { size = 1 println("size="+size) size_=(2) println("size="+size) } }
// File TestAccessor.java import static example.Accessor.*; public class TestAcessor { public static void main(String[] args) { size_$eq(1); System.out.println("size="+size()); size_$eq(2); System.out.println("size="+size()); } }
// File anonfun.scala package example object Anonfun { var firstname = "John" def hello = (s: String) => s + firstname def main(args: Array[String]) { println(firstname) println(hello("Hello ")) firstname = "Hans" // [2] println(hello("Hallo ")) val ciao = hello println(ciao("Ciao ")) } }
// File TestAnonfun.java import static example.Anonfun.*; public class TestAnonfun { public static void main(String[] args) { System.out.println(firstname()); System.out.println(hello().apply("Hello ")); // [1] firstname_$eq("Hans"); System.out.println(hello().apply("Hallo ")); scala.Function1<String, String> ciao = hello(); >// [2] System.out.println(ciao.apply("Ciao ")); } }
Note: Do not forget to add the standard Scala library to your classpath when invoking the
javac/java
command line tools.~$ scalac anonfun.scala ~$ javac -cp $SCALA_HOME/lib/scala-library.jar:. TestAnonfun.java ~$ java -cp $SCALA_HOME/lib/scala-library.jar:. TestAnonfun John Hello John Hallo Hans Ciao Hans
A Scala trait with no method implementation is just an interface at the bytecode level and can thus be used in Java code like any other interface.
However, if a trait has method implementations (eg. method
println(s: String)
in the example below), then Java classes
implementing that trait must provide concrete methods for abstract
methods with no code and forwarder methods for abstract methods
containing code.
// File trait.scala trait Printable { def print(s: String) def println() def println(s: String) { print(s); println() } }
Whenever a trait T
provides method implementations the Scala
compiler generates an abstract class T$class
with static
methods containing the implementation code.
// File TestTrait.java
class Hello implements Printable {
public void print(String s) { System.out.println(s); }
public void println() { System.out.println(); }
public void println(String s) { Printable$class.println(this, s); }
}
public class TestTrait {
public static void main(String[] args) {
new Hello().println("Bob");
}
}
Default arguments in Scala can be specified both for constructor and method parameters; their type must be assignment compatible with the given parameter type.
However you should better avoid them for methods and constructors you know they will be called from Java code (see pitfall example in the article Translating Java code to Scala).
// File defaultargs.scala class Person( name: String, age: Int = -1, city: String = "unknown") { override def toString = name+" (age="+age+",city="+city+")" def setEmail(address: String = name+"@gmail.com") { /*..*/ } } object DefaultArgs extends App { val bob = new Person("Bob") bob.setEmail() println(bob) println(new Person("Bob", city="Lausanne")) }
Specifying default arguments in Java requires to know both their declaration scope (constructor or method) and their position in the parameter list:
// File TestDefaultArgs.java public class TestDefaultArgs { public static void main(String[] args) { Person bob = new Person("Bob", // [1] Person.init$default$2(), Person.init$default$3())); bob.setEmail(bob.setEmail$default$1()); // [2] System.out.println( new Person("Bob", // [3] Person.init$default$2(), "Lausanne")); } }
Note: Since the Scala compiler doesn't generate Java annotations for preserving parameter names, you must add yourself a Java annotation in front of each constructor (resp. method) parameter (eg.
@ScalaParameterName("name")
in the example below) in order to be able to access their name from Java:// File ScalaParameterName.java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface ScalaParameterName { public String value(); }
class Person( @ScalaParameterName("name") name: String, @ScalaParameterName("age") age: Int = -1, @ScalaParameterName("city") city: String = "unknown") { override def toString = name+"(age="+age+",city="+city+")" }