3

I'm writing Java (not Scala) code, where I have a Java Class reference, referring to a Scala type T:

trait T {}

I would now like to discover the Java Class reference, and the singleton instance that corresponds to object T by using Java reflection only, and then call a method in that companion object.

object T {
  def someMethod : String = "abc";
}

I have reverse engineered the generated Scala code to write the following Java code:

Class<?> traitClass = ...;
Class<?> companionClass = Class.forName(traitClass.getName() + "$");
Field module = companionClass.getField("MODULE$");
Object companion = module.get(companionClass);
String abc = (String) companionClass.getMethod("someMethod").invoke(companion);

This doesn't appear very robust - it might change any time with future Scala compilers. Is there a more robust way? I'm also open to calling Scala API (from Java) to discover the companion instance.

Lukas Eder
  • 181,694
  • 112
  • 597
  • 1,319

1 Answers1

2

The code you wrote works for top-level (package-owned) companions. It will also work for basic cases of nested class/trait-object pairs, but there are cases where the simple name + $ scheme doesn't hold. Here's and example:

class C {
  class D
  object D
  def foo() {
    class K
    object K
  }
}

This produces the classfiles C, C$D, C$D$, C$K$2 and C$K$3$.

While the encoding may change any time in the future, it did not change in a very long time, and it's not planned to change. I just tried 2.5.0, the class names are the same except for the classes nested in the method.

If you have control over the Scala classes, the easies way is to add a method def companion = T to trait T.

Another option is to use the Scala reflection API:

scala> import scala.reflect.runtime.{universe => ru}
scala> val m = ru.runtimeMirror(getClass.getClassLoader)

scala> val d = classOf[C$D]
d: Class[C$D] = class C$D

scala> m.runtimeClass(m.classSymbol(d).companionSymbol.asModule.moduleClass.asClass)
res16: Class[_] = class C$D$

But that also doesn't work as expected on the class nested in the method:

scala> val k = classOf[C$K$2]
k: Class[C$K$2] = class C$K$2

scala> m.classSymbol(k).companionSymbol
res2: reflect.runtime.universe.Symbol = <none>

Note that searching for "scala reflection companion" on SO also yields several results (and shows some problems).

Lukas Rytz
  • 1,844
  • 14
  • 26
  • Excellent. If this hasn't changed in such a long time, I suspect my approach is *"good enough"* for my use-case. About *"If you have control over the Scala classes, the easies way is to add a method def companion = T to trait T."* - Unfortunately, I don't have an *instance* of trait T, only its `Class` reference. So, I cannot really call any such method on it (except if I could force Scala to produce a static method on the trait) - *"Note that searching for "scala reflection companion" on SO also yields several results"* Yes, but usually about doing this from Scala, not from Java :) – Lukas Eder Mar 18 '16 at 08:33