【问题标题】:When there is dependency between several .java files, do we need to compile them in some order?当多个 .java 文件之间存在依赖关系时,我们是否需要按某种顺序编译它们?
【发布时间】:2019-09-02 08:11:39
【问题描述】:

在编译多个.java文件时,它们之间有一些依赖关系,我们需要按某种顺序编译它们吗?

依赖项必须是 .class 文件吗?或者依赖项可以是 .java 文件吗?

具体来说,当A.java依赖于从B.java文件编译的B.class文件,但是B.class还没有被创建(即B.java文件还没有被编译成B.class),我们能不能通过在java -cp 中指定B.java 的目录来编译A.java?还是我们需要先将B.java编译成B.class,然后在编译A.java的时候在java -cp中指定B.class的目录?

例如,从https://dzone.com/articles/java-8-how-to-create-executable-fatjar-without-ide./src/main/java/com/exec/one/Main.java 依赖于./src/main/java/com/exec/one/service/MagicService.java,两者都还没有被编译。

为什么下面的编译会失败?

$ javac  ./src/main/java/com/exec/one/*.java -d ./out/
./src/main/java/com/exec/one/Main.java:3: error: package com.exec.one.service does not exist
import com.exec.one.service.MagicService;
                           ^
./src/main/java/com/exec/one/Main.java:8: error: cannot find symbol
        MagicService service = new MagicService();
        ^
  symbol:   class MagicService
  location: class Main
./src/main/java/com/exec/one/Main.java:8: error: cannot find symbol
        MagicService service = new MagicService();
                                   ^
  symbol:   class MagicService
  location: class Main
3 errors

为什么下面的编译会成功?如何在一个javac 命令中编译它们? -cp ./src/main/java在编译中是如何使用的?编译过程会发生什么?

$ javac -cp ./src/main/java ./src/main/java/com/exec/one/*.java ./src/main/java/com/exec/one/**/*.java

./src/main/java/com/exec/one/Main.java

package com.exec.one;                                                                                                                                                                  

import com.exec.one.service.MagicService;                                                                                                                                              

public class Main {                                                                                                                                                                    

    public static void main(String[] args){                                                                                                                                            

        System.out.println("Main Class Start");                                                                                                                                        

        MagicService service = new MagicService();                                                                                                                                     

        System.out.println("MESSAGE : " + service.getMessage());                                                                                                                       

     }                                                                                                                                                                                  

}

./src/main/java/com/exec/one/service/MagicService.java

package com.exec.one.service;                                                                                                                                                          

public class MagicService {                                                                                                                                                            

  private final String message;                                                                                                                                                      

    public MagicService(){                                                                                                                                                             

        this.message = "Magic Message";                                                                                                                                                

    }                                                                                                                                                                                  

    public String getMessage(){                                                                                                                                                        

         return message;                                                                                                                                                                

    }                                                                                                                                                                                  

}

【问题讨论】:

  • 这是一个 maven 项目吗?
  • @ThorbjørnRavnAndersen 不是。

标签: java javac


【解决方案1】:

TL;DR 如下所述,如果您使用这个更简单的命令进行编译,您只要求编译 Main 类,编译器仍将定位并编译所需的 MagicService 类,因为它可以在类路径上找到源文件。

javac -cp ./src/main/java ./src/main/java/com/exec/one/Main.java

请参阅编译器文档页面的"Searching for Types" section

为方便起见,在这里引用所有内容,并添加了我添加的突出显示(粗体和/或斜体):

要编译源文件,编译器通常需要有关类型的信息,但类型定义不在命令行指定的源文件中。编译器需要源文件中使用、扩展或实现的每个类或接口的类型信息。这包括源文件中未明确提及但通过继承提供信息的类和接口。

例如,当您创建子类java.applet.Applet 时,您也在使用Applet 的祖先类:java.awt.Paneljava.awt.Containerjava.awt.Componentjava.lang.Object

当编译器需要类型信息时,它会搜索定义类型的文件或文件编译器首先在引导和扩展类中搜索 class 文件,然后在用户类路径(默认为当前目录)中搜索。通过设置CLASSPATH 环境变量或使用-classpath 选项来定义用户类路径。

如果您设置了-sourcepath 选项,那么编译器会在指定的路径中搜索源文件。否则,编译器会在用户类路径中搜索 class 文件和 source 文件。

您可以使用 -bootclasspath-extdirs 选项指定不同的引导程序或扩展类。见Cross-Compilation Options

成功的类型搜索可能会产生一个类文件、一个源文件或两者。如果两者都找到,那么您可以使用-Xprefer 选项来指示编译器使用哪个。如果指定了newer,则编译器使用两个文件中较新的一个。如果指定了source,则编译器使用源文件。默认为newer

如果类型搜索找到所需类型的文件,无论是单独找到所需类型,还是作为-Xprefer 选项设置的结果,那么编译器读取源文件以获取所需的信息。 默认情况下,编译器也会编译源文件。您可以使用-implicit 选项来指定行为。如果指定了none,则不会为源文件生成类文件。如果指定了class,则为源文件生成类文件。

在注解处理完成之前,编译器可能不会发现需要某些类型信息。当在源文件中找到类型信息且未指定 -implicit 选项时,编译器会发出警告,指出正在编译文件,但未经过注释处理。要禁用警告,请在命令行上指定文件(以便对其进行注释处理)或使用-implicit 选项指定是否应为此类源文件生成类文件。

【讨论】:

  • 请注意,这是 Oracle 编译器的行为,可能与其他编译器实现不同。
  • 谢谢。我正在使用 openjdk
【解决方案2】:

为什么下面的编译会失败?

$ javac ./src/main/java/com/exec/one/*.java -d ./out/

因为Main.java(该命令中提取的唯一文件)使用了一个名为com.exec.one.service.MagicService 的类,该类在类路径中不可用,也没有正在编译的文件之一。

为什么下面的编译会成功?

$ javac -cp ./src/main/java ./src/main/java/com/exec/one/*.java ./src/main/java/com/exec/one/**/*.java

因为Main.java 使用了一个名为com.exec.one.service.MagicService 的类,它也是正在编译的文件之一。

如何在一个javac 命令中编译它们?

你所拥有的已经是一个命令。 javac 程序接受要编译的源文件列表

Usage: javac <options> <source files>

-cp ./src/main/java在编译中是如何使用的?

用于设置类路径,即。它包括编译期间可能需要的类文件。在你的例子中,它是没用的。

但是,如果您单独编译了MagicService 并将-cp 指向相应的MagicServe.class 文件所在的位置(考虑到与其包含的包匹配的目录结构),它会很有用。这就是在 Java 项目中包含 3rd 方库的方式。


Java 编译器不强制执行排序。简单地说,在编译时,所有必需的类都必须可用,无论是通过正在编译的源文件还是类路径中可用的类。

【讨论】:

  • 即使您不完全正确,也投了赞成票(请参阅my answer)。即使源文件未在命令中列出,也可以编译源文件,只要源文件位于sourcepath(或classpath,如果没有给出sourcepath)。
【解决方案3】:

看起来您应该从路径“/src/main/java”开始。只有在此之下,您才有与您的文件夹名称匹配的包 (com.exec.one)。所以执行“cd src/main/java”并尝试:

javac ./com/exec/one/*.java

【讨论】: