JavaRush /จาวาบล็อก /Random-TH /คอฟฟี่เบรค #130. วิธีทำงานกับอาร์เรย์ Java อย่างถูกต้อง -...

คอฟฟี่เบรค #130. วิธีทำงานกับอาร์เรย์ Java อย่างถูกต้อง - เคล็ดลับจาก Oracle

เผยแพร่ในกลุ่ม
ที่มา: Oracle การทำงานกับอาร์เรย์อาจรวมถึงนิพจน์การสะท้อน ข้อมูลทั่วไป และแลมบ์ดา เมื่อเร็ว ๆ นี้ฉันได้พูดคุยกับเพื่อนร่วมงานที่พัฒนาในภาษา C การสนทนาได้เปลี่ยนไปเป็นอาร์เรย์และวิธีการทำงานใน Java เมื่อเปรียบเทียบกับ C ฉันพบว่าสิ่งนี้แปลกเล็กน้อย เนื่องจาก Java ถือเป็นภาษาที่มีลักษณะคล้าย C จริงๆ แล้วมีความคล้ายคลึงกันมาก แต่ก็มีความแตกต่างเช่นกัน มาเริ่มกันง่ายๆ คอฟฟี่เบรค #130.  วิธีทำงานอย่างถูกต้องกับอาร์เรย์ Java - เคล็ดลับจาก Oracle - 1

การประกาศอาร์เรย์

หากคุณทำตามบทช่วยสอน Java คุณจะเห็นว่ามีสองวิธีในการประกาศอาร์เรย์ ข้อแรกตรงไปตรงมา:
int[] array; // a Java array declaration
คุณสามารถดูว่ามันแตกต่างจาก C อย่างไร โดยที่ไวยากรณ์คือ:
int array[]; // a C array declaration
กลับมาที่ Java อีกครั้ง หลังจากประกาศอาร์เรย์แล้ว คุณจะต้องจัดสรรมัน:
array = new int[10]; // Java array allocation
เป็นไปได้ไหมที่จะประกาศและเริ่มต้นอาร์เรย์พร้อมกัน? จริงๆแล้วไม่:
int[10] array; // NOPE, ERROR!
อย่างไรก็ตาม คุณสามารถประกาศและเริ่มต้นอาร์เรย์ได้ทันทีหากคุณทราบค่าแล้ว:
int[] array = { 0, 1, 1, 2, 3, 5, 8 };
จะทำอย่างไรถ้าคุณไม่ทราบความหมาย? นี่คือโค้ดที่คุณจะเห็นบ่อยที่สุดสำหรับการประกาศ จัดสรร และการใช้ อาร์เรย์ int :
int[] array;
array = new int[10];
array[0] = 0;
array[1] = 1;
array[2] = 1;
array[3] = 2;
array[4] = 3;
array[5] = 5;
array[6] = 8;
...
โปรดทราบว่าฉันได้ระบุ อาร์เรย์ intซึ่งเป็นอาร์เรย์ของ ประเภทข้อมูลดั้งเดิม ของ Java มาดูกันว่าจะเกิดอะไรขึ้นหากคุณลองกระบวนการเดียวกันกับอาร์เรย์ของอ็อบเจ็กต์ Java แทนที่จะเป็นแบบพื้นฐาน:
class SomeClass {
    int val;
    // …
}
SomeClass[] array = new SomeClass[10];
array[0].val = 0;
array[1].val = 1;
array[2].val = 1;
array[3].val = 2;
array[4].val = 3;
array[5].val = 5;
array[6].val = 8;
หากเรารันโค้ดข้างต้น เราจะได้รับข้อยกเว้นทันทีหลังจากพยายามใช้อิลิเมนต์แรกของอาร์เรย์ ทำไม แม้ว่าอาร์เรย์จะได้รับการจัดสรร แต่ละส่วนของอาร์เรย์จะมีการอ้างอิงอ็อบเจ็กต์ว่าง หากคุณป้อนรหัสนี้ลงใน IDE ของคุณ รหัสนั้นจะเติม .val ให้คุณโดยอัตโนมัติ ดังนั้นข้อผิดพลาดอาจทำให้เกิดความสับสน หากต้องการแก้ไขข้อบกพร่อง ให้ทำตามขั้นตอนเหล่านี้:
SomeClass[] array = new SomeClass[10];
for ( int i = 0; i < array.length; i++ ) {  //new code
    array[i] = new SomeClass();             //new code
}                                           //new code
array[0].val = 0;
array[1].val = 1;
array[2].val = 1;
array[3].val = 2;
array[4].val = 3;
array[5].val = 5;
array[6].val = 8;
แต่มันไม่สง่างาม ฉันสงสัยว่าทำไมฉันไม่สามารถจัดสรรอาร์เรย์และวัตถุภายในอาร์เรย์ที่มีโค้ดน้อยกว่าได้อย่างง่ายดาย บางทีทั้งหมดอาจอยู่ในบรรทัดเดียวด้วยซ้ำ เพื่อหาคำตอบ ฉันทำการทดลองหลายครั้ง

