ฉันควรใช้ initializer blocks ใน Java หรือไม่


16

ฉันเพิ่งเจอโครงสร้าง Java ฉันไม่เคยเห็นมาก่อนและสงสัยว่าฉันควรใช้ มันดูเหมือนว่าจะถูกเรียกว่าการเริ่มต้นบล็อก

public class Test {
  public Test() { /* first constructor */ }
  public Test(String s) { /* second constructor */ }

  // Non-static initializer block - copied into every constructor:
  {
    doStuff();
  }
}

บล็อกรหัสจะถูกคัดลอกลงในตัวสร้างแต่ละตัวเช่นถ้าคุณมีตัวสร้างหลายตัวคุณไม่จำเป็นต้องเขียนรหัสใหม่

อย่างไรก็ตามฉันเห็นข้อเสียเปรียบหลักสามประการที่ใช้ไวยากรณ์นี้:

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

หากข้อความข้างต้นของฉันเป็นจริงทำไม (และเมื่อใด) จึงมีการสร้างภาษานี้ขึ้น มีกรณีการใช้งานที่ถูกกฎหมายหรือไม่?


3
ตัวอย่างที่คุณโพสต์ไม่รวมถึงสิ่งที่ดูเหมือนบล็อกเริ่มต้น
Simon B

6
@SimonBarker มองอีกครั้ง - { doStuff(); }ในระดับชั้นคือบล็อก initializer
amon

@SimonBarker บล็อกโค้ดที่อยู่โดยรอบdoStuff()
Reinstate Monica - dirkk


2
"[S] หมายถึงการเปลี่ยนลำดับของการบล็อกรหัสจะเปลี่ยนรหัสจริง ๆ " และแตกต่างจากการเปลี่ยนการจัดลำดับของตัวแปรเริ่มต้นหรือแต่ละบรรทัดของรหัสอย่างไร หากไม่มีการขึ้นต่อกันจะไม่มีอันตรายใด ๆ เกิดขึ้นและหากมีการขึ้นต่อกันจากนั้นการวางการพึ่งพานั้นไม่เป็นไปในลักษณะเดียวกับการอ้างอิงที่ผิดสำหรับแต่ละบรรทัดของโค้ด เพียงเพราะ Java ให้คุณอ้างอิงถึงวิธีการและชั้นเรียนก่อนที่จะมีการกำหนดไม่ได้หมายความว่ารหัสขึ้นอยู่กับการสั่งซื้อเป็นของหายากใน Java
JAB

คำตอบ:


20

มีสองกรณีที่ฉันใช้บล็อก initializer

คนแรกคือการเริ่มต้นสมาชิกสุดท้าย ใน Java คุณสามารถกำหนดค่าเริ่มต้นให้กับสมาชิกขั้นสุดท้ายทั้งแบบอินไลน์ด้วยการประกาศหรือคุณสามารถเริ่มต้นได้ในการกำหนด ในวิธีการนั้นห้ามมิให้กำหนดให้กับสมาชิกรายสุดท้าย

สิ่งนี้ใช้ได้:

final int val = 2;

สิ่งนี้ใช้ได้เช่นกัน:

final int val;

MyClass() {
    val = 2;
}

สิ่งนี้ไม่ถูกต้อง:

final int val;

MyClass() {
    init();
}

void init() {
    val = 2;  // cannot assign to 'final' field in a method
}

หากคุณมีหลาย Constructor และหากคุณไม่สามารถกำหนดค่าเริ่มต้นให้สมาชิกอินไลน์สุดท้าย (เนื่องจากตรรกะการกำหนดค่าเริ่มต้นซับซ้อนเกินไป) หรือหาก Constructor ไม่สามารถเรียกตัวเองได้คุณสามารถคัดลอก / วางรหัสเริ่มต้นได้หรือคุณสามารถใช้ บล็อก initializer

final int val;
final int squareVal;

MyClass(int v, String s) {
    this.val = v;
    this.s = s;
}

MyClass(Point p, long id) {
    this.val = p.x;
    this.id = id;
}

{
    squareVal = val * val;
}

กรณีการใช้งานอื่น ๆ ที่ฉันมีสำหรับบล็อกเริ่มต้นคือการสร้างโครงสร้างข้อมูลตัวช่วยขนาดเล็ก ฉันประกาศสมาชิกและใส่ค่าลงไปทันทีหลังจากประกาศในบล็อกเริ่มต้นของตัวเอง

private Map<String, String> days = new HashMap<String, String>();
{
    days.put("mon", "monday");
    days.put("tue", "tuesday");
    days.put("wed", "wednesday");
    days.put("thu", "thursday");
    days.put("fri", "friday");
    days.put("sat", "saturday");
    days.put("sun", "sunday");
}

ไม่ใช่วิธีการโทรที่ไม่ถูกต้อง มันเป็นรหัสในวิธีการเริ่มต้นที่ไม่ถูกต้อง เฉพาะ constructors และ initalizer block เท่านั้นที่สามารถกำหนดให้กับตัวแปรสมาชิกขั้นสุดท้ายดังนั้นการกำหนดใน init จะไม่รวบรวม
barjak

รหัสบล็อกที่สี่ของคุณไม่ได้รวบรวม บล็อก Initalizer ทำงานก่อนตัวสร้างทั้งหมดดังนั้นsquareVal = val * valจะบ่นเกี่ยวกับการเข้าถึงค่าที่ไม่ได้กำหนดค่าเริ่มต้น บล็อกเริ่มต้นไม่สามารถขึ้นอยู่กับอาร์กิวเมนต์ใด ๆ ที่ส่งผ่านไปยังตัวสร้าง วิธีแก้ปัญหาตามปกติที่ฉันเคยเห็นกับปัญหาแบบนั้นคือการกำหนดคอนสตรัคเตอร์ "ฐาน" เดี่ยวด้วยตรรกะที่ซับซ้อนและเพื่อกำหนดคอนสตรัคเตอร์อื่น ๆ ทั้งหมดในแง่ของอันนั้น การใช้อินสแตนซ์ initializers ส่วนใหญ่ที่จริงแล้วสามารถแทนที่ด้วยรูปแบบนั้นได้
Malnormalulo

11

โดยทั่วไปอย่าใช้บล็อกเริ่มต้นแบบไม่คงที่ (และอาจหลีกเลี่ยงบล็อกแบบคงที่ด้วย)

ไวยากรณ์ที่สับสน

ดูคำถามนี้มี 3 คำตอบ แต่คุณหลอก 4 คนด้วยไวยากรณ์นี้ ฉันเป็นหนึ่งในนั้นและฉันเขียน Java มา 16 ปีแล้ว! เห็นได้ชัดว่าไวยากรณ์อาจเกิดข้อผิดพลาดได้ง่าย! ฉันจะอยู่ห่างจากมัน

ตัวสร้างเหลื่อม

สำหรับสิ่งที่ง่ายจริงๆคุณสามารถใช้ตัวสร้าง "เหลื่อม" เพื่อหลีกเลี่ยงความสับสนนี้:

public class Test {
    private String something;

    // Default constructor does some things
    public Test() { doStuff(); }

    // Other constructors call the default constructor
    public Test(String s) {
        this(); // Call default constructor
        something = s;
    }
}

รูปแบบการสร้าง

หากคุณต้องการ doStuff () ที่ส่วนท้ายของ Constructor แต่ละตัวหรือการกำหนดค่าเริ่มต้นที่ซับซ้อนอื่น ๆ บางทีรูปแบบการสร้างจะดีที่สุด Josh Blochแสดงเหตุผลหลายประการว่าทำไมผู้สร้างจึงเป็นความคิดที่ดี ผู้สร้างใช้เวลาเล็กน้อยในการเขียน แต่เขียนอย่างถูกต้องพวกเขามีความสุขที่จะใช้

public class Test {
    // Value can be final (immutable)
    private final String something;

    // Private constructor.
    private Test(String s) { something = s; }

    // Static method to get a builder
    public static Builder builder() { return new Builder(); }

    // builder class accumulates values until a valid Test object can be created. 
    private static class Builder {
        private String tempSomething;
        public Builder something(String s) {
            tempSomething = s;
            return this;
        }
        // This is our factory method for a Test class.
        public Test build() {
            Test t = new Test(tempSomething);
            // Here we do your extra initialization after the
            // Test class has been created.
            doStuff();
            // Return a valid, potentially immutable Test object.
            return t;
        }
    }
}

// Now you can call:
Test t = Test.builder()
             .setString("Utini!")
             .build();

Static Initializer Loops

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

การเริ่มต้น Lazy

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

นิยามข้อมูล

แทนที่จะเริ่มต้นแบบคงที่สำหรับการสร้างโครงสร้างข้อมูล (เปรียบเทียบกับตัวอย่างในคำตอบอื่น ๆ ) ตอนนี้ฉันใช้ฟังก์ชันตัวช่วยกำหนดนิยามข้อมูลที่ไม่เปลี่ยนรูปของ Paguro :

private ImMap<String,String> days =
        map(tup("mon", "monday"),
            tup("tue", "tuesday"),
            tup("wed", "wednesday"),
            tup("thu", "thursday"),
            tup("fri", "friday"),
            tup("sat", "saturday"),
            tup("sun", "sunday"));

Conculsion

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


3

นอกเหนือจากการเริ่มต้นของตัวแปรอินสแตนซ์ที่ประกาศเป็นfinal(ดูคำตอบของ barjak ) ฉันจะพูดถึงการstaticเริ่มต้นบล็อก

คุณสามารถใช้มันเป็น "ตัวสร้างแบบคงที่"

ด้วยวิธีนี้คุณสามารถทำการกำหนดค่าเริ่มต้นที่ซับซ้อนในตัวแปรแบบสแตติกในครั้งแรกที่มีการอ้างอิงคลาส

นี่คือตัวอย่างแรงบันดาลใจจากหนึ่งใน barjak:

public class dayHelper(){
    private static Map<String, String> days = new HashMap<String, String>();
    static {
        days.put("mon", "monday");
        days.put("tue", "tuesday");
        days.put("wed", "wednesday");
        days.put("thu", "thursday");
        days.put("fri", "friday");
        days.put("sat", "saturday");
        days.put("sun", "sunday");
    }
    public static String getLongName(String shortName){
         return days.get(shortName);
    }
}

1

ในฐานะที่เป็น fas เป็นบล็อกเริ่มต้นที่ไม่คงที่มีความกังวลฟังก์ชั่นเปลือยของพวกเขาคือการทำหน้าที่เป็นตัวสร้างเริ่มต้นในชั้นเรียนที่ไม่ระบุชื่อ นั่นเป็นเพียงสิทธิของพวกเขาเท่านั้นที่มีอยู่


0

ฉันเห็นด้วยอย่างยิ่งกับข้อความที่ 1, 2, 3 ฉันไม่เคยใช้ block initializers ด้วยเหตุผลเหล่านี้และฉันไม่รู้ว่าทำไมจึงมีอยู่ใน Java

อย่างไรก็ตามฉันถูกบังคับให้ใช้initializer บล็อกแบบคงที่ในกรณีเดียว: เมื่อฉันต้องสร้างอินสแตนซ์ฟิลด์แบบคงที่ซึ่งตัวสร้างสามารถส่งข้อยกเว้นที่เลือก

private static final JAXBContext context = JAXBContext.newInstance(Foo.class); //doesn't compile

แต่คุณต้องทำ:

private static JAXBContext context;
static {
    try
    {
        context = JAXBContext.newInstance(Foo.class);
    }
    catch (JAXBException e)
    {
        //seriously...
    }
}

ฉันพบว่าสำนวนนี้น่าเกลียดมาก (มันยังป้องกันไม่ให้คุณทำเครื่องหมายcontextเป็นfinal) แต่นี่เป็นวิธีเดียวที่ Java สนับสนุนเพื่อเริ่มต้นฟิลด์ดังกล่าว


ฉันคิดว่าถ้าคุณตั้งcontext = null;บล็อก catch ของคุณเพื่อที่คุณจะสามารถประกาศบริบทเป็นที่สิ้นสุด
GlenPeterson

@GlenPeterson ฉันพยายาม แต่มันไม่ได้รวบรวม:The final field context may already have been assigned
ด่าง

โอ๊ะ! ฉันเดิมพันคุณสามารถทำให้บริบทของคุณเป็นครั้งสุดท้ายหากคุณแนะนำตัวแปรท้องถิ่นภายในบล็อกแบบคงที่:static { JAXBContext tempCtx = null; try { tempCtx = JAXBContext.newInstance(Foo.class); } catch (JAXBException ignored) { ; } context = tempCtx; }
GlenPeterson
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.