Когда между несколькими файлами .java есть зависимость, нужно ли их компилировать в некотором порядке? - PullRequest
0 голосов
/ 11 апреля 2019

При компиляции нескольких файлов .java с некоторым отношением зависимостей между ними, нам нужно скомпилировать их в некотором порядке?

Должна ли зависимость быть .class файлом? Или зависимость может быть файлом .java?

В частности, когда A.java зависит от файла B.class, скомпилированного из файла B.java, но B.class не был создан (то есть файл B.java не был скомпилирован в B.class), можем ли мы скомпилируйте A.java, указав каталог для B.java в java -cp? Или нам нужно сначала скомпилировать B.java в B.class, а затем указать каталог для B.class в java -cp при компиляции A.java?

Например, от 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 / главная / Java / COM / Exec / один / 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 / главная / Java / COM / Exec / один / сервис / 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;                                                                                                                                                                

    }                                                                                                                                                                                  

}

Ответы [ 3 ]

3 голосов
/ 11 апреля 2019

TL; DR Как документировано ниже, если вы скомпилируете с помощью этой более простой команды, в которой вы только попросите скомпилировать класс Main, компилятор все равно найдет и скомпилирует требуемый класс MagicService, потому что он может найти исходный файл на пути к классам.

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

См. раздел «Поиск типов» на странице документации компилятора.

Цитирую все это здесь для вашего удобства, с выделенными мной выделениями ( жирный и / или курсив ):

Для компиляции исходного файла компилятору часто требуется информация о типе, но определение типа отсутствует в исходных файлах, указанных в командной строке. Компилятору нужна информация о типе для каждого класса или интерфейса, используемого, расширенного или реализованного в исходном файле. Это включает в себя классы и интерфейсы, явно не упомянутые в исходном файле, но предоставляющие информацию посредством наследования.

Например, когда вы создаете подкласс java.applet.Applet, вы также используете классы-предки Applet: java.awt.Panel, java.awt.Container, java.awt.Component и java.lang.Object.

Когда компилятору требуется информация о типе, он ищет файл source или class file , который определяет тип. Компилятор ищет файлы class сначала в в классах начальной загрузки и расширения, а затем в пути к пользовательскому классу (который по умолчанию является текущим каталогом). Путь к классу пользователя определяется путем установки переменной среды CLASSPATH или использования опции -classpath.

Если вы установите опцию -sourcepath, то компилятор будет искать по указанному пути исходные файлы. В противном случае компилятор ищет путь к классу пользователя как для файлов class , так и для source файлов.

Вы можете указать различные классы начальной загрузки или расширения с помощью опций -bootclasspath и -extdirs. См. Параметры кросс-компиляции .

При успешном поиске по типу может быть создан файл класса, исходный файл или оба. Если оба найдены, вы можете использовать опцию -Xprefer, чтобы указать компилятору, что использовать. Если указано newer, то компилятор использует более новые файлы. Если указано source, компилятор использует исходный файл. По умолчанию newer.

Если поиск по типу находит source file для требуемого типа, либо сам по себе, либо в результате установки параметра -Xprefer, то компилятор читает исходный файл для получения необходимой информации. По умолчанию компилятор также компилирует исходный файл. Вы можете использовать опцию -implicit, чтобы указать поведение. Если указано none, то для исходного файла не создаются файлы классов. Если указано class, то файлы классов создаются для исходного файла.

Компилятор может не обнаружить необходимость в какой-либо информации о типе до завершения обработки аннотации. Когда информация о типе находится в исходном файле и не указана опция -implicit, компилятор выдает предупреждение о том, что файл компилируется без обработки аннотаций. Чтобы отключить предупреждение, либо укажите файл в командной строке (чтобы он подвергался обработке аннотаций), либо используйте параметр -implicit, чтобы указать, следует ли создавать файлы классов для таких исходных файлов.

2 голосов
/ 11 апреля 2019

Почему следующая компиляция не удалась?

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


Компилятор Java не устанавливает порядок. Проще говоря, во время компиляции все необходимые классы должны быть доступны либо через скомпилированный исходный файл, либо через класс, доступный в пути к классам.

1 голос
/ 11 апреля 2019

Похоже, вы должны начать с пути "/ src / main / java". Только ниже у вас есть пакеты (com.exec.one), соответствующие именам ваших папок. Так что сделайте «cd src / main / java» и попробуйте:

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