Khi nào một giao diện với một phương thức mặc định được khởi tạo?


94

Trong khi tìm kiếm thông qua Đặc tả ngôn ngữ Java để trả lời câu hỏi này , tôi đã học được rằng

Trước khi một lớp được khởi tạo, lớp cha trực tiếp của nó phải được khởi tạo, nhưng các giao diện do lớp thực hiện sẽ không được khởi tạo. Tương tự, các siêu giao diện của một giao diện không được khởi tạo trước khi giao diện được khởi tạo.

Vì sự tò mò của riêng tôi, tôi đã thử nó và như mong đợi, giao diện InterfaceTypekhông được khởi tạo.

public class Example {
    public static void main(String[] args) throws Exception {
        InterfaceType foo = new InterfaceTypeImpl();
        foo.method();
    }
}

class InterfaceTypeImpl implements InterfaceType {
    @Override
    public void method() {
        System.out.println("implemented method");
    }
}

class ClassInitializer {
    static {
        System.out.println("static initializer");
    }
}

interface InterfaceType {
    public static final ClassInitializer init = new ClassInitializer();

    public void method();
}

Chương trình này in

implemented method

Tuy nhiên, nếu giao diện khai báo một defaultphương thức, thì quá trình khởi tạo sẽ xảy ra. Hãy xem xét InterfaceTypegiao diện được đưa ra là

interface InterfaceType {
    public static final ClassInitializer init = new ClassInitializer();

    public default void method() {
        System.out.println("default method");
    }
}

thì chương trình tương tự ở trên sẽ in

static initializer  
implemented method

Nói cách khác, statictrường của giao diện được khởi tạo ( bước 9 trong Quy trình khởi tạo chi tiết ) và trình statickhởi tạo của kiểu đang được khởi tạo được thực thi. Điều này có nghĩa là giao diện đã được khởi tạo.

Tôi không thể tìm thấy bất kỳ điều gì trong JLS để chỉ ra rằng điều này sẽ xảy ra. Đừng hiểu sai ý tôi, tôi hiểu rằng điều này sẽ xảy ra trong trường hợp lớp thực thi không cung cấp triển khai cho phương thức, nhưng nếu nó xảy ra thì sao? Điều kiện này có bị thiếu trong Đặc tả ngôn ngữ Java không, tôi có bỏ sót điều gì không, hay tôi diễn giải sai?


4
Tôi đoán sẽ là - các giao diện như vậy được coi là các lớp trừu tượng về thứ tự khởi tạo. Tôi đã viết điều này dưới dạng nhận xét vì tôi không chắc liệu đây có phải là tuyên bố chính xác hay không :)
Alexey Malev

Nó phải nằm trong phần 12.4 của JLS, nhưng dường như không có ở đó. Tôi muốn nói rằng nó bị thiếu.
Warren Dew

1
Đừng bận tâm .... hầu hết khi họ không hiểu hoặc không có lời giải thích, họ sẽ phản đối :(. Điều này thường xảy ra trên SO.
NeverGiveUp161

Tôi nghĩ rằng interfacetrong Java không nên xác định bất kỳ phương thức cụ thể nào. Vì vậy, tôi ngạc nhiên rằng InterfaceTypemã đã được biên dịch.
MaxZoom

Câu trả lời:


85

Đây là một vấn đề rất thú vị!

Có vẻ như phần 12.4.1 của JLS phải đề cập đến vấn đề này một cách dứt khoát. Tuy nhiên, hoạt động của Oracle JDK và OpenJDK (javac và HotSpot) khác với những gì được chỉ định ở đây. Đặc biệt, Ví dụ 12.4.1-3 từ phần này bao gồm việc khởi tạo giao diện. Ví dụ như sau:

interface I {
    int i = 1, ii = Test.out("ii", 2);
}
interface J extends I {
    int j = Test.out("j", 3), jj = Test.out("jj", 4);
}
interface K extends J {
    int k = Test.out("k", 5);
}
class Test {
    public static void main(String[] args) {
        System.out.println(J.i);
        System.out.println(K.j);
    }
    static int out(String s, int i) {
        System.out.println(s + "=" + i);
        return i;
    }
}

Sản lượng dự kiến ​​của nó là:

1
j=3
jj=4
3

và thực sự tôi nhận được kết quả như mong đợi. Tuy nhiên, nếu một phương thức mặc định được thêm vào giao diện I,

interface I {
    int i = 1, ii = Test.out("ii", 2);
    default void method() { } // causes initialization!
}

đầu ra thay đổi thành:

1
ii=2
j=3
jj=4
3

điều này chỉ ra rõ ràng rằng giao diện Iđang được khởi tạo so với trước đây! Chỉ sự hiện diện của phương thức mặc định là đủ để kích hoạt quá trình khởi tạo. Phương thức mặc định không cần phải được gọi hoặc ghi đè hoặc thậm chí được đề cập, cũng như sự hiện diện của một phương thức trừu tượng sẽ kích hoạt khởi tạo.

Suy đoán của tôi là việc triển khai HotSpot muốn tránh thêm việc kiểm tra khởi tạo lớp / giao diện vào đường dẫn quan trọng của invokevirtualcuộc gọi. Trước Java 8 và các phương thức mặc định, invokevirtualkhông bao giờ có thể kết thúc việc thực thi mã trong một giao diện, vì vậy điều này không phát sinh. Người ta có thể nghĩ đây là một phần của giai đoạn chuẩn bị lớp / giao diện ( JLS 12.3.2 ) khởi tạo những thứ như bảng phương thức. Nhưng có lẽ điều này đã đi quá xa và thay vào đó đã vô tình khởi tạo đầy đủ.

Tôi đã đưa ra câu hỏi này trong danh sách gửi thư của trình biên dịch OpenJDK. Đã có một câu trả lời từ Alex Buckley (biên tập viên của JLS), trong đó anh ấy đặt ra nhiều câu hỏi hơn hướng đến nhóm triển khai JVM và lambda. Anh ấy cũng lưu ý rằng có một lỗi trong thông số kỹ thuật ở đây khi nó nói "T là một lớp và một phương thức tĩnh do T khai báo được gọi ra" cũng nên áp dụng nếu T là một giao diện. Vì vậy, có thể có cả lỗi đặc tả và lỗi HotSpot ở đây.

Tiết lộ : Tôi làm việc cho Oracle trên OpenJDK. Nếu mọi người nghĩ rằng điều này mang lại cho tôi một lợi thế không công bằng khi nhận được tiền thưởng kèm theo câu hỏi này, tôi sẵn sàng linh hoạt về nó.


6
Tôi đã hỏi các nguồn chính thức. Tôi không nghĩ nó trở nên chính thức hơn thế này. Hãy cho nó hai ngày để xem tất cả các diễn biến.
Sotirios Delimanolis

48
@StuartMarks " Nếu mọi người nghĩ rằng điều này mang lại cho tôi một lợi thế không công bằng, v.v. " => chúng tôi ở đây để nhận câu trả lời cho các câu hỏi và đây là một câu trả lời hoàn hảo!
assylias

2
Lưu ý phụ: Thông số JVM chứa mô tả tương tự như mô tả của JLS: docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5 Điều này cũng cần được cập nhật .
Marco 13

2
@assylias và Sotirios, cảm ơn bạn đã nhận xét. Họ, cùng với 14 phiếu ủng hộ (tính đến thời điểm viết bài này) về nhận xét của assylias, đã làm giảm bớt lo ngại của tôi về bất kỳ sự không công bằng tiềm ẩn nào.
Stuart Marks

1
@SotiriosDelimanolis Có một vài lỗi có vẻ liên quan, JDK-8043275JDK-8043190 , và chúng được đánh dấu là đã sửa trong 8u40. Tuy nhiên, hành vi có vẻ giống nhau. Cũng có một số thay đổi JVM Spec đan xen với điều này, vì vậy có lẽ cách khắc phục là một cái gì đó khác hơn là "khôi phục lại thứ tự khởi tạo cũ."
Stuart Marks

13

Giao diện không được khởi tạo vì trường hằng số InterfaceType.init, đang được khởi tạo bởi giá trị không hằng số (gọi phương thức), không được sử dụng ở bất kỳ đâu.

Được biết tại thời điểm biên dịch, trường giao diện không đổi không được sử dụng ở bất kỳ đâu và giao diện không chứa bất kỳ phương thức mặc định nào (Trong java-8) nên không cần khởi tạo hoặc tải giao diện.

Giao diện sẽ được khởi tạo trong các trường hợp sau,

  • trường hằng số được sử dụng trong mã của bạn.
  • Giao diện chứa một phương thức mặc định (Java 8)

Trong trường hợp Phương thức Mặc định , Bạn đang triển khai InterfaceType. Vì vậy, If InterfaceTypesẽ chứa bất kỳ phương thức mặc định nào, Nó sẽ được INHERITED (được sử dụng) trong việc triển khai lớp. Và Khởi tạo sẽ có trong hình.

Tuy nhiên, nếu bạn đang truy cập trường giao diện không đổi (được khởi tạo theo cách bình thường), thì không cần khởi tạo giao diện.

Hãy xem xét đoạn mã sau.

public class Example {
    public static void main(String[] args) throws Exception {
        InterfaceType foo = new InterfaceTypeImpl();
        System.out.println(InterfaceType.init);
        foo.method();
    }
}

class InterfaceTypeImpl implements InterfaceType {
    @Override
    public void method() {
        System.out.println("implemented method");
    }
}

class ClassInitializer {
    static {
        System.out.println("static initializer");
    }
}

interface InterfaceType {
    public static final ClassInitializer init = new ClassInitializer();

    public void method();
}

Trong trường hợp trên, Giao diện sẽ được khởi tạo và tải vì bạn đang sử dụng trường InterfaceType.init.

Tôi không đưa ra ví dụ về phương pháp mặc định như bạn đã đưa ra trong câu hỏi của mình.

Đặc tả ngôn ngữ Java và ví dụ được đưa ra trong JLS 12.4.1 (Ví dụ không chứa các phương thức mặc định.)


Tôi không thể tìm thấy JLS cho các phương thức Mặc định, có thể có hai khả năng

  • Người Java đã quên xem xét trường hợp của phương thức mặc định. (Thông số kỹ thuật Doc bug.)
  • Họ chỉ tham khảo các phương thức mặc định như là thành viên không cố định của giao diện. (Nhưng không đề cập ở đâu, lại là lỗi Tài liệu đặc tả.)

Tôi đang tìm tham chiếu cho phương thức mặc định. Trường này chỉ để chứng minh rằng giao diện đã được khởi tạo hay chưa.
Sotirios Delimanolis

@SotiriosDelimanolis Tôi đã đề cập đến lý do trong câu trả lời cho phương thức Mặc định ... nhưng tiếc là không tìm thấy bất kỳ JLS nào cho phương thức mặc định.
Không phải là một lỗi

Thật không may, đó là những gì tôi đang tìm kiếm. Tôi cảm thấy như câu trả lời của bạn chỉ lặp lại những điều tôi đã nêu trong câu hỏi, tức là. rằng một giao diện sẽ được khởi tạo nếu nó chứa một defaultphương thức và một lớp thực thi giao diện được khởi tạo.
Sotirios Delimanolis

Tôi nghĩ rằng mọi người java đã quên xem xét trường hợp của phương thức mặc định, Hoặc họ chỉ coi các phương thức mặc định là thành viên không cố định của giao diện (giả định của tôi, không thể tìm thấy trong bất kỳ tài liệu nào).
Không phải lỗi,

1
@KishanSarsechaGajjar: Ý bạn là gì về trường không cố định trong giao diện? Bất kỳ biến / trường nào trong giao diện là tĩnh cuối cùng theo mặc định.
Lokesh

10

Tệp instanceKlass.cpp từ OpenJDK chứa phương thức khởi tạo InstanceKlass::initialize_impltương ứng với Quy trình khởi tạo chi tiết trong JLS, được tìm thấy tương tự trong phần Khởi tạo trong JVM Spec.

Nó chứa một bước mới không được đề cập trong JLS và không có trong sách JVM được đề cập đến trong mã:

// refer to the JVM book page 47 for description of steps
...

if (this_oop->has_default_methods()) {
  // Step 7.5: initialize any interfaces which have default methods
  for (int i = 0; i < this_oop->local_interfaces()->length(); ++i) {
    Klass* iface = this_oop->local_interfaces()->at(i);
    InstanceKlass* ik = InstanceKlass::cast(iface);
    if (ik->has_default_methods() && ik->should_be_initialized()) {
      ik->initialize(THREAD);
    ....
    }
  }
}

Vì vậy, việc khởi tạo này đã được thực hiện một cách rõ ràng như một Bước 7.5 mới . Điều này cho thấy rằng việc triển khai này tuân theo một số thông số kỹ thuật, nhưng có vẻ như thông số kỹ thuật bằng văn bản trên trang web đã không được cập nhật tương ứng.

CHỈNH SỬA: Như một tài liệu tham khảo, cam kết (từ tháng 10 năm 2012!) Trong đó bước tương ứng đã được đưa vào triển khai: http://hg.openjdk.java.net/jdk8/build/hotspot/rev/4735d2c84362

EDIT2: Thật trùng hợp, tôi đã tìm thấy Tài liệu này về các phương pháp mặc định trong điểm phát sóng có chứa một ghi chú thú vị ở cuối:

3.7 Điều khoản khác

Bởi vì các giao diện bây giờ có bytecode trong chúng, chúng ta phải khởi tạo chúng tại thời điểm mà một lớp thực thi được khởi tạo.


1
Cảm ơn vì đã đào lên điều này. (+1) Có thể "bước 7.5" mới đã vô tình bị bỏ qua khỏi thông số kỹ thuật hoặc nó đã được đề xuất và bị từ chối và việc triển khai chưa bao giờ được khắc phục để xóa nó.
Stuart Marks

1

Tôi sẽ cố gắng đưa ra một trường hợp rằng việc khởi tạo giao diện không được gây ra bất kỳ tác dụng phụ kênh phụ nào mà các kiểu phụ phụ thuộc vào, do đó, cho dù đây có phải là lỗi hay không, hay bất kỳ cách nào Java sửa nó, điều đó không quan trọng ứng dụng trong đó các giao diện thứ tự được khởi tạo.

Trong trường hợp của a class, nó được chấp nhận tốt rằng nó có thể gây ra tác dụng phụ mà các phân lớp phụ thuộc vào. Ví dụ

class Foo{
    static{
        Bank.deposit($1000);
...

Bất kỳ lớp con Foonào cũng mong đợi rằng họ sẽ thấy $ 1000 trong ngân hàng, ở bất kỳ đâu trong mã lớp con. Do đó, lớp cha được khởi tạo trước lớp con.

Chúng ta cũng không nên làm điều tương tự cho các siêu bề mặt sao? Thật không may, thứ tự của các siêu giao diện không được cho là có ý nghĩa, do đó không có thứ tự xác định rõ để khởi tạo chúng.

Vì vậy, tốt hơn chúng ta không nên thiết lập loại tác dụng phụ này trong quá trình khởi tạo giao diện. Rốt cuộc, interfacekhông có nghĩa là cho các tính năng này (trường / phương thức tĩnh) mà chúng tôi tích lũy để thuận tiện.

Do đó, nếu chúng ta tuân theo nguyên tắc đó, chúng ta sẽ không quan tâm đến việc khởi tạo các giao diện thứ tự nào.

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.