Зависимость времени компиляции от времени выполнения - Java

95

В чем разница между зависимостями времени компиляции и времени выполнения в Java? Это связано с путем к классам, но чем они отличаются?

Кунал
источник

Ответы:

82
  • Зависимость во время компиляции : вам нужна зависимость в вашем CLASSPATHфайле для компиляции вашего артефакта. Они создаются, потому что у вас есть какая-то «ссылка» на зависимость, жестко закодированная в вашем коде, например, вызов newнекоторого класса, расширение или реализация чего-либо (прямо или косвенно) или вызов метода с использованием прямой reference.method()записи.

  • Зависимость во время выполнения : вам нужна зависимость CLASSPATHдля запуска вашего артефакта. Они создаются, потому что вы выполняете код, который обращается к зависимости (либо жестко запрограммированным способом, либо через отражение или что-то еще).

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

Пример этого

В C.java (генерирует C.class):

package dependencies;
public class C { }

В A.java (генерирует A.class):

package dependencies;
public class A {
    public static class B {
        public String toString() {
            C c = new C();
            return c.toString();
        }
    }
    public static void main(String[] args) {
        if (args.length > 0) {
            B b = new B();
            System.out.println(b.toString());
        }
    }
}

В этом случае он Aимеет зависимость времени компиляции от Cсквозного B, но он будет иметь зависимость времени выполнения от C только в том случае, если вы передадите некоторые параметры при выполнении java dependencies.A, поскольку JVM будет пытаться решить Bзависимость только от того, Cкогда он выполнит B b = new B(). Эта функция позволяет вам предоставлять во время выполнения только зависимости классов, которые вы используете в своих путях кода, и игнорировать зависимости остальных классов в артефакте.

gpeche
источник
1
Я знаю, что это очень старый ответ, но как JVM может не иметь C в качестве зависимости во время выполнения с самого начала? Если он способен распознать «вот ссылка на C, пора добавить его как зависимость», то разве C уже не является зависимостью, поскольку JVM распознает ее и знает, где она находится?
wearebob 07
@wearebob Это могло быть указано таким образом, я думаю, но они решили, что ленивая компоновка лучше, и лично я согласен по причине, указанной выше: она позволяет вам использовать некоторый код, если необходимо, но не заставляет вас включать его в ваше развертывание, если оно вам не нужно. Это очень удобно при работе со сторонним кодом.
gpeche 07
Если у меня где-то развернут jar, он уже должен будет содержать все его зависимости. Он не знает, будет ли он запускаться с аргументами или нет (поэтому он не знает, будет ли использоваться C), поэтому он должен был бы иметь C в любом случае. Я просто не понимаю, как можно сэкономить память / время, если с самого начала не было C в пути к классам.
wearebob 07
1
@wearebob JAR не обязательно должен включать все его зависимости. Вот почему почти каждое нетривиальное приложение имеет каталог / lib или аналогичный, содержащий несколько JAR.
gpeche 07
34

Простой пример - посмотреть на api, например на api сервлета. Чтобы ваши сервлеты компилировались, вам понадобится servlet-api.jar, но во время выполнения контейнер сервлетов предоставляет реализацию сервлета api, поэтому вам не нужно добавлять servlet-api.jar в путь к вашему классу времени выполнения.

Мартин Альгестен
источник
Для пояснения (это меня смутило), если вы используете maven и создаете войну, "servlet-api" обычно является "предоставленной" зависимостью, а не зависимостью "времени выполнения", что может привести к его включению в войну, если Я прав.
xdhmoore
2
«предоставлено» означает включать во время компиляции, но не связывать его в WAR или другом наборе зависимостей. «runtime» делает обратное (недоступно при компиляции, но упаковано с WAR).
KC Baltz
30

Компилятору нужен правильный путь к классам для компиляции вызовов библиотеки (зависимости времени компиляции)

JVM нужен правильный путь к классам, чтобы загружать классы в вызываемой вами библиотеке (зависимости времени выполнения).

Они могут отличаться по двум причинам:

1) если ваш класс C1 вызывает библиотечный класс L1, а L1 вызывает библиотечный класс L2, то C1 имеет зависимость времени выполнения от L1 и L2, но только зависимость времени компиляции от L1.

2) если ваш класс C1 динамически создает экземпляр интерфейса I1, используя Class.forName () или какой-либо другой механизм, а класс реализации для интерфейса I1 - это класс L1, то C1 имеет зависимость времени выполнения от I1 и L1, но только зависимость времени компиляции на I1.

Другие «косвенные» зависимости, одинаковые для времени компиляции и времени выполнения:

3) ваш класс C1 расширяет библиотечный класс L1, а L1 реализует интерфейс I1 и расширяет библиотечный класс L2: C1 имеет зависимость во время компиляции от L1, L2 и I1.

4) у вашего класса C1 есть метод foo(I1 i1)и метод, bar(L1 l1)где I1 - это интерфейс, а L1 - это класс, который принимает параметр, который является интерфейсом I1: C1 имеет зависимость времени компиляции от I1 и L1.

