When compiling several .java files with some dependency relation between them, do we need to compile them in some order?
Must a dependency be .class file? Or can a dependency be a .java file?
Specifically, when A.java depends on B.class file compiled from B.java file, but B.class hasn't been created (i.e. B.java file hasn't been compiled into B.class), can we compile A.java by specifying the directory for B.java in java -cp
? Or do we need to compile B.java into B.class first, and then specify the directory for B.class in java -cp
when compiling A.java?
For example, from https://dzone.com/articles/java-8-how-to-create-executable-fatjar-without-ide, ./src/main/java/com/exec/one/Main.java
depends on ./src/main/java/com/exec/one/service/MagicService.java
, and both haven't been compiled yet.
Why does the following compilation fail?
$ 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
Why does the following compilation succeed? How can one compile them in one javac
command? How is -cp ./src/main/java
used in the compilation? What happens in the compilation process?
$ 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;
}
}
TL;DR As documented below, if you compile with this simpler command, where you only ask to compile the Main
class, the compiler will still locate and compile the required MagicService
class, because it can find the source file on the classpath.
javac -cp ./src/main/java ./src/main/java/com/exec/one/Main.java
See the "Searching for Types" section of the compiler documentation page.
Quoting it all here for your convenience, with highlights (bold and/or italic) added by me:
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
, andjava.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. Ifnewer
is specified, then the compiler uses the newer of the two files. Ifsource
is specified, the compiler uses the source file. The default isnewer
.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. Ifnone
is specified, then no class files are generated for the source file. Ifclass
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.