ประสิทธิภาพของ Java“ การรั้งสองครั้งเริ่มต้น”?


823

ในคุณสมบัติที่ซ่อนของ Javaคำตอบด้านบนกล่าวถึงคู่รั้งการเริ่มต้นด้วยมากไวยากรณ์ล่อลวง:

Set<String> flavors = new HashSet<String>() {{
    add("vanilla");
    add("strawberry");
    add("chocolate");
    add("butter pecan");
}};

สำนวนนี้สร้างคลาสภายในที่ไม่ระบุชื่อโดยมีเพียง initializer อินสแตนซ์ซึ่งมัน "สามารถใช้วิธีการใด ๆ [... ] ในขอบเขตที่มี"

คำถามหลัก: นี่มันไม่มีประสิทธิภาพเท่าที่ฟังดูไหม? การใช้งานควรถูก จำกัด เพียงการเริ่มต้นครั้งเดียวหรือไม่? (และแน่นอนแสดงออก!)

คำถามที่สอง: HashSet ใหม่จะต้องเป็น "this" ที่ใช้ใน initializer อินสแตนซ์ ... ทุกคนสามารถแสดงความเห็นต่อกลไกได้หรือไม่?

คำถามที่สาม: สำนวนนี้คลุมเครือเกินไปที่จะใช้ในรหัสการผลิตหรือไม่

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

ตามคำถาม (1) รหัสที่สร้างขึ้นควรทำงานได้อย่างรวดเร็ว ไฟล์. class เพิ่มเติมทำให้เกิดความวุ่นวายของไฟล์ jar และการเริ่มต้นโปรแกรมช้าลงเล็กน้อย (ขอบคุณ @coobird สำหรับการวัดผล) @Thilo ชี้ให้เห็นว่าการเก็บรวบรวมขยะอาจได้รับผลกระทบและต้นทุนหน่วยความจำสำหรับคลาสที่โหลดเพิ่มอาจเป็นปัจจัยในบางกรณี

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

โดยรวมแล้ว DBI ทำให้ฉันเป็นสิ่งที่สงสัยทางปัญญา Coobird และคนอื่น ๆ ชี้ให้เห็นว่าคุณสามารถได้รับผลเช่นเดียวกันกับ Arrays.asList, วิธีการแปรผัน, คอลเลกชันของ Google และตัวอักษร Java 7 Collection ที่เสนอ ภาษา JVM ที่ใหม่กว่าเช่น Scala, JRuby และ Groovy ยังมีสัญลักษณ์ที่กระชับสำหรับการสร้างรายการและทำงานร่วมกันได้ดีกับ Java เนื่องจาก DBI จัดกลุ่ม classpath ให้ช้าลงการโหลดคลาสเล็กน้อยและทำให้โค้ดดูไม่ชัดเจนมากขึ้นฉันอาจอายไปจากมัน อย่างไรก็ตามฉันวางแผนที่จะสปริงเรื่องนี้กับเพื่อนที่เพิ่งได้รับ SCJP ของเขาและชอบการแข่งขันที่มีอัธยาศัยที่ดีเกี่ยวกับความหมายของ Java! ;-) ขอบคุณทุกคน!

7/2017: Baeldung มีการสรุปที่ดีของการเริ่มต้นวงเล็บปีกกาคู่และถือว่าเป็นรูปแบบการต่อต้าน

12/2017: @Basil Bourque ตั้งข้อสังเกตว่าใน Java 9 ใหม่คุณสามารถพูดได้:

Set<String> flavors = Set.of("vanilla", "strawberry", "chocolate", "butter pecan");

นั่นคือหนทางที่แน่นอน หากคุณติดอยู่กับรุ่นก่อนหน้านี้จะดูที่Google คอลเลกชัน ImmutableSet


33
รหัสกลิ่นฉันเห็นที่นี่คือว่าผู้อ่านไร้เดียงสาจะคาดหวังว่าflavorsจะเป็นHashSetแต่ทว่ามันเป็นคลาสย่อยที่ไม่ระบุชื่อ
Elazar Leibovich

6
หากคุณพิจารณาว่าจะใช้งานแทนการโหลดประสิทธิภาพไม่มีความแตกต่างให้ดูคำตอบของฉัน
Peter Lawrey

4
ฉันรักที่คุณสร้างบทสรุปฉันคิดว่านี่เป็นแบบฝึกหัดที่คุ้มค่าสำหรับคุณทั้งสองเพื่อเพิ่มความเข้าใจและชุมชน
Patrick Murphy

3
มันไม่ชัดเจนในความคิดของฉัน ผู้อ่านควรรู้ว่าคู่ ... o รอ @ElazarLeibovich แล้วกล่าวว่าในความคิดเห็นของเขา วงเล็บปีกกา initializer ตัวเองไม่ได้อยู่ในรูปแบบของภาษามันเป็นเพียงการรวมกันของคลาสย่อยที่ไม่ระบุชื่อและตัวเริ่มต้นอินสแตนซ์ สิ่งเดียวคือที่ผู้คนจะต้องตระหนักถึงเรื่องนี้
MC Emperor

