ทำไม enum singleton ถึงขี้เกียจ?

Sep 24 2020

ผมเห็นคำตอบเช่นนี้พยายามที่จะชี้แจงผ่านทางความคิดเห็นและยังไม่รับหนังสือจากตัวอย่างที่นี่

อาจถึงเวลาสำหรับคำถามเฉพาะนี้ ...

ทำไมการดำเนิน enum เดี่ยวที่เรียกว่าขี้เกียจ ?

public enum EnumLazySingleton {
    INSTANCE;
    EnumLazySingleton() {
        System.out.println("constructing: " + this);
    }
    public static void touchClass() {}
}

มันแตกต่างจากการใช้งานอย่างกระตือรือร้นอย่างไร?

public class BasicEagerSingleton {
    private static final BasicEagerSingleton instance = new BasicEagerSingleton();
    public static BasicEagerSingleton getInstance() {
        return instance;
    }
    private BasicEagerSingleton() {
        System.out.println("constructing: " + this);
    }
    public static void touchClass() {}
}

ทั้งสองจะ init อินสแตนซ์โดยไม่ต้องเข้าถึงINSTANCE/getInstance()- touchClass()เช่นการโทร

public class TestSingleton {
    public static void main(String... args) {
        System.out.println("sleeping for 5 sec...");
        System.out.println("touching " + BasicEagerSingleton.class.getSimpleName());
        BasicEagerSingleton.touchClass();
        System.out.println("touching " + EnumLazySingleton.class.getSimpleName());
        EnumLazySingleton.touchClass();
    }
}

เอาท์พุต:

sleeping for 5 sec...
touching BasicEagerSingleton
constructing: BasicEagerSingleton@7bfcd12c
touching EnumLazySingleton
constructing: INSTANCE

ตอนนี้เราสามารถพูดได้ว่าทั้งสองมีความขี้เกียจ อะไรคือความกระตือรือร้น ?

เป็นที่ชัดเจนว่า (เช่น) วิธี "ตรวจสอบการล็อกซ้ำ" นั้นขี้เกียจจริง ๆ อย่างไร (ยุ่งและช้า) แต่ถ้า enum ขี้เกียจแสดงว่าคนโสดขี้เกียจเนื่องจากการโหลดคลาสที่หลีกเลี่ยงไม่ได้ - อันที่จริงทุกอย่างขี้เกียจ ความแตกต่างนี้จะหยุดลงเมื่อถึงจุดใด

คำตอบ

3 jaco0646 Sep 24 2020 at 19:58

สองคำตอบแรกที่เชื่อมโยงกัน (โดยPeter LawreyและJoachim Sauer ) ต่างยอมรับว่า enums ไม่ได้เริ่มต้นอย่างเกียจคร้าน คำตอบในลิงค์ที่สามนั้นผิดเกี่ยวกับความหมายของการเริ่มต้นที่ขี้เกียจ

คำแนะนำในการใช้ enums เป็น singletons มาจาก Effective Java ของ Josh Bloch โดยเฉพาะอย่างยิ่งบทที่เกี่ยวกับ enum singletons ไม่ได้กล่าวถึงความเกียจคร้าน มีบทต่อมาที่อุทิศให้กับการเริ่มต้นที่ขี้เกียจซึ่งในทำนองเดียวกันก็ไม่มีการกล่าวถึง enums บทนี้มีสองไฮไลท์

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

ไม่ต้องสงสัยเลยว่า enums จะเป็นอีกสำนวนหนึ่งในรายการนี้หากพวกเขาเริ่มต้นอย่างเฉื่อยชา ในความเป็นจริงพวกเขาไม่ได้แม้ว่าความสับสนเกี่ยวกับความหมายของการเริ่มต้นแบบขี้เกียจจะส่งผลให้คำตอบที่ไม่ถูกต้องตามที่ OP แสดง

Y2020-09 Sep 24 2020 at 18:58

ฉันขอเดิมพันดังต่อไปนี้:

คุณกำลังพยายามระบุ 2 "กระบวนการ" หรือ ... "things"(มาทำให้เข้าใจง่าย - เพราะถ้าฉันเริ่มพูดว่า "Code Blocks" ฟังดูยากกว่า) ...

  • เมื่อถึงจุดหนึ่ง class-loader จะทำงานและคุณต้องการทราบว่า"things"จะดำเนินการอย่างไรเมื่อ class-loader โหลดคลาส
  • ในอีกจุดหนึ่งการเรียกใช้เมธอดในคลาสจะทำให้อีกอันหนึ่ง"thing"รัน / รันและคุณต้องการทราบว่า (ซึ่ง"processes") จะเริ่มต้นอย่างไร ..

ข้อเท็จจริงต่อไปนี้เกี่ยวข้อง:

  • Static initializers ถูกรันเมื่อ class-loader โหลดคลาส class-loader จะไม่โหลดคลาสจนกว่าโค้ดที่รันอยู่พบความจำเป็นในการโหลด (เนื่องจากมีการเรียกใช้เมธอดหรือฟิลด์) เช่น:touchClass()
  • หากอินสแตนซ์ซิงเกิลตันของคลาสใดคลาสหนึ่งหรือประเภทที่แจกแจงมีfieldที่กำลังเริ่มต้นในstaticส่วนของคลาสคลาสนั้นจะถูกโหลดทันทีที่คุณ 'แตะ' คลาส - เนื่องจากคลาส - โหลดเดอร์รันทั้งหมดstatic initializationsสำหรับคลาสหรือ enum ในการโหลด
  • ขี้เกียจโหลดน่าจะเป็น (และนี่คือ "การตีความ" ของฉันเกี่ยวกับสิ่งที่คุณกำลังถาม) จะเกิดขึ้นเมื่อการเรียกใช้เมธอดขอให้ชั้นเรียนสร้างอินสแตนซ์ซิงเกิลตันซึ่งอาจเกิดขึ้นได้ไม่นานหลังจาก "โหลด" ของclassหรือenum.

ชั้นเรียนดังต่อไปนี้:

public class LazySingleton
{
    // At time of class-loading, this singleton is set to 'null'
    private static singleton = null;

    // This is a method that will not be invoked until it is called by
    // some other code-block (some other "thing")...  When "touchClass()"
    // is called, the singleton instance is not created.
    public static LazySingleton retrieveSingleton()
    {
        if (singleton == null) singleton = new LazySingleton();
        return singleton;
    }

    // DOES NOTHING...  The Singleton is *not* loaded, even though the
    // Class Loader has already loaded this Java ".class" file
    // into memory.
    public static void touchClass() { }

    private LazySingleton()
    { System.out.println("constructing: LazySingleton"); }
}

ในทางกลับกัน:

public enum EagerEnum
{
  // The class loader will run this constructor as soon as this 'enum'
  // is loaded from a '.class' file (in JAR or on disk) into memory
  MyEnumConstant();

  private EagerEnum()
  { System.out.println("Eager Enum Constructed"); }

  // This will cause the Class Loader to Load this enum from the
  // Java ".class" File immediately, and the "MyEnumConstant" will
  // also have to be loaded - meaning the constructor will be called.
  public static void touchEnum() { }
}

ดังนั้นรหัสต่อไปนี้จะสร้างผลลัพธ์

LazySingleton.touchClass();            // Prints nothing
EagerEnum.touchClass();                // Prints "Eager Enum Constructed"
LazySingleton.getSingletonInstance();  // Prints "constructing: LazySingleton