Tim
Tim

Reputation: 99408

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

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;                                                                                                                                                                

    }                                                                                                                                                                                  

}

Upvotes: 3

Views: 3980

Answers (3)

Andreas
Andreas

Reputation: 159086

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, 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.

Upvotes: 3

Savior
Savior

Reputation: 3531

Why does the following compilation fail?

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

Because Main.java (the only file picked up in that command) makes use of a class called com.exec.one.service.MagicService that is not available on the classpath nor is one of the files being compiled.

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

Because Main.java makes use of a class called com.exec.one.service.MagicService that is also one of the files being compiled.

How can one compile them in one javac command?

What you have is already one command. The javac program accepts a list of source files to compile

Usage: javac <options> <source files>

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

It's used to set the classpath, ie. it includes class files that may be needed during compilation. In your example, it's useless.

However, if you had compiled MagicService separately and pointed the -cp to wherever the corresponding MagicServe.class file was located (taking into account the directory structure that matches its containing package), it would have been useful. This is how 3rd party libraries are included in Java projects.


The Java compiler doesn't impose an ordering. Simply, at compilation time, all required classes must be available, either through a source file being compiled or a class available in the classpath.

Upvotes: 3

Axel Podehl
Axel Podehl

Reputation: 4303

It looks like you should start inside the path "/src/main/java". Only below that you have packages (com.exec.one) matching your folder names. So do a "cd src/main/java" and try:

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

Upvotes: 1

Related Questions