Thời gian biên dịch so với thời gian chạy Phụ thuộc - Java


90

Sự khác biệt giữa thời gian biên dịch và thời gian chạy phụ thuộc trong Java là gì? Nó liên quan đến đường dẫn lớp, nhưng chúng khác nhau như thế nào?

Câu trả lời:


78
  • Phụ thuộc thời gian biên dịch : Bạn cần phụ thuộc trong của mình CLASSPATHđể biên dịch tạo tác của bạn. Chúng được tạo ra bởi vì bạn có một số loại "tham chiếu" đến phần phụ thuộc được mã hóa cứng trong mã của bạn, chẳng hạn như gọi newcho một số lớp, mở rộng hoặc triển khai một cái gì đó (trực tiếp hoặc gián tiếp) hoặc một lệnh gọi phương thức sử dụng reference.method()ký hiệu trực tiếp .

  • Sự phụ thuộc vào thời gian chạy : Bạn cần sự phụ thuộc trong của mình CLASSPATHđể chạy phần mềm của bạn. Chúng được tạo ra bởi vì bạn thực thi mã truy cập phần phụ thuộc (theo cách được mã hóa cứng hoặc thông qua phản chiếu hoặc bất cứ điều gì).

Mặc dù phụ thuộc thời gian biên dịch thường ngụ ý phụ thuộc thời gian chạy, nhưng bạn có thể có một phụ thuộc chỉ thời gian biên dịch. Điều này dựa trên thực tế là Java chỉ liên kết các lớp phụ thuộc vào lần truy cập đầu tiên vào lớp đó, vì vậy nếu bạn không bao giờ truy cập một lớp cụ thể tại thời điểm chạy vì một đường dẫn mã không bao giờ được duyệt qua, Java sẽ bỏ qua cả lớp và các phụ thuộc của nó.

Ví dụ về điều này

Trong C.java (tạo C.class):

package dependencies;
public class C { }

Trong A.java (tạo 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());
        }
    }
}

Trong trường hợp này, Acó phụ thuộc thời gian biên dịch vào Cthông qua B, nhưng nó sẽ chỉ có phụ thuộc thời gian chạy vào C nếu bạn truyền một số tham số khi thực thi java dependencies.A, vì JVM sẽ chỉ cố gắng giải quyết Bphụ thuộc của Ckhi nào nó được thực thi. B b = new B(). Tính năng này cho phép bạn chỉ cung cấp trong thời gian chạy các phần phụ thuộc của các lớp mà bạn sử dụng trong các đường dẫn mã của mình và bỏ qua các phần phụ thuộc của phần còn lại của các lớp trong tạo tác.


1
Tôi biết đây là một câu trả lời rất cũ, nhưng làm thế nào để JVM không có C làm phụ thuộc thời gian chạy ngay từ đầu? Nếu nó có thể nhận ra "đây là một tham chiếu đến C, đã đến lúc thêm nó làm phụ thuộc", thì C về cơ bản đã không phải là một phụ thuộc vì JVM nhận ra nó và biết nó ở đâu?
Wearebob

@wearebob Nó có thể được chỉ định theo cách tôi đoán, nhưng họ quyết định rằng liên kết lười biếng là tốt hơn và cá nhân tôi đồng ý vì lý do đã nêu ở trên: nó cho phép bạn sử dụng một số mã nếu cần thiết, nhưng không buộc bạn phải đưa nó vào triển khai của bạn nếu bạn không cần nó. Điều đó khá tiện lợi khi xử lý mã của bên thứ ba.
gpeche

Nếu tôi có một jar được triển khai ở đâu đó, thì nó sẽ phải chứa tất cả các phụ thuộc của nó. Nó không biết liệu nó có được chạy với các đối số hay không (vì vậy nó không biết liệu C có được sử dụng hay không), vì vậy nó sẽ phải có C khả dụng theo cả hai cách. Tôi chỉ không thấy làm thế nào bất kỳ bộ nhớ / thời gian nào được lưu bằng cách không có C trên classpath ngay từ đầu.
Wearebob

1
@wearebob một JAR không cần phải bao gồm tất cả các phụ thuộc của nó. Đó là lý do tại sao hầu hết mọi ứng dụng không tầm thường đều có thư mục / lib hoặc thư mục tương tự chứa nhiều JAR.
gpeche

33

Một ví dụ đơn giản là xem một api giống như api servlet. Để biên dịch các servlet của bạn, bạn cần có servlet-api.jar, nhưng trong thời gian chạy, vùng chứa servlet cung cấp một triển khai api servlet nên bạn không cần thêm servlet-api.jar vào đường dẫn lớp thời gian chạy của mình.


