Czas kompilacji a zależność od czasu wykonywania - Java

90

Jaka jest różnica między zależnościami czasu kompilacji i czasu wykonywania w języku Java? Jest to związane ze ścieżką klasową, ale czym się różnią?

Kunal
źródło

Odpowiedzi:

78
  • Zależność w czasie kompilacji : potrzebujesz zależności w swoim, CLASSPATHaby skompilować artefakt. Są tworzone, ponieważ masz jakieś „odniesienie” do zależności zakodowanej na stałe w swoim kodzie, na przykład wywołanie newjakiejś klasy, rozszerzenie lub zaimplementowanie czegoś (bezpośrednio lub pośrednio) lub wywołanie metody przy użyciu reference.method()notacji bezpośredniej .

  • Zależność czasu wykonywania : potrzebujesz zależności w swoim, CLASSPATHaby uruchomić artefakt. Są tworzone, ponieważ wykonujesz kod, który uzyskuje dostęp do zależności (w zakodowany sposób, poprzez odbicie lub cokolwiek innego).

Chociaż zależność od czasu kompilacji zwykle implikuje zależność od czasu wykonywania, można mieć zależność tylko w czasie kompilacji. Jest to oparte na fakcie, że Java łączy zależności klas tylko przy pierwszym dostępie do tej klasy, więc jeśli nigdy nie uzyskasz dostępu do określonej klasy w czasie wykonywania, ponieważ ścieżka kodu nigdy nie jest przechodzona, Java zignoruje zarówno klasę, jak i jej zależności.

Przykład tego

W C.java (generuje klasę C.):

package dependencies;
public class C { }

W A.java (generuje klasę A.):

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());
        }
    }
}

W tym przypadku Ama zależność w czasie kompilacji od Cthrough B, ale będzie miała zależność czasu wykonywania od języka C tylko wtedy, gdy przekażesz niektóre parametry podczas wykonywania java dependencies.A, ponieważ JVM będzie próbować rozwiązać Bzależność tylko od tego, Ckiedy zostanie wykonana B b = new B(). Ta funkcja umożliwia udostępnienie w czasie wykonywania tylko zależności klas, których używasz w ścieżkach kodu, i zignorowanie zależności pozostałych klas w artefakcie.

gpeche
źródło
1
Wiem, że jest to teraz bardzo stara odpowiedź, ale jak JVM może od samego początku nie mieć C jako zależności środowiska wykonawczego? Jeśli jest w stanie rozpoznać „tutaj jest odniesienie do C, czas dodać je jako zależność”, to czy C nie jest już zasadniczo zależnością, ponieważ JVM je rozpoznaje i wie, gdzie się znajduje?
wearebob
@wearebob Wydaje mi się, że można to określić w ten sposób, ale zdecydowali, że leniwe linkowanie jest lepsze i osobiście zgadzam się z podanym powyżej powodem: pozwala to na użycie kodu, jeśli to konieczne, ale nie zmusza do włączenia go Twoje wdrożenie, jeśli go nie potrzebujesz. Jest to bardzo przydatne w przypadku kodu stron trzecich.
gpeche
Jeśli jednak mam gdzieś wdrożony jar, będzie on już musiał zawierać wszystkie jego zależności. Nie wie, czy będzie uruchamiany z argumentami, czy nie (więc nie wie, czy będzie używany C), więc i tak musiałby mieć dostępne C. Po prostu nie widzę, jak oszczędza się pamięć / czas, nie mając C na ścieżce klas od początku.
wearebob
1
@wearebob JAR nie musi zawierać wszystkich swoich zależności. Dlatego prawie każda nietrywialna aplikacja ma katalog / lib lub podobny zawierający wiele plików JAR.
gpeche
33

Prostym przykładem jest spojrzenie na interfejs API, taki jak api serwletu. Aby skompilować serwlety, potrzebny jest plik servlet-api.jar, ale w czasie wykonywania kontener serwletu zapewnia implementację apletu serwletu, więc nie ma potrzeby dodawania pliku servlet-api.jar do ścieżki klasy środowiska wykonawczego.

