【问题标题】:Java cannot find symbol for imported scala classJava找不到导入的scala类的符号
【发布时间】:2022-01-18 14:29:38
【问题描述】:

我收到了一个 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);
    }
}

以下是观察结果:

  • 错误消息:找不到符号构造函数 VerifyApp()
    • 位置:com.company.VerifyApp 类
    • 请注意,此位置正确
  • 从反编译的代码来看,VerifyApp 是一个没有构造函数的最终类(因此应该有一个默认的 0-arg 构造函数)
  • 我正在使用 IntelliJ 并已激活“包含具有“已提供”范围的依赖项”
  • 这不是 maven 项目,我只是将 jar 加载为外部库
  • 编辑时,IntelliJ 不显示错误或警告
  • 我查看了大量具有相同主题的 Stackoverflow 帖子,但找不到任何有用的内容

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

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

NoClassDefFoundError,由 ClassNotFoundException 引起

现在我没戏了。

【问题讨论】:

  • 您调用 JAR 的方式一定是本地类路径问题。如果我使用正确的目录结构(以及import 上缺少的分号)逐字创建这两个.java 文件,那么一切正常。所以无论发生什么都是你的开发环境本地的。
  • 您是否尝试过:VerifyApp$.MODULE$.main(args); 或类似的东西? lampwww.epfl.ch/~michelou/scala/using-scala-from-java.html。如果它最初是在对象 App.main 中定义的,那么您可以直接调用它:App.main(args).
  • 在运行时检查它是否在类路径中包含scala-library.jar
  • 顺便说一句,这是一个有效的问题,所以我不知道为什么人们试图拒绝或关闭它。让我们让它活着。这是 Java/Scala 互操作的有效问题。
  • 我会将该 JAR 和 Scala JAR 添加到 IntelliJ 中的库中并尝试编译/运行。我不确定什么是实用程序类。通常所有的库都以 JAR 的形式出现,这就是我们所需要的。

标签: java scala intellij-idea


【解决方案1】:

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

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

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

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

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

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

  • 使用对象名称后跟$ 创建一个类,其中包含对象上定义的方法的实例方法、一个私有构造函数和一个包含该类实例的静态最终MODULE$ 字段。李>
  • 使用对象的纯名称创建一个类,该类包含调用 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 访问这些对象的常用方法是使用静态转发器方法而不尝试实例化类。在这种情况下,VerifyApp.main(args)(如您所见)。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-04-06
    • 1970-01-01
    • 2022-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多