การค้นหานิพพานในอาร์เรย์ Java

เป้าหมายของเราคือการเขียนโค้ดอย่างหรูหรา ตามกฎของ "โค้ดที่สะอาด" ฉันตัดสินใจสร้างโค้ดที่นำมาใช้ซ้ำได้เพื่อล้างรูปแบบการจัดสรรอาร์เรย์ นี่เป็นการลองครั้งแรก:
public class MyArray {

    public static Object[] toArray(Class cls, int size)
      throws Exception {
        Constructor ctor = cls.getConstructors()[0];
        Object[] objects = new Object[size];
        for ( int i = 0; i < size; i++ ) {
            objects[i] = ctor.newInstance();
        }

        return objects;
    }

    public static void main(String[] args) throws Exception {
        SomeClass[] array1 = (SomeClass[])MyArray.toArray(SomeClass.class, 32); // see this
        System.out.println(array1);
    }
}
บรรทัดโค้ดที่ทำเครื่องหมายว่า "ดูสิ่งนี้" มีลักษณะตรงตามที่ฉันต้องการ ต้องขอบคุณการ ใช้งาน toArray วิธีการนี้ใช้การสะท้อนกลับเพื่อค้นหาคอนสตรัคเตอร์เริ่มต้นสำหรับคลาสที่จัดเตรียมไว้ จากนั้นจึงเรียกคอนสตรัคเตอร์นั้นเพื่อสร้างอินสแตนซ์ของอ็อบเจ็กต์ของคลาสนั้น กระบวนการเรียก Constructor หนึ่งครั้งสำหรับแต่ละองค์ประกอบอาร์เรย์ เลิศ! น่าเสียดายที่มันไม่ได้ผล โค้ดคอมไพล์ได้ดี แต่เกิด ข้อผิดพลาด ClassCastExceptionเมื่อรัน ในการใช้โค้ดนี้ คุณต้องสร้างอาร์เรย์ขององค์ประกอบ Object จากนั้นจึงแปลงแต่ละองค์ประกอบของอาร์เรย์เป็น คลาส SomeClassดังนี้:
Object[] objects = MyArray.toArray(SomeClass.class, 32);
SomeClass scObj = (SomeClass)objects[0];
...
นี่ไม่สง่างาม! หลังจากการทดลองเพิ่มเติม ฉันพัฒนาวิธีแก้ปัญหาหลายอย่างโดยใช้นิพจน์การสะท้อน ข้อมูลทั่วไป และแลมบ์ดา

โซลูชันที่ 1: ใช้การสะท้อน