Martin Algesten
źródło
Dla wyjaśnienia (to mnie zdezorientowało), jeśli używasz mavena i budujesz wojnę, "aplet-api" jest zwykle zależnością "dostarczoną" zamiast zależności "środowiska uruchomieniowego", co spowodowałoby włączenie go do wojny, jeśli Mam rację.
xdhmoore
2
„dostarczone” oznacza dołączenie w czasie kompilacji, ale nie dołączaj go do WAR lub innej kolekcji zależności. „runtime” robi odwrotnie (niedostępne podczas kompilacji, ale w pakiecie z WAR).
KC Baltz
29

Kompilator potrzebuje odpowiedniej ścieżki klasy, aby skompilować wywołania biblioteki (zależności czasu kompilacji)

JVM potrzebuje odpowiedniej ścieżki klas, aby załadować klasy w wywoływanej bibliotece (zależności środowiska wykonawczego).

Mogą się różnić na kilka sposobów:

1) jeśli twoja klasa C1 wywołuje klasę biblioteki L1, a L1 wywołuje klasę biblioteki L2, wówczas C1 ma zależność wykonawczą od L1 i L2, ale tylko zależność czasu kompilacji od L1.

2) jeśli twoja klasa C1 dynamicznie tworzy instancję interfejsu I1 za pomocą Class.forName () lub innego mechanizmu, a klasa implementująca dla interfejsu I1 to klasa L1, wówczas C1 ma zależność wykonawczą od I1 i L1, ale tylko zależność czasu kompilacji na I1.

Inne "pośrednie" zależności, które są takie same w czasie kompilacji i wykonywania:

3) Twoja klasa C1 rozszerza klasę biblioteki L1, a L1 implementuje interfejs I1 i rozszerza klasę biblioteki L2: C1 ma zależność czasu kompilacji od L1, L2 i I1.

4) Twoja klasa C1 ma metodę foo(I1 i1)i metodę, bar(L1 l1)gdzie I1 jest interfejsem, a L1 jest klasą, która przyjmuje parametr, którym jest interfejs I1: C1 ma zależność w czasie kompilacji od I1 i L1.

Zasadniczo, aby zrobić coś interesującego, Twoja klasa musi łączyć się z innymi klasami i interfejsami w ścieżce klas. Wykres klasy / interfejsu utworzony przez ten zestaw interfejsów biblioteki daje łańcuch zależności w czasie kompilacji. Implementacje bibliotek dają łańcuch zależności czasu wykonywania. Należy zauważyć, że łańcuch zależności czasu wykonywania jest zależny od czasu wykonania lub powolny: jeśli implementacja L1 czasami zależy od tworzenia instancji obiektu klasy L2, a instancja tej klasy jest tworzona tylko w jednym konkretnym scenariuszu, to nie ma zależności z wyjątkiem ten scenariusz.

