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 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.
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.
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.
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