Reputation: 1471
I want to call some Java code from Scala code. I would like to use Scala's apply construct, so I can call it like this:
val led = OutPin(0)
instead of:
val led = new OutPin(0)
I naively implemented an additional apply method in my Java code like this:
public class OutPin {
public OutPin(int pinNumber) {
}
public OutPin apply(int pinNumber) {
return new OutPin(pinNumber);
}
}
This does not make my Scala code (first line above) compile, and instead gives me an error:
Object OutPin is not a value
What is the correct way to implement Scala's apply method in Java?
Upvotes: 9
Views: 2376
Reputation: 14073
Your problem is not with the apply method per-se, but with trying to implement a Scala singleton object in Java.
I think (but am not certain) that this is very difficult, perhaps even impossible, by design.
Consider a very, very simple case:
object Obj;
This compiles to two JVM bytcode files, Obj$.class
and Obj.class
. In theory, it should be easy to just inspect the bytecode of those two classes, and reexpress the same thing in Java. The basic structure of Scala singleton objects is very, very simple:
Obj
, a Obj.class
and Obj$.class
must be generatedObj$
class must have a public final static
field of type Obj$
called MODULE$
, which will be initialized on class initialization refer to the singleton object. In Scala, calls to Obj.foo()
get mapped to Obj$.MODULE$.foo()
[...if Obj had a method called foo()
, that is!]Obj
class contains static functions that just forward to a call of a method of the same name and signature on Obj$.MODULE$
.That sounds complicated, but it's really not so much. It's trivial to write a pair of Java classes that goes this far. But the Scala compiler (2.10.3) still won't recognize the pair as constituting a Scala singleton. Diving into the bytecode of a Scala-compiler generated singleton, you'll see that there are details that are hard to express in legal Java. [Hint: javap -c -p -s -v <fully-qualified-class-name>
]
For example, the final static MODULE$
field is initialized indirectly by the static initializer. The static initializer just constructs an Obj$
object, without directly assigning it. The assignment occurs within the private constructor. That's illegal in Java: blank static finals must be certainly initialized in the static initializer, and cannot be assigned in code (like the private constructor) that might potentially be called from outside the initializer and multiple times. The Scala compiler generates bytecode that respects the blank final semantics (because the private constructor is only called once), but exceeds the Java compiler's ability to verify those semantics. So this code would be rejected if expressed in Java.
Also, the Obj
class (the version without the terminal dollar sign) includes an annotation of type ScalaSig, which looks quite complicated and would be hard to reproduce by hand (in Java or in Scala), at least for those of us unsure exactly how this annotation works.
I don't know exactly what the Scala compiler looks for before deciding to treat a pair of classes as a "value", that is a valid Scala singleton object, but Scala's designers have chosen not to make it easy, despite the simplicity of the basic scheme. Probably they wish to preserve the ability to reorganize how scala singleton objects translate to bytecode. Letting Java programmers synthesize scala singleton objects would effectively render the current scheme a permanent part of Scala's public API.
Note that writing an ordinary, non-singleton class whose instances have an apply(...) method and so can be called like functions is easy from Java and works fine. Here's a Java cat:
public class Cat {
public String apply( int i ) {
return "Meow: " + i;
}
}
Here's a use of Scala's sugared apply:
Welcome to Scala version 2.10.3 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_45).
Type in expressions to have them evaluated.
Type :help for more information.
scala> val Morris = new Cat;
Morris: Cat = Cat@6b4feafa
scala> Morris(8)
res0: String = Meow: 8
Upvotes: 14
Reputation: 24040
The problem is that the Scala compiler says you must define two companion objects in the same file, otherwise you get the error:
Companions 'class OutPin' and 'object OutPin' must be defined in same file
Here is another approach that might work for you. If you define your Scala OutPin
in a separate package, then it will work. For example:
Java class:
package base;
public class OutPin {
private final int i;
public OutPin(int i) {
this.i = i;
}
public int getI() {
return this.i;
}
}
Scala class:
package base.scala
object OutPin {
def apply(i: Int): base.OutPin = new base.OutPin(i)
}
Sample Scala client:
import base.scala.OutPin
object Client extends App {
val op = OutPin(1)
println(op.getI)
}
Running Client prints 1
To get access to the sugared apply
you would have to import base.scala
instead of just base
. If it's really important to get the apply syntax it might be worth it.
Upvotes: 0
Reputation: 25909
If you want an apply method that is usable in Scala you should implement it on the scala an side using object
to wrap your java class with the same name of the class which instantiate it
object OutPin {
def apply(pinNumber :Int) = new OutPin(pinNumber)
}
Upvotes: 1