การเริ่มต้น Double Brace ใน Java คืออะไร


306

ไวยากรณ์การกำหนดค่าเริ่มต้น Double Brace ( {{ ... }}) ใน Java คืออะไร




10
การเริ่มต้น Double รั้งเป็นคุณลักษณะที่อันตรายมากและควรใช้อย่างรอบคอบ มันอาจแตกหักเท่ากับสัญญาและแนะนำการรั่วไหลของหน่วยความจำที่ยุ่งยาก นี้บทความอธิบายรายละเอียด
Andrii Polunin

คำตอบ:


302

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

new ArrayList<Integer>() {{
   add(1);
   add(2);
}};

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


11
ขอขอบคุณที่อธิบายความหมายของเครื่องมือจัดฟันทั้งภายในและภายนอก ฉันสงสัยว่าทำไมมีวงเล็บปีกกาสองอันที่อนุญาตให้มีความหมายพิเศษเมื่อพวกเขาอยู่ในความเป็นจริงจาวาปกติสร้างที่ปรากฏเป็นเคล็ดลับใหม่บางขลัง สิ่งต่าง ๆ ที่ทำให้ฉันตั้งคำถามกับไวยากรณ์ของ Java หากคุณไม่ใช่ผู้เชี่ยวชาญอยู่แล้วอาจเป็นเรื่องยากมากที่จะอ่านและเขียน
jackthehipster

4
"Magic syntax" มีอยู่ในหลายภาษาเช่นภาษา C เกือบทุกภาษารองรับ "ไปที่ 0" ไวยากรณ์ของ "x -> 0" ในลูปซึ่งเป็นเพียง "x--> 0" แปลก ๆ การวางพื้นที่
Joachim Sauer

17
เราสามารถสรุปได้ว่า "double brace initialization" ไม่มีอยู่ด้วยตัวเองมันเป็นเพียงการรวมกันของการสร้างคลาสนิรนามและบล็อก initializerซึ่งเมื่อรวมกันแล้วดูเหมือนโครงสร้างการสร้างประโยค แต่ในความเป็นจริงมันไม่ใช่ ' เสื้อ
พิธีกรจักรพรรดิ

ขอบคุณ! Gson ส่งคืนค่า null เมื่อเราจัดลำดับสิ่งที่มีการเริ่มต้นวงเล็บปีกกาคู่เนื่องจากการใช้งานคลาสภายในที่ไม่ระบุชื่อ
Pradeep AJ- Msft MVP

295

ทุกครั้งที่มีคนใช้วงเล็บปีกกาเริ่มต้นลูกแมวถูกฆ่าตาย

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

1. คุณกำลังสร้างคลาสที่ไม่ระบุชื่อมากเกินไป

ทุกครั้งที่คุณใช้การเริ่มต้นวงเล็บปีกกาสองครั้งคลาสใหม่จะถูกสร้างขึ้น เช่นตัวอย่างนี้:

Map source = new HashMap(){{
    put("firstName", "John");
    put("lastName", "Smith");
    put("organizations", new HashMap(){{
        put("0", new HashMap(){{
            put("id", "1234");
        }});
        put("abc", new HashMap(){{
            put("id", "5678");
        }});
    }});
}};

... จะสร้างคลาสเหล่านี้:

Test$1$1$1.class
Test$1$1$2.class
Test$1$1.class
Test$1.class
Test.class

นั่นเป็นค่าใช้จ่ายเล็กน้อยสำหรับ classloader ของคุณ - เพื่ออะไร! แน่นอนว่าจะไม่ใช้เวลาเริ่มต้นมากถ้าคุณทำครั้งเดียว แต่ถ้าคุณทำเช่นนี้ 20,000 ครั้งตลอดทั้งแอปพลิเคชั่นองค์กรของคุณ ... หน่วยความจำทั้งหมดนั้นใช้เพียงแค่ "ซินแท็กซ์น้ำตาล" หรือไม่?

2. คุณกำลังสร้างหน่วยความจำรั่ว!

หากคุณใช้รหัสด้านบนและส่งคืนแผนที่นั้นจากวิธีผู้โทรของวิธีนั้นอาจถือทรัพยากรที่หนักมากซึ่งไม่สามารถรวบรวมขยะได้อย่างไม่น่าสงสัย ลองพิจารณาตัวอย่างต่อไปนี้:

public class ReallyHeavyObject {

    // Just to illustrate...
    private int[] tonsOfValues;
    private Resource[] tonsOfResources;

    // This method almost does nothing
    public Map quickHarmlessMethod() {
        Map source = new HashMap(){{
            put("firstName", "John");
            put("lastName", "Smith");
            put("organizations", new HashMap(){{
                put("0", new HashMap(){{
                    put("id", "1234");
                }});
                put("abc", new HashMap(){{
                    put("id", "5678");
                }});
            }});
        }};

        return source;
    }
}

กลับมาตอนนี้จะมีการอ้างอิงถึงเช่นการปิดล้อมของMap ReallyHeavyObjectคุณอาจไม่ต้องการที่จะเสี่ยง:

หน่วยความจำรั่วที่นี่

ภาพจากhttp://blog.jooq.org/2014/12/08/dont-be-clever-the-double-curly-braces-anti-pattern/

3. คุณสามารถทำเป็นว่า Java มีตัวอักษรแผนที่

เพื่อตอบคำถามที่แท้จริงของคุณผู้คนใช้ไวยากรณ์นี้เพื่อแกล้งทำเป็นว่า Java มีบางอย่างเช่นตัวอักษรแผนที่คล้ายกับตัวอักษรแถวลำดับที่มีอยู่:

String[] array = { "John", "Doe" };
Map map = new HashMap() {{ put("John", "Doe"); }};

บางคนอาจพบว่ามีการกระตุ้น syntactically


11
"คุณกำลังสร้างคลาสที่ไม่ระบุชื่อมากเกินไป" - ดูว่า (พูด) Scala สร้างคลาสที่ไม่ระบุชื่อได้อย่างไรฉันไม่แน่ใจว่านี่เป็นปัญหาใหญ่
Brian Agnew

2
มันยังคงเป็นวิธีที่ดีและถูกต้องในการประกาศแผนที่แบบคงที่ใช่หรือไม่ ถ้า HashMap ถูกเตรียมใช้งานด้วย{{...}}และประกาศเป็นstaticเขตข้อมูลไม่ควรมีหน่วยความจำรั่วที่เป็นไปได้มีคลาสที่ไม่ระบุชื่อเดียวเท่านั้นและไม่มีการอ้างอิงอินสแตนซ์ที่ล้อมรอบใช่มั้ย
lorenzo-s

8
@ lorenzo-s: ใช่ 2) และ 3) ไม่ได้ใช้เพียง 1) โชคดีที่มี Java 9 ในที่สุดก็Map.of()มีวัตถุประสงค์เพื่อที่จะเป็นทางออกที่ดีกว่า
Lukas Eder

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

41
  • รั้งแรกสร้าง Anonymous Inner Class ใหม่
  • วงเล็บปีกกาชุดที่สองจะสร้างตัวเริ่มต้นอินสแตนซ์เช่นบล็อกแบบคงที่ในคลาส

ตัวอย่างเช่น:

   public class TestHashMap {
    public static void main(String[] args) {
        HashMap<String,String> map = new HashMap<String,String>(){
        {
            put("1", "ONE");
        }{
            put("2", "TWO");
        }{
            put("3", "THREE");
        }
        };
        Set<String> keySet = map.keySet();
        for (String string : keySet) {
            System.out.println(string+" ->"+map.get(string));
        }
    }

}

มันทำงานอย่างไร

รั้งแรกสร้าง Anonymous Inner Class ใหม่ คลาสภายในเหล่านี้มีความสามารถในการเข้าถึงพฤติกรรมของคลาสแม่ของพวกเขา ดังนั้นในกรณีของเราเรากำลังสร้างคลาสย่อยของคลาส HashSet ดังนั้นคลาสภายในนี้สามารถใช้เมธอด put ()

และชุดที่สองของการจัดฟันนั้นไม่มีอะไรนอกจาก initializers เช่น หากคุณเตือนแนวคิดของ Java หลักคุณสามารถเชื่อมโยงบล็อกตัวเริ่มต้นอินสแตนซ์กับตัวเริ่มต้นแบบคงที่ได้ง่ายเนื่องจากวงเล็บปีกกาที่คล้ายกันเช่น struct ข้อแตกต่างเพียงอย่างเดียวคือว่า initializer แบบสแตติกจะถูกเพิ่มด้วยคำหลักแบบสแตติกและรันเพียงครั้งเดียวเท่านั้น ไม่ว่าคุณจะสร้างวัตถุกี่ชิ้น

มากกว่า


24

สำหรับการประยุกต์ใช้ความสนุกสนานของการเริ่มต้นรั้งคู่ดูที่นี่อาร์เรย์ Dwemthy ใน Java

ข้อความที่ตัดตอนมา

private static class IndustrialRaverMonkey
  extends Creature.Base {{
    life = 46;
    strength = 35;
    charisma = 91;
    weapon = 2;
  }}

private static class DwarvenAngel
  extends Creature.Base {{
    life = 540;
    strength = 6;
    charisma = 144;
    weapon = 50;
  }}

และตอนนี้เตรียมพร้อมสำหรับBattleOfGrottoOfSausageSmellsและ ... เบคอนอ้วน!


16

ฉันคิดว่ามันเป็นสิ่งสำคัญที่จะเน้นว่ามีสิ่งไม่เช่น "คู่รั้งการเริ่มต้น" ในชวา Oracle เว็บไซต์ไม่มีคำนี้ ในตัวอย่างนี้มีคุณสมบัติสองอย่างที่ใช้ร่วมกันคือคลาสที่ไม่ระบุชื่อและบล็อก initializer ดูเหมือนว่าบล็อกเริ่มต้นเก่าถูกลืมโดยนักพัฒนาและทำให้เกิดความสับสนในหัวข้อนี้ การอ้างอิงจากเอกสารของ Oracle :

ตัวเริ่มต้นบล็อกสำหรับตัวแปรอินสแตนซ์มีลักษณะเหมือนบล็อกแบบเริ่มต้นคงที่ แต่ไม่มีคำหลักแบบคงที่:

{
    // whatever code is needed for initialization goes here
}

11

1- ไม่มีสิ่งใดที่เป็นเครื่องหมายปีกกาคู่:
ฉันต้องการชี้ให้เห็นว่าไม่มีสิ่งเช่นการเริ่มต้นวงเล็บปีกกาสองครั้ง มีเพียงบล็อก initializition ดั้งเดิมหนึ่งวงเล็บแบบดั้งเดิมเท่านั้น การจัดฟันบล็อกที่สองไม่มีส่วนเกี่ยวข้องกับการเริ่มต้น รู้รอบตอบว่าวงเล็บปีกกาทั้งสองนั้นเริ่มต้นบางอย่าง แต่มันไม่เหมือน

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

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

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

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


9

ในการหลีกเลี่ยงผลกระทบด้านลบทั้งหมดของการเริ่มต้นวงเล็บปีกกาคู่เช่น:

  1. ความเข้ากันได้ "เท่ากับ" ที่ใช้งานไม่ได้
  2. ไม่มีการตรวจสอบเมื่อใช้การมอบหมายโดยตรง
  3. หน่วยความจำรั่ว

ทำสิ่งต่อไป:

  1. ทำคลาส "Builder" แยกต่างหากโดยเฉพาะสำหรับการเริ่มต้นวงเล็บปีกกา
  2. ประกาศเขตข้อมูลด้วยค่าเริ่มต้น
  3. ใส่วิธีการสร้างวัตถุในชั้นเรียนนั้น

ตัวอย่าง:

public class MyClass {
    public static class Builder {
        public int    first  = -1        ;
        public double second = Double.NaN;
        public String third  = null      ;

        public MyClass create() {
            return new MyClass(first, second, third);
        }
    }

    protected final int    first ;
    protected final double second;
    protected final String third ;

    protected MyClass(
        int    first ,
        double second,
        String third
    ) {
        this.first = first ;
        this.second= second;
        this.third = third ;
    }

