Kompilasi waktu vs Ketergantungan waktu proses - Java

91

Apa perbedaan antara waktu kompilasi dan dependensi run time di Java? Ini terkait dengan jalur kelas, tetapi apa perbedaannya?

Kunal
sumber

Jawaban:

78
  • Ketergantungan waktu kompilasi : Anda memerlukan ketergantungan di Anda CLASSPATHuntuk mengompilasi artefak Anda. Mereka dibuat karena Anda memiliki semacam "referensi" ke dependensi yang di-hardcode dalam kode Anda, seperti memanggil newbeberapa kelas, memperluas atau mengimplementasikan sesuatu (baik secara langsung atau tidak langsung), atau pemanggilan metode menggunakan reference.method()notasi langsung .

  • Ketergantungan run-time : Anda membutuhkan ketergantungan di Anda CLASSPATHuntuk menjalankan artefak Anda. Mereka diproduksi karena Anda menjalankan kode yang mengakses dependensi (baik dengan cara hardcode atau melalui refleksi atau apa pun).

Meskipun ketergantungan waktu kompilasi biasanya menyiratkan ketergantungan waktu proses, Anda dapat memiliki ketergantungan hanya waktu kompilasi. Hal ini didasarkan pada fakta bahwa Java hanya menautkan dependensi kelas pada akses pertama ke kelas tersebut, jadi jika Anda tidak pernah mengakses kelas tertentu pada waktu proses karena jalur kode tidak pernah dilalui, Java akan mengabaikan kelas dan dependensinya.

Contoh ini

Dalam C. java (menghasilkan C.class):

package dependencies;
public class C { }

Dalam A. java (menghasilkan kelas 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());
        }
    }
}

Dalam kasus ini, Amemiliki ketergantungan waktu kompilasi pada Cthrough B, tetapi itu hanya akan memiliki ketergantungan waktu proses pada C jika Anda melewatkan beberapa parameter saat mengeksekusi java dependencies.A, karena JVM hanya akan mencoba untuk menyelesaikan Bketergantungan pada Csaat itu akan dieksekusi B b = new B(). Fitur ini memungkinkan Anda untuk menyediakan pada waktu proses hanya dependensi class yang Anda gunakan di jalur kode, dan mengabaikan dependensi class lainnya dalam artefak.

gpeche.dll
sumber
1
Saya tahu ini sekarang merupakan jawaban yang sangat lama, tetapi bagaimana JVM tidak memiliki C sebagai ketergantungan runtime dari awal? Jika ia dapat mengenali "inilah referensi ke C, saatnya menambahkannya sebagai dependensi", bukankah C pada dasarnya sudah menjadi dependensi karena JVM mengenalinya dan tahu di mana itu?
wearebob
@wearebob Saya kira bisa ditentukan seperti itu, tetapi mereka memutuskan bahwa penautan malas lebih baik, dan secara pribadi saya setuju untuk alasan yang disebutkan di atas: ini memungkinkan Anda menggunakan beberapa kode jika perlu, tetapi tidak memaksa Anda untuk memasukkannya ke dalam penerapan Anda jika Anda tidak membutuhkannya. Itu sangat berguna saat berurusan dengan kode pihak ketiga.
gpeche
Jika saya memiliki toples yang ditempatkan di suatu tempat, itu sudah harus berisi semua dependensinya. Ia tidak tahu apakah ia akan dijalankan dengan argumen atau tidak (jadi ia tidak tahu apakah C akan digunakan atau tidak), jadi ia harus memiliki C yang tersedia. Saya hanya tidak melihat bagaimana memori / waktu disimpan dengan tidak memiliki C di classpath sejak awal.
wearebob
1
@wearebob, JAR tidak perlu menyertakan semua dependensinya. Itu sebabnya hampir setiap aplikasi non-sepele memiliki direktori / lib atau sejenisnya yang berisi beberapa JAR.
gpeche
33

Contoh mudahnya adalah dengan melihat api seperti servlet api. Untuk membuat servlet Anda dikompilasi, Anda memerlukan servlet-api.jar, tetapi pada saat runtime container servlet menyediakan implementasi servlet api sehingga Anda tidak perlu menambahkan servlet-api.jar ke jalur kelas runtime Anda.

Martin Algesten
sumber
Untuk klarifikasi (ini membingungkan saya), jika Anda menggunakan maven dan membangun perang, "servlet-api" biasanya merupakan ketergantungan "disediakan", bukan ketergantungan "waktu proses", yang akan menyebabkannya disertakan dalam perang, jika Saya benar.
xdhmoore
2
'disediakan' artinya, termasuk pada waktu kompilasi, tetapi jangan memaketkannya dalam WAR atau kumpulan dependensi lainnya. 'runtime' melakukan yang sebaliknya (tidak tersedia pada kompilasi, tetapi dikemas dengan WAR).
KC Baltz
29

Kompiler memerlukan classpath yang benar untuk mengompilasi panggilan ke perpustakaan (mengompilasi dependensi waktu)

JVM memerlukan jalur kelas yang benar untuk memuat kelas di perpustakaan yang Anda panggil (dependensi waktu proses).

Mereka mungkin berbeda dalam beberapa hal:

1) jika kelas C1 Anda memanggil kelas perpustakaan L1, dan L1 memanggil kelas perpustakaan L2, maka C1 memiliki ketergantungan waktu proses pada L1 dan L2, tetapi hanya ketergantungan waktu kompilasi pada L1.

2) jika kelas C1 Anda secara dinamis membuat instance antarmuka I1 menggunakan Class.forName () atau mekanisme lain, dan kelas implementasi untuk antarmuka I1 adalah kelas L1, maka C1 memiliki ketergantungan waktu proses pada I1 dan L1, tetapi hanya ketergantungan waktu kompilasi di I1.

Dependensi "tidak langsung" lainnya yang sama untuk waktu kompilasi dan waktu proses:

3) kelas C1 Anda memperluas kelas perpustakaan L1, dan L1 mengimplementasikan antarmuka I1 dan memperluas kelas perpustakaan L2: C1 memiliki ketergantungan waktu kompilasi pada L1, L2, dan I1.

4) kelas C1 Anda memiliki metode foo(I1 i1)dan metode di bar(L1 l1)mana I1 adalah antarmuka dan L1 adalah kelas yang mengambil parameter yaitu antarmuka I1: C1 memiliki ketergantungan waktu kompilasi pada I1 dan L1.

Pada dasarnya, untuk melakukan sesuatu yang menarik, kelas Anda perlu berinteraksi dengan kelas dan antarmuka lain di classpath. Grafik kelas / antarmuka yang dibentuk oleh kumpulan antarmuka perpustakaan tersebut menghasilkan rantai ketergantungan waktu kompilasi. Implementasi library menghasilkan rantai dependensi run-time. Perhatikan bahwa rantai ketergantungan waktu proses bergantung pada waktu proses atau gagal-lambat: jika penerapan L1 terkadang bergantung pada pembuatan instance objek kelas L2, dan kelas tersebut hanya dibuat instance-nya dalam satu skenario tertentu, maka tidak ada ketergantungan kecuali di skenario itu.