8
Java 9 เสนอวิธีการแบบคงที่จากโรงงานที่ไม่เปลี่ยนรูปแบบซึ่งอาจแทนที่การใช้ DCI ในบางสถานการณ์:Set<String> flavors = Set.of( "vanilla" , "strawberry" , "chocolate" , "butter pecan" ) ;
Basil Bourque

คำตอบ:


607

นี่คือปัญหาเมื่อฉันได้รับการดำเนินการไปด้วยชั้นในที่ไม่ระบุชื่อ:

2009/05/27  16:35             1,602 DemoApp2$1.class
2009/05/27  16:35             1,976 DemoApp2$10.class
2009/05/27  16:35             1,919 DemoApp2$11.class
2009/05/27  16:35             2,404 DemoApp2$12.class
2009/05/27  16:35             1,197 DemoApp2$13.class

/* snip */

2009/05/27  16:35             1,953 DemoApp2$30.class
2009/05/27  16:35             1,910 DemoApp2$31.class
2009/05/27  16:35             2,007 DemoApp2$32.class
2009/05/27  16:35               926 DemoApp2$33$1$1.class
2009/05/27  16:35             4,104 DemoApp2$33$1.class
2009/05/27  16:35             2,849 DemoApp2$33.class
2009/05/27  16:35               926 DemoApp2$34$1$1.class
2009/05/27  16:35             4,234 DemoApp2$34$1.class
2009/05/27  16:35             2,849 DemoApp2$34.class

/* snip */

2009/05/27  16:35               614 DemoApp2$40.class
2009/05/27  16:35             2,344 DemoApp2$5.class
2009/05/27  16:35             1,551 DemoApp2$6.class
2009/05/27  16:35             1,604 DemoApp2$7.class
2009/05/27  16:35             1,809 DemoApp2$8.class
2009/05/27  16:35             2,022 DemoApp2$9.class

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

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

พิจารณาว่า Java Virtual Machine จะต้องอ่านคลาสเหล่านั้นทั้งหมดเมื่อใช้พวกเขาซึ่งอาจนำไปสู่เวลาในกระบวนการvertec การ bytecodeและเช่น ไม่ต้องพูดถึงการเพิ่มขึ้นของพื้นที่ดิสก์ที่ต้องการเพื่อจัดเก็บสิ่งเหล่านั้นทั้งหมดclassไฟล์ทั้งหมด

ดูเหมือนว่ามีค่าใช้จ่ายเล็กน้อยเมื่อใช้การเตรียมใช้งานแบบ double-brace ดังนั้นจึงไม่ใช่ความคิดที่ดีที่จะลงเรือเกินไป แต่ดังที่เอ็ดดี้ได้ระบุไว้ในความคิดเห็นมันเป็นไปไม่ได้ที่จะแน่ใจได้ถึงผลกระทบ


สำหรับการอ้างอิงการเตรียมใช้งานวงเล็บปีกกาคู่มีดังต่อไปนี้:

List<String> list = new ArrayList<String>() {{
    add("Hello");
    add("World!");
}};

ดูเหมือนว่าคุณลักษณะ "ซ่อนอยู่" ของ Java แต่เป็นเพียงการเขียนใหม่ของ:

List<String> list = new ArrayList<String>() {

    // Instance initialization block
    {
        add("Hello");
        add("World!");
    }
};

ดังนั้นโดยพื้นฐานแล้วมันคือบล็อกการเริ่มต้นอินสแตนซ์ที่เป็นส่วนหนึ่งของระดับชั้นที่ไม่ระบุชื่อ


ข้อเสนอการรวบรวมตัวอักษรของ Joshua Bloch สำหรับProject Coinเป็นไปตาม:

List<Integer> intList = [1, 2, 3, 4];

Set<String> strSet = {"Apple", "Banana", "Cactus"};

Map<String, Integer> truthMap = { "answer" : 42 };

น่าเศร้าที่มันไม่ได้เกิดขึ้นกลายเป็น Java 7 และ 8 และถูกเก็บไปเรื่อย ๆ


การทดลอง

นี่คือการทดลองง่ายๆที่ฉันทดสอบ - สร้าง 1,000 ArrayLists ด้วยองค์ประกอบ"Hello"และ"World!"เพิ่มเข้าไปในพวกเขาผ่านทางaddวิธีการโดยใช้สองวิธี:

วิธีที่ 1: การเตรียมใช้งานวงเล็บปีกกาคู่

List<String> l = new ArrayList<String>() {{
  add("Hello");
  add("World!");
}};

วิธีที่ 2: สร้างอินสแตนซ์ArrayListและadd

List<String> l = new ArrayList<String>();
l.add("Hello");
l.add("World!");

ฉันสร้างโปรแกรมอย่างง่ายเพื่อเขียนไฟล์ต้นฉบับ Java เพื่อทำการเริ่มต้น 1,000 ครั้งโดยใช้สองวิธี:

ทดสอบ 1:

class Test1 {
  public static void main(String[] s) {
    long st = System.currentTimeMillis();

    List<String> l0 = new ArrayList<String>() {{
      add("Hello");
      add("World!");
    }};

    List<String> l1 = new ArrayList<String>() {{
      add("Hello");
      add("World!");
    }};

    /* snip */

    List<String> l999 = new ArrayList<String>() {{
      add("Hello");
      add("World!");
    }};

    System.out.println(System.currentTimeMillis() - st);
  }
}

ทดสอบ 2:

class Test2 {
  public static void main(String[] s) {
    long st = System.currentTimeMillis();

    List<String> l0 = new ArrayList<String>();
    l0.add("Hello");
    l0.add("World!");

    List<String> l1 = new ArrayList<String>();
    l1.add("Hello");
    l1.add("World!");

    /* snip */

    List<String> l999 = new ArrayList<String>();
    l999.add("Hello");
    l999.add("World!");

    System.out.println(System.currentTimeMillis() - st);
  }
}

โปรดทราบว่าเวลาที่ผ่านไปเพื่อเริ่มต้น 1000 ArrayLists และการขยายชั้นในแบบไม่ระบุชื่อ 1,000 ครั้งArrayListนั้นถูกตรวจสอบโดยใช้System.currentTimeMillisดังนั้นตัวจับเวลาจึงไม่มีความละเอียดสูงมาก สำหรับระบบ Windows ของฉันความละเอียดประมาณ 15-16 มิลลิวินาที

ผลลัพธ์สำหรับการทดสอบ 10 ครั้งของการทดสอบสองครั้งมีดังนี้:

Test1 Times (ms)           Test2 Times (ms)
----------------           ----------------
           187                          0
           203                          0
           203                          0
           188                          0
           188                          0
           187                          0
           203                          0
           188                          0
           188                          0
           203                          0

ดังที่เห็นได้ว่าการเริ่มต้นวงเล็บปีกกาคู่นั้นมีเวลาดำเนินการที่สังเกตได้ประมาณ 190 ms

ในขณะเดียวกันArrayListเวลาดำเนินการเตรียมใช้งานก็เป็น 0 ms แน่นอนความละเอียดของตัวจับเวลาควรถูกนำมาพิจารณา แต่มีแนวโน้มที่จะต่ำกว่า 15 มิลลิวินาที

ดังนั้นจึงมีความแตกต่างที่เห็นได้ชัดเจนในเวลาดำเนินการของทั้งสองวิธี ปรากฏว่ามีค่าใช้จ่ายในการเริ่มต้นสองวิธี

และใช่มี.classไฟล์1000 ไฟล์ที่สร้างขึ้นโดยการรวบรวมTest1โปรแกรมทดสอบการเริ่มต้นการรั้งสองครั้ง


10
"อาจจะ" เป็นคำผ่าตัด ยกเว้นว่าวัดได้ไม่มีข้อความเกี่ยวกับประสิทธิภาพที่มีความหมาย
อินสแตนซ์ฮันเตอร์

16
คุณทำผลงานได้ดีมากฉันแทบจะไม่อยากพูดแบบนี้ แต่ครั้ง Test1 อาจถูกครอบงำด้วยการโหลดคลาส มันน่าสนใจที่จะเห็นบางคนเรียกใช้หนึ่งอินสแตนซ์ของการทดสอบแต่ละครั้งในลูปบอกว่า 1,000 ครั้งจากนั้นรันอีกครั้งในหนึ่งวินาทีสำหรับลูป 1,000 หรือ 10,000 ครั้งแล้วพิมพ์ความแตกต่างของเวลา (System.nanoTime ()) สิ่งแรกสำหรับลูปควรผ่านเอฟเฟกต์การวอร์มร่างกายทั้งหมด (JIT คลาสโหลดเป็นต้น) การทดสอบทั้งสองแบบนั้นแตกต่างกันไปแล้วแต่กรณี ฉันจะพยายามวิ่งวันนี้ในที่ทำงาน
Jim Ferrans

8
@Jim Ferrans: ฉันค่อนข้างแน่ใจว่าเวลา Test1 นั้นมาจากการโหลดในคลาส แต่ผลที่ตามมาของการใช้การเตรียมใช้งานวงเล็บปีกกาคู่จะต้องรับมือจากการโหลดคลาส ฉันเชื่อว่ากรณีการใช้งานส่วนใหญ่สำหรับ init brace คู่ สำหรับการเริ่มต้นครั้งเดียวการทดสอบนั้นใกล้เคียงกับสภาพการใช้งานทั่วไปของการเริ่มต้นประเภทนี้ ฉันเชื่อว่าการทำซ้ำหลายครั้งในการทดสอบแต่ละครั้งจะทำให้ช่องว่างเวลาดำเนินการมีขนาดเล็กลง
coobird

73
สิ่งนี้พิสูจน์ได้ว่าก) การเตรียมใช้งานแบบ double-brace นั้นช้ากว่าและ b) แม้ว่าคุณจะทำ 1,000 ครั้งคุณอาจไม่สังเกตเห็นความแตกต่าง และมันก็ไม่ได้เป็นเช่นนี้อาจเป็นคอขวดในวงด้านในเช่นกัน มันกำหนดโทษครั้งเดียวเล็ก ๆ น้อย ๆ ที่แย่ที่สุด
Michael Myers

15
หากการใช้ DBI ทำให้โค้ดอ่านหรือแสดงความหมายได้มากกว่าให้ใช้มัน ความจริงที่ว่ามันเพิ่มงานที่ JVM ต้องทำเล็กน้อยไม่ใช่อาร์กิวเมนต์ที่ถูกต้อง ถ้ามันเป็นแล้วเราควรที่จะกังวลเกี่ยวกับวิธีการพิเศษผู้ช่วย / เรียนเลือกเรียนมากแทนด้วยวิธีการที่น้อยลง ...
Rogério

105

หนึ่งในคุณสมบัติของวิธีนี้ที่ยังไม่ได้ชี้ให้เห็นคือเนื่องจากคุณสร้างคลาสภายในคลาสที่มีทั้งทั้งหมดจะถูกบันทึกไว้ในขอบเขต ซึ่งหมายความว่าตราบใดที่ชุดของคุณยังมีชีวิตอยู่มันจะรักษาตัวชี้ไปยังอินสแตนซ์ที่มี ( this$0) และป้องกันไม่ให้มีการรวบรวมขยะซึ่งอาจเป็นปัญหา

นี่คือความจริงที่ว่าคลาสใหม่ถูกสร้างขึ้นตั้งแต่แรกแม้ว่า HashSet ปกติจะใช้งานได้ดี (หรือดีกว่า) ทำให้ฉันไม่ต้องการใช้โครงสร้างนี้

คำถามที่สอง: HashSet ใหม่จะต้องเป็น "this" ที่ใช้ใน initializer อินสแตนซ์ ... ทุกคนสามารถแสดงความเห็นต่อกลไกได้หรือไม่? ฉันคาดหวังอย่างไร้เดียงสาว่า "สิ่งนี้" เพื่ออ้างถึงการเริ่มต้นของ "รสชาติ" ของวัตถุ

นี่เป็นวิธีการทำงานของชั้นเรียนภายใน พวกเขาได้รับของพวกเขาเองthisแต่พวกเขายังมีตัวชี้ไปยังอินสแตนซ์หลักเพื่อให้คุณสามารถเรียกใช้เมธอดบนวัตถุที่มีเช่นกัน ในกรณีที่มีความขัดแย้งของการตั้งชื่อคลาสภายใน (ในกรณีของคุณ HashSet) จะมีความสำคัญกว่า แต่คุณสามารถนำหน้า "this" ด้วย classname เพื่อรับเมธอดด้านนอกได้เช่นกัน

public class Test {

    public void add(Object o) {
    }

    public Set<String> makeSet() {
        return new HashSet<String>() {
            {
              add("hello"); // HashSet
              Test.this.add("hello"); // outer instance 
            }
        };
    }
}

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

    public Set<String> makeSet() {
        return new HashSet<String>() {
            {
              add("hello"); // not HashSet anymore ...
            }

            @Override
            boolean add(String s){

            }

        };
    }

5
จุดที่ดีมากในการอ้างอิงที่ซ่อนอยู่ในชั้นเรียนที่มี ในตัวอย่างดั้งเดิมตัวเริ่มต้นอินสแตนซ์กำลังเรียกใช้เมธอด add () ของ HashSet <String> ใหม่ไม่ใช่ Test.this.add () นั่นแสดงให้ฉันเห็นว่ามีบางอย่างกำลังเกิดขึ้น มีคลาสภายในที่ไม่ระบุชื่อสำหรับ HashSet <String> ตามที่ Nathan Kitchen แนะนำหรือไม่
Jim Ferrans

การอ้างอิงไปยังคลาสที่ประกอบด้วยอาจเป็นอันตรายหาก serialization ของ datastructure เกี่ยวข้อง คลาสที่มีอยู่จะถูกทำให้เป็นอนุกรมและจะต้องเป็น Serializable สิ่งนี้สามารถนำไปสู่ข้อผิดพลาดที่ชัดเจน
อีกโปรแกรมเมอร์ Java หนึ่ง

56

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

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

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


7
บันทึกลูกแมว! คำตอบที่ดี!
Aris2World

36

เข้าชั้นเรียนทดสอบต่อไปนี้:

public class Test {
  public void test() {
    Set<String> flavors = new HashSet<String>() {{
        add("vanilla");
        add("strawberry");
        add("chocolate");
        add("butter pecan");
    }};
  }
}

แล้ว decompiling ไฟล์ class ฉันเห็น:

public class Test {
  public void test() {
    java.util.Set flavors = new HashSet() {

      final Test this$0;

      {
        this$0 = Test.this;
        super();
        add("vanilla");
        add("strawberry");
        add("chocolate");
        add("butter pecan");
      }
    };
  }
}

สิ่งนี้ดูไม่ไร้ประสิทธิภาพสำหรับฉัน ถ้าฉันกังวลเกี่ยวกับการแสดงบางอย่างเช่นนี้ฉันจะบันทึกมัน และคำถาม # 2 ของคุณได้รับคำตอบจากโค้ดด้านบน: คุณอยู่ใน Constructor (และ initializer อินสแตนซ์) สำหรับคลาสภายในของคุณดังนั้น "thisอ้างอิงถึงคลาสภายในนี้

ใช่ไวยากรณ์นี้ไม่ชัดเจน แต่ความคิดเห็นสามารถชี้แจงการใช้ไวยากรณ์ที่ไม่ชัดเจน เพื่อชี้แจงไวยากรณ์คนส่วนใหญ่คุ้นเคยกับบล็อก initializer แบบคงที่ (JLS 8.7 Initializers แบบคงที่):

public class Sample1 {
    private static final String someVar;
    static {
        String temp = null;
        ..... // block of code setting temp
        someVar = temp;
    }
}

คุณยังสามารถใช้ไวยากรณ์ที่คล้ายกัน (โดยไม่มีคำว่า " static") สำหรับการใช้งานคอนสตรัคเตอร์ (JLS 8.6 Instance Initializers) แม้ว่าฉันไม่เคยเห็นสิ่งนี้ใช้ในรหัสการผลิต เรื่องนี้ไม่ค่อยเป็นที่รู้จักมากนัก

public class Sample2 {
    private final String someVar;

    // This is an instance initializer
    {
        String temp = null;
        ..... // block of code setting temp
        someVar = temp;
    }
}

หากคุณไม่มีคอนสตรัคเตอร์เริ่มต้นบล็อกของโค้ดระหว่าง{และ}จะกลายเป็นตัวสร้างโดยคอมไพเลอร์ ด้วยสิ่งนี้ในใจคลี่คลายรหัสวงเล็บปีกกาสอง:

public void test() {
  Set<String> flavors = new HashSet<String>() {
      {
        add("vanilla");
        add("strawberry");
        add("chocolate");
        add("butter pecan");
      }
  };
}

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

public void test() {
  Set<String> flavors = new MyHashSet();
}

class MyHashSet extends HashSet<String>() {
    public MyHashSet() {
        add("vanilla");
        add("strawberry");
        add("chocolate");
        add("butter pecan");
    }
}

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

อีกครั้งสำหรับคำถาม # 2 ของคุณโค้ดด้านบนคือการเทียบเคียงเริ่มต้นแบบลอจิกและแบบลอจิคัลที่ชัดเจนและชัดเจนและมันทำให้เห็นได้อย่างชัดเจนว่า " this" หมายถึงอะไร: ถึงคลาสภายในที่ขยายHashSetการระดับชั้นที่ขยาย

หากคุณมีคำถามเกี่ยวกับรายละเอียดของ initializers อินสแตนซ์ให้ตรวจสอบรายละเอียดในเอกสารประกอบJLS


เอ็ดดี้คำอธิบายที่ดีมาก หากรหัส JVM มีความสะอาดเท่ากับการแยกส่วนความเร็วในการประมวลผลจะเร็วพอแม้ว่าฉันจะค่อนข้างกังวลเกี่ยวกับความยุ่งเหยิงของไฟล์. class เพิ่มเติม ฉันยังสงสัยว่าทำไมตัวสร้างของ initializer อินสแตนซ์จึงเห็นว่า "นี่" เป็นอินสแตนซ์ HashSet <String> ใหม่ไม่ใช่อินสแตนซ์ทดสอบ นี่เป็นพฤติกรรมที่ระบุไว้อย่างชัดเจนใน Java Language Specification ล่าสุดเพื่อสนับสนุนสำนวนหรือไม่
Jim Ferrans

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

1
@ จิมการตีความคำว่า "นี่" ไม่ใช่กรณีพิเศษ มันหมายถึงอินสแตนซ์ของคลาสที่ปิดท้ายสุดซึ่งเป็นคลาสย่อยที่ไม่ระบุชื่อของ HashSet <String>
นาธานครัว

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

1
@ chiastic-security: บางครั้ง decompilers สร้างรหัสที่จะไม่คอมไพล์
Eddie

35

รั่วไหลได้ง่าย

ฉันได้ตัดสินใจที่จะเข้าร่วมผลกระทบของประสิทธิภาพรวมถึง: การทำงานของดิสก์ + เปิดเครื่องรูด (สำหรับ jar), การตรวจสอบระดับ, พื้นที่เพิ่มระดับ (สำหรับ JVM ฮอตสปอตของ Sun) อย่างไรก็ตามที่เลวร้ายที่สุดของทั้งหมด: มันรั่วไหลได้ง่าย คุณไม่สามารถกลับมาได้ง่ายๆ

Set<String> getFlavors(){
  return Collections.unmodifiableSet(flavors)
}

