This is an excerpt from the Scala Cookbook (partially modified for the internet). This is Recipe 17.7, “How to write Scala traits so they can be used from Java code.”
Problem
You’ve written a Scala trait with implemented methods and need to be able to use those methods from a Java application.
Solution
You can’t use the implemented methods of a Scala trait from Java, so wrap the trait in a class.
Assuming you have a Scala trait named MathTrait
with a method named sum
that you want to access from Java code, create a Scala class named MathTraitWrapper
that wraps MathTrait
:
// scala package foo // the original trait trait MathTrait { def sum(x: Int, y: Int) = x + y } // the wrapper class class MathTraitWrapper extends MathTrait
In your Java code, extend the MathTraitWrapper
class, and access the sum
method:
// java package foo; public class JavaMath extends MathTraitWrapper { public static void main(String[] args) { new JavaMath(); } public JavaMath() { System.out.println(sum(2,2)); } }
This code works as expected, printing the number 4
when it is run.
Discussion
A Java class can’t extend a Scala trait that has implemented methods. To demonstrate the problem, first create a trait with a simple implemented method named sum
:
package foo trait MathTrait { def sum(x: Int, y: Int) = x + y }
Next, to attempt to use this trait from Java, you have a choice of trying to extend it or implement it. Let’s first try to extend it:
package foo; public class JavaMath extends MathTrait {}
By the time you finish typing that code in your IDE, you see the following compiler error message:
The type MathTrait cannot be the superclass of JavaMath; a superclass must be a class
Next, you can attempt to implement the trait, but intuitively you know that won’t work, because in Java you implement interfaces, and this trait has implemented behavior, so it’s not a regular Java interface:
package foo; public class JavaMath implements MathTrait {}
This code leads to the following compiler error:
The type JavaMath must implement the inherited abstract method MathTrait.sum(int, int)
You could implement a sum
method in your JavaMath
class, but that defeats the purpose of trying to use the sum
method that’s already written in the Scala MathTrait
.
Other attempts
You can try other things, such as attempting to create an instance of the MathTrait
and trying to use the sum
method, but this won’t work either:
// java package foo; public static void main(String[] args) { MathTrait trait = new MathTrait(); // error, won't compile int sum = trait.sum(1,2); System.out.println("SUM = " + sum); }
Trying to instantiate a MathTrait
instance results in this compiler error:
foo.MathTrait is abstract; cannot be instantiated [error] MathTrait trait = new MathTrait(); [error] ^
You may already know what the problem is, but to be clear, let’s see what class files are generated on the Scala side. In an SBT project, the class files are located in the following directory:
$PROJECT/target/scala-2.10.0/classes/foo
If you move into that directory and list the files, you’ll see that two files related to the Scala MathTrait
trait have been created:
MathTrait.class MathTrait$class.class
You can see the problem by disassembling these files with the javap
command. First, the MathTrait.class file:
$ javap MathTrait Compiled from "MathTrait.scala" public interface foo.MathTrait{ public abstract int sum(int, int); }
Next, the MathTrait$class.class file:
$ javap MathTrait\$class Compiled from "MathTrait.scala" public abstract class foo.MathTrait$class extends java.lang.Object{ public static int sum(foo.MathTrait, int, int); public static void $init$(foo.MathTrait); }
The problem with trying to work with the Scala MathTrait
from a Java perspective is that MathTrait
looks like an interface
, and MathTrait$class
looks like an abstract class. Neither one will let you use the logic in the sum
method.
Because MathTrait
looks like just an interface, you realize you might be able to create a Java class that implements that interface, and then override the sum
method:
// java package foo; public class JavaMath implements MathTrait { public int sum(int x, int y) { return x + y; } public static void main(String[] args) { JavaMath math = new JavaMath(); System.out.println(math.sum(1,1)); } }
This does indeed work, but for the purposes of this recipe, it doesn’t really matter. The purpose of trying to use the trait was to use the behavior of the trait’s sum
method, and there’s no way to do this from Java without creating a Scala wrapper class.
In a last desperate attempt, you might try to call super.sum(x,y)
from your Java method, like this:
// java public int sum(int x, int y) { return super.sum(x, y); }
But that won’t work either, failing with the following error message:
cannot find symbol [error] symbol : method sum(int,int) [error] location: class java.lang.Object [error] return super.sum(x,y); [error] ^
The only way to solve the problem is to wrap the trait with a class on the Scala side, which was demonstrated in the Solution.
To summarize: If you’re writing a Scala API that will be used by Java clients, don’t expose traits that have implemented behavior in your public API. If you have traits like that, wrap them in a class for your Java consumers.
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |