Час компіляції та залежність від часу роботи - Java


91

У чому різниця між залежністю часу компіляції та часу виконання в Java? Це пов’язано з класом, але чим вони відрізняються?

Відповіді:


78
  • Залежність від часу компіляції : Вам потрібна залежність у вашому 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(). Ця функція дозволяє надавати під час виконання лише залежності класів, які ви використовуєте у своїх шляхах коду, а також ігнорувати залежності решти класів в артефакті.


1
Я знаю, що це дуже стара відповідь, але як JVM не може мати C як залежність від часу виконання з самого початку? Якщо він здатний розпізнати "ось посилання на C, час додати його як залежність", то чи не є C по суті вже залежністю, оскільки JVM його розпізнає і знає, де воно знаходиться?
wearebob

@wearebob Це могло бути вказано таким чином, я думаю, але вони вирішили, що ліниве посилання було краще, і особисто я погоджуюсь з вищезазначеною причиною: це дозволяє використовувати якийсь код, якщо це необхідно, але не змушує вас включати його в ваше розгортання, якщо воно вам не потрібно. Це дуже зручно при роботі зі стороннім кодом.
gpeche

Якщо у мене десь розгорнутий jar, він вже повинен містити всі його залежності. Він не знає, чи буде він запускатися з аргументами чи ні (тому він не знає, чи буде використовуватися C), тому він повинен мати доступний C в будь-якому випадку. Я просто не бачу, як економиться будь-яка пам’ять / час, якщо не мати C на шляху до класу з самого початку.
wearebob

1
@wearebob JAR не повинен включати всі його залежності. Ось чому майже в кожному нетривіальному додатку є каталог / lib або подібний, що містить кілька JAR.
gpeche

33

Простий приклад - розглянути такий api, як сервлет api. Щоб змушувати ваші сервлети компілюватись, вам потрібен servlet-api.jar, але під час виконання контейнер сервлета забезпечує реалізацію API сервлету, тому вам не потрібно додавати servlet-api.jar до шляху вашого класу середовища виконання.


Для роз'яснення (це мене збентежило), якщо ви використовуєте maven і будуєте війну, "servlet-api", як правило, є "забезпеченою" залежністю замість "runtime" залежності, що призведе до її включення у війну, Я маю рацію.
xdhmoore

2
"надано" означає, включати під час компіляції, але не об'єднувати його в WAR або іншу колекцію залежностей. 'runtime' робить навпаки (не доступний під час компіляції, але в комплекті з WAR).
KC Baltz,

29

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

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

Дякую, але як працює завантаження класу під час виконання? Під час компіляції це легко зрозуміти. Але як це діє під час виконання у випадку, коли у мене є два банки різних версій? Якого він обере?
Кунал,

1
Я майже впевнений, що завантажувач класів за замовчуванням бере шлях до класу і проходить по порядку, тому, якщо у вас є два банки в шляху до класу, які обидва містять один і той же клас (наприклад, com.example.fooutils.Foo), він буде використовувати той є першим у класі. Або це, або ви отримаєте помилку із заявою про двозначність. Але якщо ви хочете отримати більше інформації, що стосується завантажувачів класів, вам слід задати окреме питання.
Jason S

Я думаю, що в першому випадку залежності від часу компіляції також повинні бути там на L2, тобто речення повинно бути: 1) якщо ваш клас C1 викликає клас бібліотеки L1, а L1 викликає клас бібліотеки L2, тоді C1 має залежність часу виконання від L1 і L2, але лише залежність часу компіляції від L1 і L2. Це так, оскільки під час компіляції також, коли компілятор Java перевіряє L1, тоді він також перевіряє всі інші класи, на які посилається L1 (виключаючи динамічні залежності, такі як Class.forName ("myclassname)) ... інакше як він перевіряє, що компіляція працює нормально. Будь ласка, поясніть, якщо вважаєте інакше
Раджеш Гоель

1
Ні. Вам потрібно прочитати про те, як компіляція та зв’язки працюють у Java. Все, про що турбує компілятор, коли він посилається на зовнішній клас, це те, як використовувати цей клас, наприклад, які його методи та поля. Байдуже, що насправді відбувається в методах цього зовнішнього класу. Якщо L1 викликає L2, це деталь реалізації L1, і L1 вже складено в іншому місці.
Jason S

12

Java насправді нічого не пов’язує під час компіляції. Він перевіряє синтаксис лише за допомогою відповідних класів, знайдених у CLASSPATH. Лише під час виконання все збирається разом і виконується на основі CLASSPATH того часу.


Це не до часу завантаження ... час виконання відрізняється від часу завантаження.
переобмін

10

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


Дякую, але як працює завантаження класу під час виконання? Під час компіляції це легко зрозуміти. Але як це діє під час виконання у випадку, коли у мене є два банки різних версій? Який клас би взяв Class.forName () у випадку, коли в шляху до класу є кілька класів різних класів?
Кунал,

Той, що відповідає назві, звичайно. Якщо ви насправді маєте на увазі "кілька версій одного класу", то це залежить від завантажувача класів. Буде завантажено «найближчий».
BalusC

Ну, я думаю, якщо у вас є A.jar з A, B.jar з B extends Aі C.jar з C extends Bтоді C.jar залежить від часу компіляції на A.jar, хоча залежність C від A є непрямою.
gpeche

1
Проблема у всіх залежностях часу компіляції полягає в залежності інтерфейсу (незалежно від того, чи інтерфейс здійснюється через методи класу, чи через методи інтерфейсу, чи через метод, що містить аргумент, який є класом або інтерфейсом)
Jason S,

2

Для Java залежність від часу компіляції - це залежність вашого вихідного коду. Наприклад, якщо клас A викликає метод із класу B, то A залежить від B під час компіляції, оскільки A повинен знати про B (тип B), який потрібно скомпілювати. Фокус тут повинен бути такий: скомпільований код ще не є повним та виконуваним кодом. Він включає замінні адреси (символи, метадані) для джерел, які ще не скомпільовані або не існують у зовнішніх банках. Під час зв'язування ці адреси повинні бути замінені фактичними адресами в пам'яті. Щоб зробити це правильно, слід створити правильні символи / адреси. І це можна зробити з типом класу (B). Я вважаю, що це основна залежність під час компіляції.

Залежність від виконання більше пов'язана з фактичним потоком управління. Він викликає фактичні адреси пам'яті. Це залежність, яку ви маєте, коли працює ваша програма. Тут вам потрібні деталі класу B, такі як реалізації, а не тільки інформація про тип. Якщо клас не існує, ви отримаєте RuntimeException і JVM вийде.

Обидві залежності, як правило, і не повинні, мають однаковий напрямок. Однак це питання ОО-дизайну.

У C ++ компіляція дещо інша (не просто своєчасна), але вона також має компонувальник. Тож процес можна вважати схожим на Java, я думаю.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.