    public int    first () { return first ; }
    public double second() { return second; }
    public String third () { return third ; }
}

การใช้งาน:

MyClass my = new MyClass.Builder(){{ first = 1; third = "3"; }}.create();

ข้อดี:

  1. เพียงใช้
  2. อย่าแบ่งความเข้ากันได้ "เท่ากับ"
  3. คุณสามารถทำการตรวจสอบในวิธีการสร้าง
  4. ไม่มีหน่วยความจำรั่ว

ข้อเสีย:

  • ไม่มี.

และด้วยเหตุนี้เราจึงมีรูปแบบตัวสร้างจาวาที่ง่ายที่สุดเท่าที่เคยมีมา

ดูตัวอย่างทั้งหมดได้ที่ github: java-sf-builder-simple-example


4

มันคือ - ท่ามกลางการใช้งานอื่น ๆ - ทางลัดสำหรับการเริ่มต้นการรวบรวม เรียนรู้เพิ่มเติม ...


2
นั่นเป็นแอปพลิเคชั่นเดียวสำหรับมัน แต่ไม่ได้มีเพียงแอปพลิเคชันเดียวเท่านั้น
skaffman

4

คุณหมายถึงอะไรเช่นนี้?

List<String> blah = new ArrayList<String>(){{add("asdfa");add("bbb");}};

มันคือการเริ่มต้นรายการอาร์เรย์ในเวลาสร้าง (แฮ็ค)


4

คุณสามารถทำให้บางคำสั่ง Java เป็นวงเพื่อเริ่มต้นการเก็บรวบรวม:

List<Character> characters = new ArrayList<Character>() {
    {
        for (char c = 'A'; c <= 'E'; c++) add(c);
    }
};

Random rnd = new Random();

List<Integer> integers = new ArrayList<Integer>() {
    {
         while (size() < 10) add(rnd.nextInt(1_000_000));
    }
};

แต่กรณีนี้ส่งผลกระทบต่อประสิทธิภาพการทำงานตรวจสอบการสนทนานี้


4

ตามที่อธิบายไว้โดย @Lukas Eder การจัดฟันสองครั้งของการเริ่มต้นการรวบรวมจะต้องหลีกเลี่ยง

มันสร้างคลาสภายในแบบไม่ระบุชื่อและเนื่องจากคลาสภายในทั้งหมดเก็บการอ้างอิงไปยังอินสแตนซ์หลักซึ่งสามารถ - และ 99% มีแนวโน้มที่จะ - ป้องกันการรวบรวมขยะหากวัตถุการรวบรวมเหล่านี้ถูกอ้างอิงโดยวัตถุมากกว่าการประกาศ

Java 9 มีวิธีการอำนวยความสะดวกแนะนำList.of, Set.ofและMap.ofที่ควรจะนำมาใช้แทน มันเร็วกว่าและมีประสิทธิภาพมากกว่า initializer แบบ double-brace


0

วงเล็บปีกกาแรกสร้างคลาสไม่ระบุชื่อใหม่และชุดวงเล็บปีกกาที่สองสร้างอินสแตนซ์ initializers เช่นบล็อกแบบคงที่

เช่นเดียวกับคนอื่น ๆ ชี้ไปมันไม่ปลอดภัยที่จะใช้

อย่างไรก็ตามคุณสามารถใช้ทางเลือกนี้เพื่อเริ่มต้นการรวบรวมได้เสมอ

  • Java 8
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
  • Java 9
List<String> list = List.of("A", "B", "C");

-1

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


ไม่ได้จริงๆ นั่นก็เหมือนกับการบอกว่าการสร้างคลาสใหม่เป็นวิธีการเปลี่ยนสิ่งที่thisเป็น ไวยากรณ์เพิ่งสร้างคลาสที่ไม่ระบุชื่อ (ดังนั้นการอ้างอิงใด ๆthisจะอ้างถึงวัตถุของคลาสนิรนามใหม่นั้น) จากนั้นใช้บล็อก initializer {...}เพื่อเริ่มต้นอินสแตนซ์ที่สร้างขึ้นใหม่
grinch
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.