Jason S.
źródło
1
Czy zależność czasu kompilacji w przykładzie 1 nie powinna mieć wartości L1?
BalusC
Dzięki, ale jak działa ładowanie zajęć w czasie wykonywania? W czasie kompilacji jest to łatwe do zrozumienia. Ale jak to działa w czasie wykonywania w przypadku, gdy mam dwa słoiki w różnych wersjach? Który wybierze?
Kunal
1
Jestem prawie pewien, że domyślny program ładujący klasy pobiera ścieżkę klas i przechodzi przez nią po kolei, więc jeśli masz dwa słoiki w ścieżce klas, które zawierają tę samą klasę (np. Com.example.fooutils.Foo), użyje tej, która jest pierwszy w ścieżce klas. Albo to, albo otrzymasz błąd stwierdzający niejednoznaczność. Ale jeśli chcesz uzyskać więcej informacji dotyczących programów ładujących klasy, zadaj osobne pytanie.
Jason S
Myślę, że w pierwszym przypadku zależności czasu kompilacji powinny również istnieć na L2, tj. Zdanie powinno brzmieć: 1) jeśli twoja klasa C1 wywołuje klasę biblioteki L1, a L1 wywołuje klasę biblioteki L2, to C1 ma zależność wykonawczą od L1 i L2, ale tylko zależność czasu kompilacji od L1 i L2. Dzieje się tak, ponieważ w czasie kompilacji również, gdy kompilator java weryfikuje L1, weryfikuje również wszystkie inne klasy, do których odwołuje się L1 (z wyłączeniem dynamicznych zależności, takich jak Class.forName ("myklassname)) ... kompilacja działa dobrze. Proszę wyjaśnij, jeśli myślisz inaczej
Rajesh Goel
1
Nie. Musisz przeczytać, jak działa kompilacja i łączenie w Javie. Kompilatorowi zależy tylko na tym, kiedy odwołuje się do klasy zewnętrznej, jak z niej korzystać , np. Jakie są jej metody i pola. Nie obchodzi go, co faktycznie dzieje się w metodach tej klasy zewnętrznej. Jeśli L1 wywołuje L2, jest to szczegół implementacji L1, a L1 został już skompilowany w innym miejscu.
Jason S,
12

W rzeczywistości Java nie łączy niczego w czasie kompilacji. Weryfikuje tylko składnię przy użyciu pasujących klas, które znajduje w CLASSPATH. Dopiero w czasie wykonywania wszystko zostaje złożone i wykonane w oparciu o CLASSPATH w tym czasie.

JOTN
źródło
Dopiero czas ładowania ... środowisko wykonawcze różni się od czasu ładowania.
przewyższenie
10

Zależności czasu kompilacji to tylko zależności (inne klasy), których używasz bezpośrednio w kompilowanej klasie. Zależności czasu wykonywania obejmują zarówno bezpośrednie, jak i pośrednie zależności klasy, którą uruchamiasz. W związku z tym zależności środowiska uruchomieniowego obejmują zależności zależności i wszelkie zależności odbicia, takie jak nazwy klas, które masz w pliku String, ale są używane w Class#forName().

BalusC
źródło
Dzięki, ale jak działa ładowanie zajęć w czasie wykonywania? W czasie kompilacji jest to łatwe do zrozumienia. Ale jak to działa w czasie wykonywania, gdy mam dwa słoiki w różnych wersjach? Która klasa zostanie odebrana przez Class.forName () w przypadku wielu klas różnych klas w ścieżce klas?
Kunal
Oczywiście ten pasujący do nazwy. Jeśli faktycznie masz na myśli „wiele wersji tej samej klasy”, to zależy to od modułu ładującego klasy. Wczytany zostanie „najbliższy”.
BalusC
Cóż, myślę, że jeśli masz A.jar with A, B.jar with B extends Ai C.jar with, C extends Bto C.jar zależy od czasu kompilacji na A.jar, mimo że zależność C od A jest pośrednia.
gpeche
1
Problemem we wszystkich zależnościach w czasie kompilacji jest zależność interfejsu (niezależnie od tego, czy interfejs jest za pośrednictwem metod klasy, czy za pośrednictwem metod interfejsu, czy też za pośrednictwem metody zawierającej argument będący klasą lub interfejsem)
Jason S
2

W przypadku języka Java zależność od czasu kompilacji to zależność kodu źródłowego. Na przykład, jeśli klasa A wywołuje metodę z klasy B, to A jest zależne od B w czasie kompilacji, ponieważ A musi wiedzieć o B (typ B) do skompilowania. Sztuczka powinna być taka: skompilowany kod nie jest jeszcze kompletnym i wykonywalnym kodem. Zawiera wymienne adresy (symbole, metadane) dla źródeł, które nie zostały jeszcze skompilowane lub nie istnieją w zewnętrznych jarach. Podczas linkowania adresy te należy zastąpić aktualnymi adresami w pamięci. Aby to zrobić poprawnie należy stworzyć poprawne symbole / adresy. Można to zrobić z typem klasy (B). Uważam, że jest to główna zależność w czasie kompilacji.

Zależność środowiska wykonawczego jest bardziej związana z rzeczywistym przepływem kontroli. Uwzględnia rzeczywiste adresy pamięci. Jest to zależność, która występuje, gdy program jest uruchomiony. Potrzebujesz tutaj szczegółów klasy B, takich jak implementacje, a nie tylko informacji o typie. Jeśli klasa nie istnieje, otrzymasz RuntimeException i JVM zakończy działanie.

Obie zależności, ogólnie i nie powinny, płynąć w tym samym kierunku. Jest to jednak kwestia projektu OO.

W C ++ kompilacja jest nieco inna (nie tylko w czasie), ale ma też konsolidator. Myślę, że proces można by pomyśleć podobnie jak w Javie.

stdout
źródło