По сути, чтобы делать что-нибудь интересное, ваш класс должен взаимодействовать с другими классами и интерфейсами в пути к классам. Граф класс / интерфейс, сформированный этим набором интерфейсов библиотеки, дает цепочку зависимостей времени компиляции. Библиотека реализации дают цепочку зависимостей во время выполнения. Обратите внимание, что цепочка зависимостей во время выполнения зависит от времени выполнения или работает медленно: если реализация L1 иногда зависит от создания экземпляра объекта класса L2, и этот класс создается только в одном конкретном сценарии, тогда нет никакой зависимости, кроме этот сценарий.

Джейсон С
источник
1
Разве зависимость времени компиляции в примере 1 не должна быть L1?
BalusC
Спасибо, но как работает загрузка классов во время выполнения? Во время компиляции это легко понять. Но как это работает во время выполнения, если у меня есть два Jar-файла разных версий? Какой он выберет?
Kunal
1
Я почти уверен, что загрузчик классов по умолчанию принимает путь к классам и проходит через него по порядку, поэтому, если у вас есть две банки в пути к классам, которые содержат один и тот же класс (например, com.example.fooutils.Foo), он будет использовать тот, который является первым в пути к классам. Либо так, либо вы получите ошибку, указывающую на двусмысленность. Но если вам нужна дополнительная информация о загрузчиках классов, вам следует задать отдельный вопрос.
Джейсон С.
Я думаю, что в первом случае зависимости времени компиляции также должны присутствовать в L2, то есть предложение должно быть: 1) если ваш класс C1 вызывает библиотечный класс L1, а L1 вызывает библиотечный класс L2, то C1 имеет зависимость времени выполнения от L1 и L2, но только зависимость времени компиляции от L1 и L2. Это так, поскольку во время компиляции также, когда компилятор java проверяет L1, он также проверяет все другие классы, на которые ссылается L1 (за исключением динамических зависимостей, таких как Class.forName ("myclassname)) ... в противном случае, как он проверяет, что компиляция работает нормально. Пожалуйста, объясните, если вы думаете иначе
Раджеш Гоэль
1
Нет. Вам нужно прочитать, как компиляция и компоновка работают в Java. Все, о чем компилятор заботится, когда он обращается к внешнему классу, - это как использовать этот класс, например, каковы его методы и поля. Его не волнует, что на самом деле происходит в методах этого внешнего класса. Если L1 вызывает L2, это деталь реализации L1, а L1 уже был скомпилирован где-то еще.
Джейсон С.
13

Java фактически ничего не связывает во время компиляции. Он только проверяет синтаксис, используя соответствующие классы, которые он находит в CLASSPATH. Только во время выполнения все собирается и выполняется на основе CLASSPATH того времени.

JOTN
источник
Это не раньше времени загрузки ... время выполнения отличается от времени загрузки.
overxchange 03
11

Зависимости времени компиляции - это только зависимости (другие классы), которые вы используете непосредственно в компилируемом классе. Зависимости времени выполнения охватывают как прямые, так и косвенные зависимости используемого вами класса. Таким образом, зависимости времени выполнения включают в себя зависимости зависимостей и любые зависимости отражения, такие как имена классов, которые у вас есть в String, но используются в Class#forName().

BalusC
источник
Спасибо, но как работает загрузка классов во время выполнения? Во время компиляции это легко понять. Но как это работает во время выполнения, если у меня есть два Jar-файла разных версий? Какой класс выберет Class.forName () в случае наличия нескольких классов разных классов в пути к классам?
Kunal
Тот, который, конечно, соответствует названию. Если вы на самом деле имеете в виду «несколько версий одного и того же класса», то это зависит от загрузчика классов. Будет загружен самый "ближайший".
BalusC
Что ж, я думаю, что если у вас есть A.jar с A, B.jar с B extends Aи C.jar с, C extends Bто C.jar зависит от времени компиляции на A.jar, хотя зависимость C от A является косвенной.
gpeche
1
Проблема во всех зависимостях времени компиляции - это зависимость интерфейса (независимо от того, осуществляется ли интерфейс через методы класса, или через методы интерфейса, или через метод, который содержит аргумент, который является классом или интерфейсом)
Джейсон С.
2

Для Java зависимость времени компиляции - это зависимость вашего исходного кода. Например, если класс A вызывает метод из класса B, тогда A зависит от B во время компиляции, поскольку A должен знать о B (типе B), который будет скомпилирован. Уловка здесь должна заключаться в следующем: скомпилированный код еще не является полным и исполняемым кодом. Он включает заменяемые адреса (символы, метаданные) для источников, которые еще не скомпилированы или не существуют во внешних jar-файлах. Во время связывания эти адреса должны быть заменены фактическими адресами в памяти. Чтобы сделать это правильно, должны быть созданы правильные символы / адреса. И это можно сделать с помощью типа класса (B). Я считаю, что это основная зависимость во время компиляции.

Зависимость во время выполнения больше связана с фактическим потоком управления. Он вызывает фактические адреса памяти. Это зависимость, которая возникает во время работы вашей программы. Здесь вам нужны детали класса B, такие как реализации, а не только информация о типе. Если класс не существует, вы получите исключение RuntimeException и JVM выйдет.

Обе зависимости, как правило, не должны идти в одном направлении. Однако это вопрос объектно-ориентированного дизайна.

В C ++ компиляция немного отличается (не вовремя), но у нее тоже есть компоновщик. Полагаю, этот процесс можно было бы считать похожим на Java.

стандартный вывод
источник