ที่นี่เราใช้ คลาส java.lang.reflect.Array เพื่อสร้างอินส แตนซ์อาร์เรย์ของคลาสที่คุณระบุแทนที่จะใช้ คลาส java.lang.Object พื้นฐาน นี่เป็นการเปลี่ยนแปลงโค้ดบรรทัดเดียว:
public static Object[] toArray(Class cls, int size) throws Exception {
    Constructor ctor = cls.getConstructors()[0];
    Object array = Array.newInstance(cls, size);  // new code
    for ( int i = 0; i < size; i++ ) {
        Array.set(array, i, ctor.newInstance());  // new code
    }
    return (Object[])array;
}
คุณสามารถใช้วิธีนี้เพื่อรับอาร์เรย์ของคลาสที่ต้องการแล้วทำงานกับมันได้ดังนี้:
SomeClass[] array1 = (SomeClass[])MyArray.toArray(SomeClass.class, 32);
แม้ว่านี่จะไม่ใช่การเปลี่ยนแปลงที่จำเป็น แต่บรรทัดที่สองก็เปลี่ยนไปใช้คลาสการสะท้อนอาร์เรย์เพื่อตั้งค่าเนื้อหาของแต่ละองค์ประกอบอาร์เรย์ นี่มันอัศจรรย์มาก! แต่มีรายละเอียดอีกอย่างหนึ่งที่ดูเหมือนจะไม่ถูกต้องนัก นั่นคือนักแสดงของSomeClass[]ดูไม่ค่อยดีนัก โชคดีที่มีวิธีแก้ไขด้วยยาชื่อสามัญ

โซลูชันที่ 2: ใช้ยาสามัญ

กรอบงานคอลเลกชันใช้ข้อมูลทั่วไปสำหรับการเชื่อมโยงประเภทและกำจัดการส่งผ่านข้อมูลในการดำเนินงานหลายอย่าง ข้อมูลทั่วไปยังสามารถใช้ได้ที่นี่ ลองใช้java.util.List เป็น ตัวอย่าง
List list = new ArrayList();
list.add( new SomeClass() );
SomeClass sc = list.get(0); // Error, needs a cast unless...
บรรทัดที่สามในตัวอย่างด้านบนจะทำให้เกิดข้อผิดพลาด เว้นแต่คุณจะอัปเดตบรรทัดแรกดังนี้:
List<SomeClass> = new ArrayList();
คุณสามารถบรรลุผลลัพธ์เดียวกันได้โดยใช้ยาชื่อสามัญในคลาสMyArray นี่คือเวอร์ชันใหม่:
public class MyArray<E> {
    public <E> E[] toArray(Class cls, int size) throws Exception {
        E[] array = (E[])Array.newInstance(cls, size);
        Constructor ctor = cls.getConstructors()[0];
        for ( int element = 0; element < array.length; element++ ) {
            Array.set(array, element, ctor.newInstance());
        }
        return arrayOfGenericType;
    }
}
// ...
MyArray<SomeClass> a1 = new MyArray(SomeClass.class, 32);
SomeClass[] array1 = a1.toArray();
มันดูดี. โดยการใช้ข้อมูลทั่วไปและรวมประเภทเป้าหมายไว้ในการประกาศ ประเภทดังกล่าวสามารถอนุมานได้ในการดำเนินการอื่นๆ นอกจากนี้ รหัสนี้สามารถลดเหลือหนึ่งบรรทัดได้โดยทำสิ่งนี้:
SomeClass[] array = new MyArray<SomeClass>(SomeClass.class, 32).toArray();
ภารกิจสำเร็จแล้วใช่ไหม? ก็ไม่เชิง นี่เป็นเรื่องปกติหากคุณไม่สนใจว่าคุณจะเรียกตัวสร้างคลาสตัวใด แต่ถ้าคุณต้องการเรียกตัวสร้างเฉพาะ วิธีนี้ใช้ไม่ได้ผล คุณสามารถใช้การสะท้อนกลับเพื่อแก้ไขปัญหานี้ได้ต่อไป แต่โค้ดก็จะซับซ้อน โชคดีที่มีนิพจน์แลมบ์ดาที่นำเสนอวิธีแก้ปัญหาอื่น