Để làm rõ (điều này làm tôi bối rối), nếu bạn đang sử dụng maven và xây dựng một cuộc chiến, "servlet-api" thường là một phụ thuộc "được cung cấp" thay vì phụ thuộc "thời gian chạy", điều này sẽ khiến nó được đưa vào chiến tranh, nếu Tôi đúng.
xdhmoore 14/09/2016

2
'cung cấp' có nghĩa là, bao gồm tại thời điểm biên dịch, nhưng không gói nó trong WAR hoặc bộ sưu tập phụ thuộc khác. 'runtime' làm ngược lại (không có sẵn khi biên dịch, nhưng được đóng gói với WAR).
KC Baltz

29

Trình biên dịch cần classpath phù hợp để biên dịch các cuộc gọi đến thư viện (biên dịch phụ thuộc thời gian)

JVM cần classpath phù hợp để tải các lớp trong thư viện mà bạn đang gọi (phụ thuộc thời gian chạy).

Chúng có thể khác nhau theo một số cách:

1) nếu lớp C1 của bạn gọi lớp thư viện L1 và L1 gọi lớp thư viện L2, thì C1 có phụ thuộc thời gian chạy vào L1 và L2, nhưng chỉ phụ thuộc thời gian biên dịch vào L1.

2) nếu lớp C1 của bạn khởi tạo động giao diện I1 bằng cách sử dụng Class.forName () hoặc một số cơ chế khác và lớp triển khai cho giao diện I1 là lớp L1, thì C1 có phụ thuộc thời gian chạy vào I1 và L1, nhưng chỉ phụ thuộc thời gian biên dịch trên I1.

Các phụ thuộc "gián tiếp" khác giống nhau về thời gian biên dịch và thời gian chạy:

3) lớp C1 của bạn mở rộng lớp thư viện L1 và L1 triển khai giao diện I1 và mở rộng lớp thư viện L2: C1 có phụ thuộc thời gian biên dịch vào L1, L2 và I1.

4) lớp C1 của bạn có một phương thức foo(I1 i1)và một phương thức bar(L1 l1)trong đó I1 là giao diện và L1 là lớp nhận tham số là giao diện I1: C1 có phụ thuộc thời gian biên dịch vào I1 và L1.

Về cơ bản, để làm bất cứ điều gì thú vị, lớp của bạn cần phải giao tiếp với các lớp và giao diện khác trong classpath. Biểu đồ lớp / giao diện được hình thành bởi tập hợp các giao diện thư viện đó mang lại chuỗi phụ thuộc thời gian biên dịch. Việc triển khai thư viện mang lại chuỗi phụ thuộc thời gian chạy. Lưu ý rằng chuỗi phụ thuộc thời gian chạy phụ thuộc vào thời gian chạy hoặc chạy chậm: nếu việc triển khai L1 đôi khi phụ thuộc vào việc khởi tạo một đối tượng của lớp L2 và lớp đó chỉ được khởi tạo trong một kịch bản cụ thể, thì không có phụ thuộc nào ngoại trừ trong kịch bản đó.


1
Không nên phụ thuộc thời gian biên dịch trong ví dụ 1 là L1?
BalusC

Cảm ơn, nhưng làm thế nào để tải lớp hoạt động tại thời điểm chạy? Tại thời điểm biên dịch nó rất dễ hiểu. Nhưng trong thời gian chạy, nó hoạt động như thế nào, trong trường hợp tôi có hai Lọ phiên bản khác nhau? Nó sẽ chọn cái nào?
Kunal

1
Tôi khá chắc chắn rằng trình nạp lớp mặc định lấy classpath và thực hiện các bước theo thứ tự, vì vậy nếu bạn có hai lọ trong classpath mà cả hai đều chứa cùng một lớp (ví dụ: com.example.fooutils.Foo), thì nó sẽ sử dụng cái đó. là đầu tiên trong classpath. Hoặc điều đó hoặc bạn sẽ gặp lỗi nêu rõ sự không rõ ràng. Nhưng nếu bạn muốn biết thêm thông tin cụ thể về trình tải lớp, bạn nên đặt một câu hỏi riêng.
Jason S

Tôi nghĩ trong trường hợp đầu tiên, các phụ thuộc thời gian biên dịch cũng phải ở đó trên L2, tức là câu phải là: 1) nếu lớp C1 của bạn gọi lớp thư viện L1 và L1 gọi lớp thư viện L2, thì C1 có phụ thuộc thời gian chạy vào L1 và L2, nhưng chỉ phụ thuộc thời gian biên dịch vào L1 & L2. Điều này là như vậy, vì tại thời điểm biên dịch cũng như khi trình biên dịch java xác minh L1, sau đó nó cũng xác minh tất cả các lớp khác được tham chiếu bởi L1 (không bao gồm các phụ thuộc động như Class.forName ("myclassname)) ... nếu không thì làm thế nào nó xác minh điều đó quá trình biên dịch đang hoạt động tốt. Vui lòng giải thích nếu bạn nghĩ khác
Rajesh Goel

1
Không. Bạn cần đọc về cách thức hoạt động của quá trình biên dịch và liên kết trong Java. Tất cả những gì trình biên dịch quan tâm, khi nó đề cập đến một lớp bên ngoài, là cách sử dụng lớp đó, ví dụ như các phương thức và trường của nó là gì. Nó không quan tâm những gì thực sự xảy ra trong các phương thức của lớp bên ngoài đó. Nếu L1 gọi L2, đó là chi tiết triển khai của L1 và L1 đã được biên dịch ở nơi khác.
Jason S

12

Java không thực sự liên kết bất cứ thứ gì tại thời điểm biên dịch. Nó chỉ xác minh cú pháp bằng cách sử dụng các lớp phù hợp mà nó tìm thấy trong CLASSPATH. Phải đến thời gian chạy, mọi thứ mới được tập hợp lại và thực thi dựa trên CLASSPATH tại thời điểm đó.


Không cho đến khi thời gian tải ... thời gian chạy khác với thời gian tải.
trao đổi quá mức

10

Các phụ thuộc thời gian biên dịch chỉ là các phụ thuộc (các lớp khác) mà bạn sử dụng trực tiếp trong lớp mà bạn đang biên dịch. Phụ thuộc thời gian chạy bao gồm cả phụ thuộc trực tiếp và gián tiếp của lớp bạn đang chạy. Do đó, các phụ thuộc thời gian chạy bao gồm các phụ thuộc của phụ thuộc và bất kỳ phụ thuộc phản ánh nào như tên lớp mà bạn có trong a String, nhưng được sử dụng trong đó Class#forName().


Cảm ơn, nhưng làm thế nào để tải lớp hoạt động tại thời điểm chạy? Tại thời điểm biên dịch nó rất dễ hiểu. Nhưng trong thời gian chạy, nó hoạt động như thế nào, trong trường hợp tôi có hai Lọ phiên bản khác nhau? Class.forName () sẽ nhận lớp nào trong trường hợp có nhiều lớp thuộc các lớp khác nhau trong một đường dẫn lớp?
Kunal

Tất nhiên là một trong những phù hợp với tên. Nếu bạn thực sự có nghĩa là "nhiều phiên bản của cùng một lớp", thì nó phụ thuộc vào trình tải lớp. Cái "gần nhất" sẽ được tải.
BalusC

Tôi nghĩ nếu bạn có A.jar với A, B.jar với B extends Avà C.jar với C extends Bthì C.jar phụ thuộc vào thời gian biên dịch trên A.jar mặc dù sự phụ thuộc của C vào A là gián tiếp.
gpeche

1
Vấn đề ở tất cả phụ thuộc thời gian biên dịch là giao diện phụ thuộc (cho dù giao diện là thông qua phương pháp của một lớp, hoặc thông qua các phương pháp của một giao diện, hoặc thông qua một phương pháp, trong đó có một cuộc tranh cãi đó là một lớp hoặc giao diện)
Jason S

2

Đối với Java, phụ thuộc thời gian biên dịch là phụ thuộc mã nguồn của bạn. Ví dụ: nếu lớp A gọi một phương thức từ lớp B, thì A phụ thuộc vào B tại thời điểm biên dịch vì A phải biết về B (kiểu B) được biên dịch. Bí quyết ở đây là: Mã đã biên dịch chưa phải là mã hoàn chỉnh và có thể thực thi được. Nó bao gồm các địa chỉ có thể thay thế (ký hiệu, siêu dữ liệu) cho các nguồn chưa được biên dịch hoặc tồn tại trong các lọ bên ngoài. Trong quá trình liên kết, các địa chỉ đó phải được thay thế bằng các địa chỉ thực trong bộ nhớ. Để thực hiện đúng, cần tạo các ký hiệu / địa chỉ chính xác. Và điều này có thể được thực hiện với kiểu của lớp (B). Tôi tin rằng đó là sự phụ thuộc chính tại thời điểm biên dịch.

Sự phụ thuộc thời gian chạy liên quan nhiều hơn đến luồng điều khiển thực tế. Nó xâm nhập địa chỉ bộ nhớ thực tế. Đó là sự phụ thuộc mà bạn có khi chương trình của bạn đang chạy. Bạn cần chi tiết lớp B ở đây như triển khai, không chỉ thông tin loại. Nếu lớp không tồn tại, thì bạn sẽ nhận được RuntimeException và JVM sẽ thoát.

Cả hai phần phụ thuộc, nói chung và không nên, chảy theo cùng một hướng. Đây là một vấn đề của thiết kế OO.

Trong C ++, việc biên dịch hơi khác một chút (không phải chỉ trong thời gian) nhưng nó cũng có một trình liên kết. Vì vậy, tôi đoán quá trình này có thể tương tự như Java.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.