当几个 .java 文件之间存在依赖关系时,我们是否需要按顺序编译它们?

When there is dependency between several .java files, do we need to compile them in some order?

编译多个.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;                                                                                                                                                                

    }                                                                                                                                                                                  

}

看起来你应该从路径“/src/main/java”开始。只有在它下面你有匹配你的文件夹名称的包(com.exec.one)。所以做一个 "cd src/main/java" 并尝试:

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

Why does the following compilation fail?

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

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

Why does the following compilation succeed?

$ 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 的 class,它也是正在编译的文件之一。

How can one compile them in one javac command?

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

Usage: javac <options> <source files>

How is -cp ./src/main/java used in the compilation?

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

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


Java 编译器不强制排序。简单地说,在编译时,所有必需的 classes 必须可用,通过正在编译的源文件或 class 路径中可用的 class。

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

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

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

为方便起见,在此全部引用,并添加了我添加的亮点(粗体 and/or 斜体):

To compile a source file, the compiler often needs information about a type, but the type definition is not in the source files specified on the command line. The compiler needs type information for every class or interface used, extended, or implemented in the source file. This includes classes and interfaces not explicitly mentioned in the source file, but that provide information through inheritance.

For example, when you create a subclass java.applet.Applet, you are also using the ancestor classes of Applet: java.awt.Panel, java.awt.Container, java.awt.Component, and java.lang.Object.

When the compiler needs type information, it searches for a source file or class file that defines the type. The compiler searches for class files first in the bootstrap and extension classes, then in the user class path (which by default is the current directory). The user class path is defined by setting the CLASSPATH environment variable or by using the -classpath option.

If you set the -sourcepath option, then the compiler searches the indicated path for source files. Otherwise, the compiler searches the user class path for both class files and source files.

You can specify different bootstrap or extension classes with the -bootclasspath and the -extdirs options. See Cross-Compilation Options.

A successful type search may produce a class file, a source file, or both. If both are found, then you can use the -Xprefer option to instruct the compiler which to use. If newer is specified, then the compiler uses the newer of the two files. If source is specified, the compiler uses the source file. The default is newer.

If a type search finds a source file for a required type, either by itself, or as a result of the setting for the -Xprefer option, then the compiler reads the source file to get the information it needs. By default the compiler also compiles the source file. You can use the -implicit option to specify the behavior. If none is specified, then no class files are generated for the source file. If class is specified, then class files are generated for the source file.

The compiler might not discover the need for some type information until after annotation processing completes. When the type information is found in a source file and no -implicit option is specified, the compiler gives a warning that the file is being compiled without being subject to annotation processing. To disable the warning, either specify the file on the command line (so that it will be subject to annotation processing) or use the -implicit option to specify whether or not class files should be generated for such source files.