Java 找不到导入的 Scala 的符号 class

Java cannot find symbol for imported scala class

我收到了一个Scala jar,需要写一个java程序来使用一个方法。 导入的 Scala 方法肯定有效,因为它已经投入生产多年。

程序如下:

import com.company.VerifyApp // The imported Scala jar

public class Validation {
    public static void main(String[] args) {
        VerifyApp app = new VerifyApp();
        app.main(args);
    }
}

反编译的 Scala 代码:

package com.company;

public final class VerifyApp {
    public static void main(String[] var0) {
        VerifyApp$.MODULE$.main(var0);
    }
}

以下是观察结果:

我想知道可能是什么问题?

编辑: IntelliJ 警告我我正在实例化一个实用程序 class,这很可能是一个错误。由于我是 Java 新手,我用 Google 搜索了一下,发现也许我可以直接调用 VerifyApp.main(args) 而无需实例化对象。这确实通过了构建,但我遇到了运行时错误:

NoClassDefFoundError, caused by ClassNotFoundException

现在我没戏了

回答原问题,错误“找不到符号构造函数 VerifyApp()”的原因与 Java 和 Scala 之间的差异有关。

确实,反编译的 Java 代码如果重新编译,将有一个默认的 public 零参数构造函数。这是 Java 编译器的一个特性:如果没有声明构造函数,它将把这个默认构造函数插入到编译的字节码中。请注意,如果编译的字节码中不存在默认构造函数,则字节码解释器本身不会在运行时插入默认构造函数。

在最初的 Scala 源代码中,它被定义为 singleton object。它看起来像这样:

object VerifyApp {
  def main(args: Array[String]): Unit =
    ???
}

我不知道实际的方法体是什么样的,因为它会被编译成一个名为 VerifyApp$ 的 class,你可以在反编译的源代码中看到引用问题。

通常,Scala 编译器通过以下方式编译 object 定义:

  • 创建一个 class,对象名称后跟 $,包含对象上定义的方法的实例方法、私有构造函数和静态最终 MODULE$包含 class.
  • 实例的字段
  • 使用对象的普通名称创建一个 class,该对象包含调用该 MODULE$ 实例上的匹配方法的静态转发器方法,以及 no 构造函数.

你可以使用javap程序看到这个,例如:

javap -p -c com/company/VerifyApp.class com/company/VerifyApp$.class

你会得到类似这样的输出:

Compiled from "VerifyApp.scala"
public final class com.company.VerifyApp {
  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #17                 // Field com/company/VerifyApp$.MODULE$:Lcom/company/VerifyApp$;
       3: aload_0
       4: invokevirtual #19                 // Method com/company/VerifyApp$.main:([Ljava/lang/String;)V
       7: return
}
Compiled from "VerifyApp.scala"
public final class com.company.VerifyApp$ {
  public static final com.company.VerifyApp$ MODULE$;

  public static {};
    Code:
       0: new           #2                  // class com/company/VerifyApp$
       3: dup
       4: invokespecial #12                 // Method "<init>":()V
       7: putstatic     #14                 // Field MODULE$:Lcom/company/VerifyApp$;
      10: return

  public void main(java.lang.String[]);
    Code:
       0: getstatic     #22                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
       3: invokevirtual #26                 // Method scala/Predef$.$qmark$qmark$qmark:()Lscala/runtime/Nothing$;
       6: athrow

  private com.company.VerifyApp$();
    Code:
       0: aload_0
       1: invokespecial #29                 // Method java/lang/Object."<init>":()V
       4: return
}

从 Java 访问这些对象的常用方法是使用静态转发器方法而不尝试实例化 class。在这种情况下,VerifyApp.main(args)(如您所见)。

在极少数情况下,您确实需要直接访问实例(例如,当它实现了您需要其实例的接口时),您需要使用难看的 VerifyApp$.MODULE$ 引用。它也可以分配给 Java class 或接口的另一个 static final 字段以提供更具可读性的名称。