ดังนั้นหากชุดหนีไปยังส่วนอื่นที่โหลดโดย classloader ที่แตกต่างกันและการอ้างอิงจะถูกเก็บไว้ที่นั่นต้นไม้ทั้งหมดของคลาส + classloader จะถูกรั่วไหลออกมา เพื่อหลีกเลี่ยงที่จะสำเนา HashMap new LinkedHashSet(new ArrayList(){{add("xxx);add("yyy");}})เป็นสิ่งที่จำเป็น ไม่น่ารักอีกต่อไป ฉันไม่ได้ใช้สำนวนตัวเองแทนมันเป็นเหมือน new LinkedHashSet(Arrays.asList("xxx","YYY"));


3
โชคดีที่ใน Java 8 นั้น PermGen ไม่มีอีกแล้ว ยังคงมีผลกระทบอยู่ฉันเดา แต่ไม่ใช่ข้อความที่มีข้อผิดพลาดที่คลุมเครือมาก
Joey

2
@ โจอี้สร้างความแตกต่างเป็นศูนย์ถ้าหน่วยความจำได้รับการจัดการโดยตรงโดย GC (ระดับ gen) หรือไม่ การรั่วไหลของ metaspace ยังคงเป็นการรั่วเว้นแต่ว่า meta ถูก จำกัด จะไม่มี OOM (จากระดับการดัด) โดยสิ่งต่าง ๆ เช่น oom_killer ใน linux กำลังจะเตะ
bestsss

19

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

package vanilla.java.perfeg.doublebracket;

import java.util.*;

/**
 * @author plawrey
 */
public class DoubleBracketMain {
    public static void main(String... args) {
        final List<String> list1 = new ArrayList<String>() {
            {
                add("Hello");
                add("World");
                add("!!!");
            }
        };
        List<String> list2 = new ArrayList<String>(list1);
        Set<String> set1 = new LinkedHashSet<String>() {
            {
                addAll(list1);
            }
        };
        Set<String> set2 = new LinkedHashSet<String>();
        set2.addAll(list1);
        Map<Integer, String> map1 = new LinkedHashMap<Integer, String>() {
            {
                put(1, "one");
                put(2, "two");
                put(3, "three");
            }
        };
        Map<Integer, String> map2 = new LinkedHashMap<Integer, String>();
        map2.putAll(map1);

        for (int i = 0; i < 10; i++) {
            long dbTimes = timeComparison(list1, list1)
                    + timeComparison(set1, set1)
                    + timeComparison(map1.keySet(), map1.keySet())
                    + timeComparison(map1.values(), map1.values());
            long times = timeComparison(list2, list2)
                    + timeComparison(set2, set2)
                    + timeComparison(map2.keySet(), map2.keySet())
                    + timeComparison(map2.values(), map2.values());
            if (i > 0)
                System.out.printf("double braced collections took %,d ns and plain collections took %,d ns%n", dbTimes, times);
        }
    }

    public static long timeComparison(Collection a, Collection b) {
        long start = System.nanoTime();
        int runs = 10000000;
        for (int i = 0; i < runs; i++)
            compareCollections(a, b);
        long rate = (System.nanoTime() - start) / runs;
        return rate;
    }

    public static void compareCollections(Collection a, Collection b) {
        if (!a.equals(b) && a.hashCode() != b.hashCode() && !a.toString().equals(b.toString()))
            throw new AssertionError();
    }
}

พิมพ์

double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 34 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns

2
ไม่แตกต่างกันยกเว้นว่าพื้นที่ PermGen ของคุณจะระเหยไปหาก DBI ถูกใช้งานมากเกินไป อย่างน้อยที่สุดจะเป็นการยกเว้นว่าคุณจะตั้งค่าตัวเลือก JVM ที่คลุมเครือเพื่อให้สามารถยกเลิกการโหลดคลาสและการรวบรวมขยะของพื้นที่ PermGen ได้ เมื่อพิจารณาจากความชุกของ Java เป็นภาษาฝั่งเซิร์ฟเวอร์ปัญหาหน่วยความจำ / PermGen รับประกันอย่างน้อยก็พูดถึง
aroth

1
@aroth นี่เป็นจุดที่ดี ฉันยอมรับว่าใน 16 ปีของการทำงานกับ Java ฉันไม่เคยทำงานในระบบที่คุณต้องปรับแต่ง PermGen (หรือ Metaspace) สำหรับระบบที่ฉันทำงานกับขนาดของรหัสนั้นมีขนาดเล็กพอสมควร
Peter Lawrey

2
ไม่ควรcompareCollectionsรวมกับเงื่อนไข||มากกว่า&&? การใช้งาน&&ดูเหมือนจะไม่เพียง แต่มีความผิดทางความหมายเท่านั้น แต่ยังเป็นการต่อต้านความตั้งใจในการวัดประสิทธิภาพเนื่องจากจะมีการทดสอบเงื่อนไขแรกเท่านั้น นอกจากนี้เครื่องมือเพิ่มประสิทธิภาพอัจฉริยะสามารถรับรู้ว่าเงื่อนไขจะไม่เปลี่ยนแปลงในระหว่างการทำซ้ำ
Holger

@aroth เช่นเดียวกับการอัปเดต: เนื่องจาก Java 8 VM ไม่ได้ใช้ perm-gen ใด ๆ อีกต่อไป
Angel O'Sphere

16

ในการสร้างชุดคุณสามารถใช้วิธีโรงงาน varargs แทนการเริ่มต้นสองครั้งรั้ง:

public static Set<T> setOf(T ... elements) {
    return new HashSet<T>(Arrays.asList(elements));
}

ห้องสมุด Google Collections มีวิธีอำนวยความสะดวกมากมายเช่นนี้รวมถึงฟังก์ชันที่มีประโยชน์อื่น ๆ อีกมากมาย

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


ฮะ! ;-) จริงๆแล้วฉันเป็น Rip van Winkle กลับสู่ Java จาก 1.2 วัน (ฉันเขียนเว็บเบราว์เซอร์เสียง VoiceXML ที่evolution.voxeo.comใน Java) มันสนุกกับการเรียนรู้ข้อมูลทั่วไปประเภทพารามิเตอร์ชนิดคอลเลกชัน, java.util.concurrent, ไวยากรณ์ซิงเกิ้ลใหม่และอื่น ๆ ตอนนี้มันเป็นภาษาที่ดีกว่าแล้ว ถึงจุดของคุณแม้ว่ากลไกหลัง DBI อาจดูคลุมเครือในตอนแรกความหมายของรหัสควรจะชัดเจน
Jim Ferrans

10

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

อีกวิธีหนึ่งในการสร้างการประกาศรายการโดยเฉพาะคือการใช้Arrays.asList(T ...)เช่น:

List<String> aList = Arrays.asList("vanilla", "strawberry", "chocolate");

ข้อ จำกัด ของวิธีการนี้แน่นอนว่าคุณไม่สามารถควบคุมประเภทรายการเฉพาะที่จะสร้างได้


1
Arrays.asList () เป็นสิ่งที่ฉันจะใช้ตามปกติ แต่คุณพูดถูกสถานการณ์นี้ส่วนใหญ่เกิดขึ้นในการทดสอบหน่วย รหัสจริงจะสร้างรายการจากแบบสอบถาม DB, XML และอื่น ๆ
Jim Ferrans

7
ระวัง asList แต่: รายการที่ส่งคืนไม่รองรับการเพิ่มหรือลบองค์ประกอบ เมื่อใดก็ตามที่ฉันใช้ asList ฉันจะส่งรายการผลลัพธ์ไปยังตัวสร้างที่ต้องการnew ArrayList<String>(Arrays.asList("vanilla", "strawberry", "chocolate"))แก้ไขปัญหานี้
Michael Myers

7

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

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

ใน (2) คุณอยู่ในตัวสร้างของวัตถุดังนั้น "นี่" หมายถึงวัตถุที่คุณกำลังสร้าง นั่นไม่ต่างกับตัวสร้างอื่น ๆ

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


5

การเริ่มต้นสองครั้งคือการแฮ็คที่ไม่จำเป็นซึ่งสามารถแนะนำการรั่วไหลของหน่วยความจำและปัญหาอื่น ๆ

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

ตัวอย่างในคำถามจะกลายเป็น:

Set<String> flavors = ImmutableSet.of(
    "vanilla", "strawberry", "chocolate", "butter pecan");

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

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

เกิดข้อผิดพลาดในขณะนี้ธงนี้ต่อต้านรูปแบบ


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

1
ฉันจะบอกว่ามันเดือดลงไปที่ "ใช้เครื่องมือที่เหมาะสมสำหรับงานมากกว่าแฮ็คที่อาจทำให้แอปพลิเคชันของคุณเสียหาย" Guava เป็นห้องสมุดทั่วไปที่น่าสนใจสำหรับแอปพลิเคชันที่จะรวม (คุณพลาดไม่ได้หากคุณไม่ได้ใช้) แต่แม้ว่าคุณไม่ต้องการใช้มันคุณก็สามารถทำได้และควรหลีกเลี่ยงการเริ่มต้นด้วยวงเล็บปีกกาสองครั้ง
dimo414

และการเริ่มต้นวงเล็บปีกกาว่าจะทำให้ meory รั่วอย่างแน่นอน?
Angel O'Sphere

@ AngelO'Sphere DBI เป็นวิธีที่สร้างความสับสนในการสร้างคลาสภายในและยังคงมีการอ้างอิงโดยนัยถึงคลาสที่ปิดล้อม (ยกเว้นในกรณีที่เคยใช้ในstaticบริบทเท่านั้น) ลิงก์ Error-Prone ที่ด้านล่างของคำถามของฉันพูดถึงสิ่งนี้เพิ่มเติม
dimo414

ฉันจะบอกว่ามันเป็นเรื่องของรสนิยม และมันก็ไม่ได้ทำให้งงงวยเกี่ยวกับเรื่องนี้
Angel O'Sphere

4

ฉันค้นคว้าสิ่งนี้และตัดสินใจที่จะทำการทดสอบเชิงลึกมากกว่าคำตอบที่ถูกต้อง

นี่คือรหัส: https://gist.github.com/4368924

และนี่คือข้อสรุปของฉัน

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

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

อย่างไรก็ตามเป็นที่ชัดเจนว่าในกรณีส่วนใหญ่การสร้างรายการเดียวนั้นมีค่าโสหุ้ยไม่มากนักแม้จะเป็นจำนวนมากก็ตาม

สิ่งหนึ่งที่ย้อนกลับมาคือความจริงที่ว่าการเริ่มต้นวงเล็บปีกกาสองครั้งแต่ละครั้งสร้างไฟล์คลาสใหม่ที่เพิ่มดิสก์บล็อกทั้งหมดให้มีขนาดเท่ากับแอปพลิเคชันของเรา (หรือประมาณ 1k เมื่อถูกบีบอัด) รอยเท้าเล็ก ๆ แต่ถ้ามันถูกใช้ในหลาย ๆ ที่มันอาจส่งผลกระทบได้ ใช้ 1,000 ครั้งนี้และคุณอาจเพิ่ม MiB ทั้งหมดลงในใบสมัครของคุณซึ่งอาจเกี่ยวข้องกับสภาพแวดล้อมแบบฝังตัว

ข้อสรุปของฉัน? สามารถใช้งานได้ตราบใดที่ยังไม่ถูกใช้งาน

แจ้งให้เราทราบสิ่งที่คุณคิด :)


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

3

ฉันคำตอบของ Nat สองยกเว้นฉันจะใช้วนรอบแทนการสร้างและทันที tossing รายการโดยนัยจาก asList (องค์ประกอบ):

static public Set<T> setOf(T ... elements) {
    Set set=new HashSet<T>(elements.size());
    for(T elm: elements) { set.add(elm); }
    return set;
    }

1
ทำไม? วัตถุใหม่จะถูกสร้างขึ้นในพื้นที่ eden และต้องการเพียงแค่การเพิ่มตัวชี้สองหรือสามตัวในการสร้างอินสแตนซ์ JVM อาจสังเกตเห็นว่าไม่เคยหนีพ้นขอบเขตของเมธอดและจัดสรรให้บนสแต็ก
Nat

ใช่มีแนวโน้มที่จะมีประสิทธิภาพมากกว่ารหัสนั้น (แม้ว่าคุณจะสามารถปรับปรุงได้โดยการบอกHashSetความจุที่แนะนำ - จดจำตัวประกอบการโหลด)
Tom Hawtin - tackline

คอนสตรัคเตอร์ HashSet ต้องทำการวนซ้ำอยู่ดีดังนั้นมันจะไม่มีประสิทธิภาพน้อยลง รหัสห้องสมุดที่สร้างขึ้นเพื่อนำมาใช้ซ้ำควรพยายามให้ดีที่สุดเท่าที่จะทำได้
Lawrence Dol

3

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


3

Mario Gleichman อธิบายวิธีการใช้ Java 1.5 ฟังก์ชั่นทั่วไปเพื่อจำลองตัวอักษร Scala List แม้ว่าคุณจะจบลงด้วยการเปลี่ยนไม่ได้จบรายการที่

เขากำหนดชั้นนี้:

package literal;

public class collection {
    public static <T> List<T> List(T...elems){
        return Arrays.asList( elems );
    }
}

และใช้มันดังนั้น:

import static literal.collection.List;
import static system.io.*;

public class CollectionDemo {
    public void demoList(){
        List<String> slist = List( "a", "b", "c" );
        List<Integer> iList = List( 1, 2, 3 );
        for( String elem : List( "a", "java", "list" ) )
            System.out.println( elem );
    }
}

Google Collections ซึ่งเป็นส่วนหนึ่งของGuavaสนับสนุนแนวคิดที่คล้ายกันสำหรับการสร้างรายการ ในการสัมภาษณ์นี้ Jared Levy พูดว่า:

[... ] คุณสมบัติที่มีการใช้งานมากที่สุดซึ่งปรากฏในเกือบทุกคลาส Java ที่ฉันเขียนเป็นวิธีสแตติกที่ลดจำนวนการกดแป้นซ้ำ ๆ ในโค้ด Java ของคุณ มันสะดวกมากที่จะสามารถป้อนคำสั่งดังนี้:

Map<OneClassWithALongName, AnotherClassWithALongName> = Maps.newHashMap();

List<String> animals = Lists.immutableList("cat", "dog", "horse");

7/10/2014: ถ้าเพียง แต่มันอาจจะง่ายเหมือน Python:

animals = ['cat', 'dog', 'horse']

2/21/2020: ใน Java 11 ตอนนี้คุณสามารถพูดได้:

animals = List.of(“cat”, “dog”, “horse”)


2
  1. สิ่งนี้จะเรียกadd()สมาชิกแต่ละคน หากคุณสามารถหาวิธีที่มีประสิทธิภาพมากขึ้นในการใส่รายการลงในชุดแฮชให้ใช้วิธีนั้น โปรดสังเกตว่าชั้นในจะสร้างขยะหากคุณมีความรู้สึกไวต่อเรื่องนั้น

  2. มันดูเหมือนว่าฉันเป็นถ้าบริบทเป็นวัตถุที่ส่งกลับโดยซึ่งเป็นnewHashSet

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

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