Scala:使用类型参数信息访问静态 Java 方法

Scala: access static Java method using type-parameter information

我使用 Java 代码生成器(不幸的是我无法摆脱它)生成如下代码:

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
  }

}

我还有许多其他 class 像 C1 和 C2 一样的风格。 如您所见,每个外部 class(C1、C2 ...)都有一个内部 class,始终命名为 Inner。此外,所有 InnerClasses 都扩展了相同的抽象 class.

我想从字符串中创建每个外部 classes 的实例。我有一个实用程序 (Merger),可以将一个实例与其字符串合并。

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
}

...这似乎是明显的代码重复。所以我想利用类型参数化。

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
}

所以我可以这样做:build[Cn](str)

如何使用类型参数信息调用静态 Java 方法?我尝试使用 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()
    ???
}

为什么它不推断 ins1 中的类型?

编辑:将外部class设为静态。

问题

您尝试创建的代码的主要问题是您试图访问泛型类型的 static 方法。 Java 和 Scala 中的通用类型都不允许访问与该类型关联的 static 方法。我读过一些相当复杂的反射,你 可能 可以使用它们来访问通用类型的 companion 对象,但我强烈建议避免任何事情就像在这种情况下那样,因为这样的代码非常脆弱且无法维护。毕竟,任何抽象的目标都是让代码更易于使用,而不是更难。

几个解决方案

您可以轻松地做一些事情来抽象化这种情况并使使用此代码更容易处理。 我假设您根本无法修改生成的代码,因为如果可以的话,还有一些甚至更好 处理这个问题的方法,我将跳过。

解决方案 1 结构类型化

解决此问题的第一个也是最简单的方法是使用结构类型。考虑以下代码(它是 Scala 代码,但它在 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

现在,我想用以下函数调用 newInstance,它 not compile

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

这是因为编译器不知道仅仅因为 AFoo 的子类型,A 的伴随对象就有一个 newInstance 方法在上面。 class/trait 的伴随对象与 class/trait 本身的类型层次无关。这在 Java 中也基本相同,class 上的静态成员与 class 的 type 无关,它们只是一个以特定方式定义范围为 class 的成员的地方。

但是,如果我这样定义 makeInstance 函数,一切正常,

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@1601e47

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

这仅比您的理想解决方案略多。但是请注意,编译器为此发出警告的原因是结构类型需要反射,因此会降低运行时性能。但是,如果代码不在程序的关键部分,您可能不必担心。

方案二,只传函数

不使用结构类型,我们可以直接传递函数来完成工作。这比结构类型版本稍微更冗长,但也稍微更快更安全。

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@4d0402b