Jason S
sumber
1
Bukankah seharusnya ketergantungan waktu kompilasi pada contoh 1 menjadi L1?
BalusC
Terima kasih, tapi bagaimana cara kerja pemuatan kelas pada saat berjalan? Pada waktu kompilasi mudah dimengerti. Tetapi pada waktu proses, bagaimana cara kerjanya, jika saya memiliki dua Jars dengan versi yang berbeda? Yang mana yang akan dipilihnya?
Kunal
1
Saya cukup yakin classloader default mengambil classpath dan melangkah melaluinya secara berurutan, jadi jika Anda memiliki dua jar di classpath yang keduanya berisi kelas yang sama (misalnya com.example.fooutils.Foo), itu akan menggunakan salah satu yang adalah yang pertama di classpath. Entah itu atau Anda akan mendapatkan pesan kesalahan yang menyatakan ambiguitas. Tetapi jika Anda ingin info lebih khusus tentang pemuat kelas, Anda harus mengajukan pertanyaan terpisah.
Jason S
Saya pikir dalam kasus pertama, ketergantungan waktu kompilasi juga harus ada di L2 yaitu kalimatnya harus: 1) jika kelas C1 Anda memanggil kelas perpustakaan L1, dan L1 memanggil kelas perpustakaan L2, maka C1 memiliki ketergantungan runtime pada L1 dan L2, tetapi hanya ketergantungan waktu kompilasi pada L1 & L2. Begitulah, seperti pada waktu kompilasi juga ketika compiler java memverifikasi L1, kemudian ia juga memverifikasi semua kelas lain yang dirujuk oleh L1 (tidak termasuk dependensi dinamis seperti Class.forName ("myclassname)) ... jika tidak, bagaimana cara memverifikasi itu kompilasi bekerja dengan baik. Tolong jelaskan jika Anda berpikir sebaliknya
Rajesh Goel
1
Tidak. Anda perlu membaca tentang cara kerja kompilasi dan linkage di Java. Semua yang dipedulikan oleh compiler, ketika merujuk ke kelas eksternal, adalah bagaimana menggunakan kelas itu, misalnya apa metode dan bidangnya. Itu tidak peduli apa yang sebenarnya terjadi dalam metode kelas eksternal itu. Jika L1 memanggil L2, itu adalah detail implementasi L1, dan L1 telah dikompilasi di tempat lain.
Jason S
12

Java sebenarnya tidak menautkan apa pun pada waktu kompilasi. Ini hanya memverifikasi sintaks menggunakan kelas yang cocok yang ditemukannya di CLASSPATH. Tidak sampai waktu proses semuanya disatukan dan dijalankan berdasarkan CLASSPATH pada saat itu.

JOTN
sumber
Ini tidak sampai waktu muat ... runtime berbeda dari waktu muat.
pertukaran berlebih pada
10

Dependensi waktu kompilasi hanyalah dependensi (kelas lain) yang Anda gunakan secara langsung di kelas yang Anda kompilasi. Dependensi waktu proses mencakup dependensi langsung dan tidak langsung dari kelas yang Anda jalankan. Jadi, dependensi waktu proses menyertakan dependensi dependensi dan dependensi refleksi apa pun seperti nama kelas yang Anda miliki di String, tetapi digunakan di Class#forName().

BalusC
sumber
Terima kasih, tapi bagaimana cara kerja pemuatan kelas pada saat berjalan? Pada waktu kompilasi mudah dimengerti. Tetapi pada waktu proses, bagaimana cara kerjanya, jika saya memiliki dua Jars dengan versi yang berbeda? Kelas mana yang akan diambil oleh Class.forName () jika ada beberapa kelas dari kelas yang berbeda dalam jalur kelas?
Kunal
Yang cocok dengan namanya tentunya. Jika Anda benar-benar berarti "beberapa versi kelas yang sama", maka itu tergantung pada classloader tersebut. Yang "paling dekat" akan dimuat.
BalusC
Nah menurut saya jika anda memiliki A.jar dengan A, B.jar dengan B extends Adan C.jar dengan C extends Bmaka C.jar tergantung waktu kompilasi pada A.jar meskipun ketergantungan C pada A tidak langsung.
gpeche
1
Masalah dalam semua dependensi waktu kompilasi adalah ketergantungan antarmuka (apakah antarmuka melalui metode kelas, atau melalui metode antarmuka, atau melalui metode yang berisi argumen yang merupakan kelas atau antarmuka)
Jason S
2

Untuk Java, ketergantungan waktu kompilasi adalah ketergantungan kode sumber Anda. Misalnya, jika kelas A memanggil metode dari kelas B, maka A bergantung ke B pada waktu kompilasi karena A harus mengetahui tentang B (tipe B) yang akan dikompilasi. Triknya di sini seharusnya seperti ini: Kode yang dikompilasi belum menjadi kode yang lengkap dan dapat dieksekusi. Ini termasuk alamat yang dapat diganti (simbol, metadata) untuk sumber yang belum dikompilasi atau ada di toples eksternal. Selama penautan, alamat tersebut harus diganti dengan alamat sebenarnya di memori. Untuk melakukannya dengan benar, simbol / alamat yang benar harus dibuat. Dan ini bisa dilakukan dengan tipe kelas (B). Saya yakin itulah ketergantungan utama pada waktu kompilasi.

Ketergantungan runtime lebih terkait dengan flow-of-control yang sebenarnya. Ini meminta alamat memori yang sebenarnya. Ini adalah ketergantungan yang Anda miliki saat program Anda berjalan. Anda memerlukan detail kelas B di sini seperti implementasi, bukan hanya info jenis. Jika kelas tidak ada, maka Anda akan mendapatkan RuntimeException dan JVM akan keluar.

Kedua dependensi, secara umum dan tidak seharusnya, mengalir ke arah yang sama. Ini adalah masalah desain OO.

Di C ++, kompilasi sedikit berbeda (tidak hanya dalam waktu) tetapi juga memiliki linker. Jadi prosesnya mungkin dianggap mirip dengan Java.

stdout
sumber