简体   繁体   中英

Scala: access static Java method using type-parameter information

I use a Java code-generator (unfortunately I cannot get rid of it) which spits out code like this:

abstract class Abs1 { //... }
abstract class Abs2 { //... }
interface I { //... }

public static final class C1 extends Abs implements I {

  public final static Inner newInstance() { return Inner.create(); }

  public final class Inner extends Abs2 { 
    private static Inner create() { return new Inner(); }
  }

  public final static C1 build() { 
    // builds the object instantiating some fields with defaults
  }

}

final static class C2 extends Abs implements I {

  // exactly same as in C1: 

  public final static Inner newInstance() { return Inner.create(); }

  public final class Inner extends Abs2 { 
    private static Inner create() { return new Inner(); }
  }

  public final static C1 build() { 
    // builds the object instantiating some fields with defaults
  }

}

I have many other classes like C1 and C2 in the same style. As you can see, every outer class (C1, C2 ...) has an Inner class, always named as Inner. Also, all InnerClasses extend the same abstract class.

I want to create instances of each of these outer-classes from Strings. I have a utility (Merger) that can merge an instance with it's String.

def createC1(str: String) = {
  val ins = C1.newInstance
  Merger(ins, str)
  ins.build
}

def createC2(str: String) = {
  val ins = C2.newInstance
  Merger(ins, str)
  ins.build
}

... this seemed like obvious code-duplication. So I wanted to take advantage of type-parameterization.

def build[A <: Abs](str: String) = {
  val ins = A.newInstance // this obviously won't compile - but this is my intent
  Merger(ins, str)
  ins.build
}

So I could do: build[Cn](str)

How can I call the static Java method using the type parameter info ? I tried using ClassTag:

def build[A <: Abs : ClassTag](str: String) = {
    val ins1 = (new A) // ins1 type is now A with Object, WAT ?
    val ins2 = ins.asInstanceOf[A] // I do not want to do asInstanceOf but the compiler won't recognize that ins2 is an instance of A otherwise
    // ins2 has access to newInstance method
    // Not sure if the below code works, I had to actually try it :)
    // Merger(str, ins2)
    // ins2.build()
    ???
}

Why doesn't it infer the type in ins1 ?

EDIT: Make outer classes static.

The Problem

The main problem with the code you are trying to create is that you are trying to access a static method of a generic type. Generic types, both in Java and Scala, do not allow access to the static methods associated with the type. I have read about some fairly complicated reflection that you might be able to employ in order to gain access to the companion object of a generic type, but I would highly recommend avoiding anything like that in this case as such code is extremely brittle and unmaintainable. After all, the goal of any abstraction is to make the code easier to work with, not harder.

A Couple Of Solutions

There are a few things you can do easily to abstract over this situation and make working with this code more tractable. I am making the assumption that you can't modify the generated code at all, because if you can there are some even better ways to deal with this issue that I am going to skip.

Solution 1 Structural Typing

The first, and most simple solution to this issue is to use a structural type. Consider the following code (it is Scala code, but it should function the same conceptually in Java).

scala> :paste
// Entering paste mode (ctrl-D to finish)

trait Foo
trait InnerFoo
trait Bar extends Foo
trait Baz extends Foo
object Bar {
    class InnerBar extends InnerFoo
    def newInstance: InnerBar = new InnerBar
    def build: Bar = new Bar {}
}
object Baz {
    class InnerBaz extends InnerFoo
    def newInstance: InnerBaz = new InnerBaz
    def build: Baz = new Baz {}
}

// Exiting paste mode, now interpreting.

defined trait Foo
defined trait InnerFoo
defined trait Bar
defined trait Baz
defined object Bar
defined object Baz

Now, I want to call newInstance on anything with the following function, which does not compile

def makeInstance[A <: Foo, B <: InnerFoo]: B = {
  A.newInstance
  // do some stuff
  A.build
}

This is because the compiler doesn't know that just because an A is a subtype of Foo that the companion object of A has a newInstance method on it. The companion object of a class/trait has nothing to do with the type hierarchy of the class/trait itself. This is basically the same in Java as well, the static members on a class have nothing to do with the type of a class, they are just a place to define members that are scoped to a class in a particular way.

However, if I define the makeInstance function like so, everything works,

scala> def makeInstance[A <: InnerFoo, B <: Foo](static: { def newInstance: A; def build: B }) = {
     | static.newInstance
     | // do some stuff
     | static.build
     | }
warning: there were two feature warnings; re-run with -feature for details
makeInstance: [A <: InnerFoo, B <: Foo](static: AnyRef{def newInstance: A; def build: B})B

scala> makeInstance(Bar)
res16: Bar = Bar$$anon$1@1601e47

scala> makeInstance(Baz)
res17: Baz = Baz$$anon$2@6de54b40

This is only slightly more boiler plate than your ideal solution. Take heed however, the reason that the compiler issued a warning for this is that structural types require reflection and thus have a runtime performance penalty. However, if the code is not in a critical portion of your program, you could probably not worry about it.

Solution 2, just pass the function

Rather than using structural types, we can just pass the functions to do the work directly. This is slightly more verbose than the structural type version, but also slightly faster and safer.

scala> def makeInstance[A <: InnerFoo, B <: Foo](newInstanceProc: => A, buildProc: => B): B = {
     | newInstanceProc
     | // do some stuff
     | buildProc
     | }
makeInstance: [A <: InnerFoo, B <: Foo](newInstanceProc: => A, buildProc: => B)B

scala> makeInstance(Bar.newInstance, Bar.build)
res19: Bar = Bar$$anon$1@4d0402b

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM