อินเทอร์เฟซที่มีวิธีการเริ่มต้นเริ่มต้นเมื่อใด


93

ในขณะที่ค้นหาข้อมูลจำเพาะภาษา Java เพื่อตอบคำถามนี้ฉันได้เรียนรู้สิ่งนั้น

ก่อนที่คลาสจะเริ่มต้นจะต้องเริ่มต้นคลาสซูเปอร์คลาสโดยตรงแต่อินเทอร์เฟซที่ใช้โดยคลาสจะไม่ถูกเตรียมใช้งาน ในทำนองเดียวกัน superinterfaces ของอินเทอร์เฟซจะไม่ถูกเตรียมใช้งานก่อนที่อินเทอร์เฟซจะเริ่มต้น

เพื่อความอยากรู้อยากเห็นของฉันเองฉันลองใช้แล้วและตามที่คาดไว้อินเทอร์เฟซInterfaceTypeไม่ได้เริ่มต้น

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

โปรแกรมนี้พิมพ์

implemented method

อย่างไรก็ตามหากอินเทอร์เฟซประกาศdefaultวิธีการเริ่มต้นจะเกิดขึ้น พิจารณาInterfaceTypeอินเทอร์เฟซที่ระบุเป็น

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

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

จากนั้นโปรแกรมเดียวกันด้านบนจะพิมพ์

static initializer  
implemented method

กล่าวอีกนัยหนึ่งstaticฟิลด์ของอินเทอร์เฟซถูกเตรียมใช้งาน ( ขั้นตอนที่ 9 ในขั้นตอนการเริ่มต้นโดยละเอียด ) และตัวstaticเริ่มต้นของชนิดที่กำลังเตรียมใช้งานจะถูกเรียก ซึ่งหมายความว่าอินเทอร์เฟซเริ่มต้นแล้ว

ฉันไม่พบสิ่งใดใน JLS เพื่อระบุว่าสิ่งนี้ควรเกิดขึ้น อย่าเข้าใจฉันผิดฉันเข้าใจว่าสิ่งนี้ควรเกิดขึ้นในกรณีที่คลาสการใช้งานไม่ได้จัดเตรียมการใช้งานสำหรับวิธีการนี้ แต่จะเป็นอย่างไร เงื่อนไขนี้หายไปจากข้อกำหนดภาษา Java ฉันพลาดอะไรไปหรือฉันตีความผิดหรือเปล่า


4
ฉันเดาว่า - อินเทอร์เฟซดังกล่าวถือว่าเป็นคลาสนามธรรมในแง่ของลำดับการเริ่มต้น ฉันเขียนสิ่งนี้เป็นความคิดเห็นเพราะฉันไม่แน่ใจว่านี่เป็นข้อความที่ถูกต้องหรือไม่ :)
Alexey Malev

ควรอยู่ในส่วน 12.4 ของ JLS แต่ดูเหมือนจะไม่อยู่ที่นั่น ฉันจะบอกว่ามันหายไป
Warren Dew

1
ไม่เป็นไร .... ส่วนใหญ่เมื่อพวกเขาไม่เข้าใจหรือไม่มีคำอธิบายพวกเขาจะลงคะแนน :( สิ่งนี้เกิดขึ้นกับ SO โดยทั่วไป
NeverGiveUp161

ฉันคิดว่าinterfaceใน Java ไม่ควรกำหนดวิธีการที่เป็นรูปธรรม ดังนั้นฉันจึงแปลกใจที่InterfaceTypeโค้ดได้รวบรวม
MaxZoom

คำตอบ:


85

นี่เป็นประเด็นที่น่าสนใจมาก!

ดูเหมือนว่าJLS มาตรา 12.4.1ควรจะครอบคลุมถึงเรื่องนี้อย่างชัดเจน อย่างไรก็ตามลักษณะการทำงานของ Oracle JDK และ OpenJDK (javac และ HotSpot) แตกต่างจากที่ระบุไว้ที่นี่ โดยเฉพาะอย่างยิ่งตัวอย่างที่ 12.4.1-3 จากส่วนนี้ครอบคลุมการเริ่มต้นอินเทอร์เฟซ ตัวอย่างดังต่อไปนี้:

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

ผลลัพธ์ที่คาดหวังคือ:

1
j=3
jj=4
3

และแน่นอนฉันได้ผลลัพธ์ที่คาดหวัง แต่ถ้าวิธีการเริ่มต้นมีการเพิ่มการติดต่อI,

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

ผลลัพธ์จะเปลี่ยนเป็น:

1
ii=2
j=3
jj=4
3

ซึ่งบ่งชี้อย่างชัดเจนว่าอินเทอร์เฟซIกำลังเริ่มต้นโดยที่ไม่เคยมีมาก่อน! การมีอยู่ของวิธีการเริ่มต้นก็เพียงพอที่จะเริ่มต้น เมธอดเริ่มต้นไม่จำเป็นต้องถูกเรียกใช้หรือลบล้างหรือกล่าวถึงและไม่มีเมธอดนามธรรมที่ทำให้เกิดการเริ่มต้น

การคาดเดาของฉันคือการใช้งาน HotSpot ต้องการหลีกเลี่ยงการเพิ่มการตรวจสอบการเริ่มต้นคลาส / อินเทอร์เฟซในเส้นทางวิกฤตของการinvokevirtualโทร ก่อนหน้า Java 8 และเมธอดเริ่มต้นinvokevirtualไม่สามารถสิ้นสุดการรันโค้ดในอินเทอร์เฟซได้ดังนั้นจึงไม่เกิดขึ้น อาจคิดว่านี่เป็นส่วนหนึ่งของขั้นตอนการเตรียมคลาส / อินเทอร์เฟซ ( JLS 12.3.2 ) ซึ่งเริ่มต้นสิ่งต่างๆเช่นตารางวิธีการ แต่บางทีสิ่งนี้อาจไปไกลเกินไปและตั้งใจทำการเริ่มต้นแบบเต็มแทน

ฉันได้ตั้งคำถามนี้ในรายการเมลคอมไพเลอร์-dev ของ OpenJDK มีการตอบกลับจาก Alex Buckley (บรรณาธิการของ JLS) ซึ่งเขาตั้งคำถามเพิ่มเติมซึ่งส่งไปที่ทีมดำเนินการ JVM และแลมบ์ดา นอกจากนี้เขายังตั้งข้อสังเกตว่ามีจุดบกพร่องในข้อมูลจำเพาะที่นี่ซึ่งระบุว่า "T เป็นคลาสและวิธีการคงที่ที่ประกาศโดย T ถูกเรียกใช้" ควรใช้ด้วยหาก T เป็นอินเทอร์เฟซ ดังนั้นอาจเป็นไปได้ว่ามีทั้งสเปคและบั๊ก HotSpot ที่นี่

การเปิดเผยข้อมูล : ฉันทำงานให้กับ Oracle บน OpenJDK หากผู้คนคิดว่าสิ่งนี้ทำให้ฉันได้เปรียบอย่างไม่เป็นธรรมในการได้รับรางวัลที่แนบมากับคำถามนี้ฉันก็ยินดีที่จะยืดหยุ่นในเรื่องนี้


6
ฉันขอแหล่งข้อมูลอย่างเป็นทางการ ฉันไม่คิดว่ามันจะเป็นทางการมากไปกว่านี้ ให้เวลาสองวันเพื่อดูพัฒนาการทั้งหมด
Sotirios Delimanolis

48
@StuartMarks " หากผู้คนคิดว่าสิ่งนี้ทำให้ฉันได้เปรียบอย่างไม่เป็นธรรม ฯลฯ " => เราพร้อมรับคำตอบสำหรับคำถามและนี่คือคำตอบที่สมบูรณ์แบบ!
assylias

2
หมายเหตุด้านข้าง: ข้อกำหนด JVM มีคำอธิบายที่คล้ายกับของ JLS: docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5สิ่งนี้ควรได้รับการอัปเดตเช่นกัน .
Marco13

2
@assylias และ Sotirios ขอบคุณสำหรับความคิดเห็นของคุณ พวกเขาพร้อมกับการโหวตเพิ่ม 14 โหวต (ตามที่เขียนนี้) ในความคิดเห็นของ assylias ได้บรรเทาความกังวลของฉันเกี่ยวกับความไม่ยุติธรรมที่อาจเกิดขึ้น
Stuart Marks

1
@SotiriosDelimanolis มีข้อบกพร่องสองสามข้อที่ดูเหมือนเกี่ยวข้องคือJDK-8043275และJDK-8043190และถูกทำเครื่องหมายว่าแก้ไขแล้วใน 8u40 อย่างไรก็ตามพฤติกรรมน่าจะเหมือนกัน นอกจากนี้ยังมีการเปลี่ยนแปลงข้อมูลจำเพาะของ JVM บางอย่างที่เกี่ยวพันกับสิ่งนี้ดังนั้นการแก้ไขอาจเป็นอย่างอื่นที่ไม่ใช่ "เรียกคืนลำดับการเริ่มต้นเดิม"
Stuart Marks

13

อินเทอร์เฟซไม่ได้ถูกเตรียมใช้งานเนื่องจากฟิลด์ค่าคงInterfaceType.initที่ซึ่งถูกเตรียมใช้งานโดยค่าที่ไม่ใช่ค่าคงที่ (การเรียกวิธีการ) ไม่ได้ใช้ที่ใดก็ได้

เป็นที่ทราบกันดีในขณะคอมไพล์ว่าฟิลด์อินเทอร์เฟซคงที่ไม่ได้ถูกใช้ที่ใดก็ได้และอินเทอร์เฟซไม่มีเมธอดเริ่มต้นใด ๆ (ใน java-8) ดังนั้นจึงไม่จำเป็นต้องเริ่มต้นหรือโหลดอินเตอร์เฟส

อินเทอร์เฟซจะเริ่มต้นในกรณีต่อไปนี้

  • ใช้ฟิลด์ค่าคงที่ในโค้ดของคุณ
  • อินเทอร์เฟซมีวิธีการเริ่มต้น (Java 8)

ในกรณีของการเริ่มต้นInterfaceTypeคุณมีการดำเนินการ ดังนั้นถ้าInterfaceTypeจะมีเมธอดเริ่มต้นใด ๆ มันจะถูกINHERITED (ใช้)ในการนำคลาสไปใช้ และ Initialization จะเข้าสู่ภาพ

แต่ถ้าคุณกำลังเข้าถึงฟิลด์อินเทอร์เฟซคงที่ (ซึ่งเริ่มต้นด้วยวิธีปกติ) ไม่จำเป็นต้องเริ่มต้นอินเทอร์เฟซ

พิจารณารหัสต่อไปนี้

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

InterfaceType.initในกรณีดังกล่าวข้างต้นอินเตอร์เฟซจะสามารถเริ่มต้นและเต็มไปเพราะคุณกำลังใช้สนาม

ฉันไม่ได้ให้ตัวอย่างวิธีการเริ่มต้นตามที่คุณได้ระบุไว้ในคำถามของคุณ

ข้อกำหนดและตัวอย่างภาษา Java กำหนดไว้ในJLS 12.4.1 (ตัวอย่างไม่มีวิธีการเริ่มต้น)


ฉันไม่พบ JLS สำหรับวิธีการเริ่มต้นอาจมีความเป็นไปได้สองประการ

  • ชาว Java ลืมพิจารณากรณีของวิธีการเริ่มต้น (ข้อมูลจำเพาะจุดบกพร่องของเอกสาร)
  • พวกเขาอ้างถึงวิธีการเริ่มต้นเป็นสมาชิกที่ไม่คงที่ของอินเทอร์เฟซ (แต่ไม่ได้กล่าวถึงจุดบกพร่องของเอกสารข้อมูลจำเพาะอีกครั้ง)

ฉันกำลังมองหาข้อมูลอ้างอิงสำหรับวิธีการเริ่มต้น ฟิลด์นี้เป็นเพียงการแสดงให้เห็นว่าอินเทอร์เฟซเริ่มต้นแล้วหรือไม่
Sotirios Delimanolis

@SotiriosDelimanolis ฉันพูดถึงเหตุผลในคำตอบสำหรับวิธีการเริ่มต้น ... แต่น่าเสียดายที่ยังไม่พบ JLS สำหรับวิธีการเริ่มต้น
ไม่ใช่ข้อผิดพลาด

น่าเสียดายที่นั่นคือสิ่งที่ฉันกำลังมองหา ฉันรู้สึกว่าคำตอบของคุณเป็นเพียงการทำซ้ำสิ่งที่ฉันได้ระบุไว้แล้วในคำถามเช่น อินเทอร์เฟซจะถูกเตรียมใช้งานหากมีdefaultเมธอดและคลาสที่ใช้อินเทอร์เฟซถูกเตรียมใช้งาน
Sotirios Delimanolis

ฉันคิดว่าคน java ลืมพิจารณากรณีของวิธีการเริ่มต้นหรือพวกเขาเพียงแค่อ้างถึงวิธีการเริ่มต้นว่าเป็นสมาชิกที่ไม่คงที่ของอินเทอร์เฟซ (สมมติฐานของฉันไม่พบในเอกสารใด ๆ )
ไม่ใช่ข้อผิดพลาด

1
@KishanSarsechaGajjar: ฟิลด์ที่ไม่ใช่ค่าคงที่ในอินเทอร์เฟซหมายถึงอะไร? ตัวแปร / ฟิลด์ใด ๆ ในอินเทอร์เฟซเป็นแบบคงที่โดยค่าเริ่มต้น
Lokesh

10

instanceKlass.cppไฟล์จาก OpenJDK มีวิธีการเริ่มต้นInstanceKlass::initialize_implที่สอดคล้องกับขั้นตอนการเตรียมรายละเอียดใน JLS ซึ่งพบ analogously ในการเริ่มต้นส่วนใน JVM Spec

ประกอบด้วยขั้นตอนใหม่ที่ไม่ได้กล่าวถึงใน JLS และไม่มีในหนังสือ JVM ที่อ้างถึงในรหัส:

// 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);
    ....
    }
  }
}

ดังนั้นการเริ่มต้นนี้ได้รับการดำเนินการอย่างชัดเจนเป็นใหม่ขั้นตอนที่ 7.5 สิ่งนี้บ่งชี้ว่าการใช้งานนี้เป็นไปตามข้อกำหนดบางประการ แต่ดูเหมือนว่าข้อกำหนดที่เขียนไว้บนเว็บไซต์ยังไม่ได้รับการอัปเดต

แก้ไข: เพื่อเป็นข้อมูลอ้างอิงการกระทำ (ตั้งแต่เดือนตุลาคม 2555!) ซึ่งรวมขั้นตอนตามลำดับไว้ในการใช้งาน: http://hg.openjdk.java.net/jdk8/build/hotspot/rev/4735d2c84362

แก้ไข 2: บังเอิญฉันพบเอกสารนี้เกี่ยวกับวิธีการเริ่มต้นในฮอตสปอตซึ่งมีบันทึกด้านข้างที่น่าสนใจในตอนท้าย:

3.7 เบ็ดเตล็ด

เนื่องจากตอนนี้อินเทอร์เฟซมี bytecode อยู่เราจึงต้องเตรียมใช้งานในเวลาที่เริ่มต้นคลาสการนำไปใช้งาน


1
ขอบคุณที่ขุดขึ้นมา (+1) อาจเป็นไปได้ว่า "ขั้นตอนที่ 7.5" ใหม่ถูกละเว้นจากข้อมูลจำเพาะโดยไม่ได้ตั้งใจหรือมีการเสนอและปฏิเสธและการติดตั้งไม่เคยได้รับการแก้ไขเพื่อลบออก
Stuart Marks

1

ฉันจะพยายามสร้างกรณีที่การเริ่มต้นอินเทอร์เฟซไม่ควรทำให้เกิดผลข้างเคียงของ side-channel ที่ประเภทย่อยขึ้นอยู่ดังนั้นไม่ว่าจะเป็นจุดบกพร่องหรือไม่หรือไม่ว่า Java จะแก้ไขด้วยวิธีใดก็ไม่ควรสำคัญ แอปพลิเคชันที่อินเทอร์เฟซคำสั่งเริ่มต้น

ในกรณีของ a classเป็นที่ยอมรับกันว่าอาจทำให้เกิดผลข้างเคียงซึ่งขึ้นอยู่กับคลาสย่อย ตัวอย่างเช่น

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

คลาสย่อยใด ๆ ที่Fooคาดหวังว่าพวกเขาจะเห็น $ 1,000 ในธนาคารทุกที่ในรหัสคลาสย่อย ดังนั้นซูเปอร์คลาสจึงถูกเตรียมใช้งานก่อนคลาสย่อย

เราควรทำสิ่งเดียวกันกับ superintefaces ด้วยไม่ใช่หรือ? น่าเสียดายที่ลำดับของ superinterfaces ไม่ควรมีนัยสำคัญดังนั้นจึงไม่มีลำดับที่กำหนดไว้อย่างชัดเจนในการเริ่มต้น

ดังนั้นเราจึงไม่ควรสร้างผลข้างเคียงประเภทนี้ในการเริ่มต้นอินเทอร์เฟซ หลังจากที่ทุกคนinterfaceไม่ได้หมายความว่าคุณสมบัติเหล่านี้ (สาขาคงที่ / วิธีการ) เรากองพะเนินเทินทึกเพื่อความสะดวกสบาย

ดังนั้นหากเราปฏิบัติตามหลักการนั้นเราจะไม่ต้องกังวลกับการเริ่มต้นอินเทอร์เฟซคำสั่ง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.