โซลูชันที่ 3: ใช้นิพจน์แลมบ์ดา

ฉันยอมรับว่าฉันไม่เคยตื่นเต้นกับสำนวนแลมบ์ดามาก่อน แต่ฉันได้เรียนรู้ที่จะชื่นชมมันแล้ว โดยเฉพาะอย่างยิ่งฉันชอบ อินเทอร์เฟซ java.util.stream.Streamซึ่งจัดการคอลเลกชันของวัตถุ สตรีมช่วยให้ฉันเข้าถึงอาร์เรย์ Nirvana ของ Java ได้ นี่เป็นความพยายามครั้งแรกของฉันในการใช้ lambdas:
SomeClass[] array =
    Stream.generate(() -> new SomeClass())
    .toArray(SomeClass[]::new);
ฉันแบ่งรหัสนี้ออกเป็นสามบรรทัดเพื่อให้อ่านง่ายขึ้น คุณจะเห็นว่ามันทำเครื่องหมายถูกทุกช่อง: เรียบง่ายและสวยงาม สร้างอาร์เรย์ของอินสแตนซ์อ็อบเจ็กต์ที่มีประชากร และช่วยให้คุณสามารถเรียกตัวสร้างเฉพาะได้ ให้ความสนใจกับ พารามิเตอร์วิธีการ toArray : SomeClass[]:: new นี่คือฟังก์ชันตัวสร้างที่ใช้ในการจัดสรรอาร์เรย์ประเภทที่ระบุ อย่างไรก็ตาม โค้ดนี้มีปัญหาเล็กน้อย นั่นคือสร้างอาร์เรย์ที่มีขนาดไม่สิ้นสุด นี่ไม่เหมาะสมที่สุด แต่ปัญหาสามารถแก้ไขได้ด้วยการเรียก วิธี จำกัด :
SomeClass[] array =
    Stream.generate(() -> new SomeClass())
    .limit(32)   // calling the limit method
    .toArray(SomeClass[]::new);
ขณะนี้อาร์เรย์ถูกจำกัดไว้ที่ 32 องค์ประกอบ คุณยังสามารถตั้งค่าอ็อบเจ็กต์เฉพาะสำหรับแต่ละองค์ประกอบของอาร์เรย์ได้ ดังที่แสดงด้านล่าง:
SomeClass[] array = Stream.generate(() -> {
    SomeClass result = new SomeClass();
    result.val = 16;
    return result;
    })
    .limit(32)
    .toArray(SomeClass[]::new);
โค้ดนี้แสดงให้เห็นถึงพลังของนิพจน์แลมบ์ดา แต่โค้ดไม่เรียบร้อยหรือกะทัดรัด ในความคิดของฉัน การเรียก Constructor อื่นมาตั้งค่าจะดีกว่ามาก
SomeClass[] array6 = Stream.generate( () -> new SomeClass(16) )
    .limit(32)
    .toArray(SomeClass[]::new);
ฉันชอบโซลูชันที่ใช้นิพจน์แลมบ์ดา เหมาะอย่างยิ่งเมื่อคุณต้องการเรียกตัวสร้างเฉพาะหรือทำงานกับแต่ละองค์ประกอบของอาร์เรย์ เมื่อฉันต้องการอะไรที่ง่ายกว่านี้ ฉันมักจะใช้วิธีแก้ปัญหาแบบยาสามัญเพราะมันง่ายกว่า อย่างไรก็ตาม คุณสามารถเห็นได้ด้วยตัวเองว่านิพจน์ lambda เป็นโซลูชันที่หรูหราและยืดหยุ่น

บทสรุป

วันนี้เราได้เรียนรู้วิธีทำงานกับการประกาศและการจัดสรรอาร์เรย์ของ primitives การจัดสรรอาร์เรย์ของ องค์ประกอบ Objectโดยใช้นิพจน์การสะท้อน ข้อมูลทั่วไป และแลมบ์ดาใน Java
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION