อาร์เรย์ไม่เปลี่ยนรูปใน Java


158

มีทางเลือกที่ไม่เปลี่ยนรูปแบบไปยังอาร์เรย์ดั้งเดิมใน Java หรือไม่? การสร้างอาร์เรย์แบบดั้งเดิมfinalนั้นไม่ได้ขัดขวางการทำสิ่งที่ต้องการ

final int[] array = new int[] {0, 1, 2, 3};
array[0] = 42;

ฉันต้องการให้องค์ประกอบของอาร์เรย์ไม่สามารถเปลี่ยนแปลงได้


43
ทำตัวเองให้เป็นที่โปรดปรานและหยุดใช้อาร์เรย์ใน Java เพื่ออะไรนอกเหนือจาก 1) io 2) การทำ crunching จำนวนมาก 3) ถ้าคุณจำเป็นต้องใช้ List / Collection ของคุณเอง (ซึ่งหายาก ) พวกมันยืดหยุ่นและโบราณวัตถุอย่างมาก ... ตามที่คุณเพิ่งค้นพบกับคำถามนี้
Whaley

39
@ Whaley และการแสดงและโค้ดที่คุณไม่ต้องการอาร์เรย์ "ไดนามิก" อาร์เรย์ยังคงมีประโยชน์ในหลาย ๆ ที่ไม่ใช่เรื่องยาก
Colin Hebert

10
@ Colin: ใช่ แต่พวกเขากำลัง จำกัด อย่างรุนแรง เป็นการดีที่สุดที่จะใช้ความคิดว่า "ฉันต้องการอาร์เรย์ที่นี่หรือฉันสามารถใช้รายการแทนได้หรือไม่"
Jason S

7
@Colin: เพิ่มประสิทธิภาพเฉพาะเมื่อคุณต้องการ เมื่อคุณพบว่าตัวเองใช้เวลาครึ่งนาทีในการเพิ่มสิ่งต่าง ๆ เช่นขยายอาร์เรย์หรือเก็บดัชนีไว้นอกขอบเขตของ for-loop คุณได้สูญเสียเวลาของเจ้านายไปแล้ว สร้างก่อนเพิ่มประสิทธิภาพเมื่อต้องการและที่ไหน - และในแอปพลิเคชันส่วนใหญ่ที่ไม่ได้แทนที่รายการด้วยอาร์เรย์
fwielstra

9
ทำไมไม่มีใครพูดถึงint[]และnew int[]พิมพ์ง่ายกว่าList<Integer>และnew ArrayList<Integer>? XD
lcn

คำตอบ:


164

ไม่ได้อยู่กับอาร์เรย์ดั้งเดิม คุณจะต้องใช้รายการหรือโครงสร้างข้อมูลอื่น:

List<Integer> items = Collections.unmodifiableList(Arrays.asList(0,1,2,3));

18
ยังไงก็เถอะฉันไม่เคยรู้เกี่ยวกับ Arrays.asList (T ... ) ฉันเดาว่าฉันสามารถกำจัด ListUtils.list (T ... ) ของฉันตอนนี้ได้
MattRS

3
โดยวิธีการ Arrays.asList ให้รายการที่แก้ไขไม่ได้
mauhiz

3
@tony ที่ ArrayList ไม่ใช่ java.util's
mauhiz

11
@mauhiz Arrays.asListคือไม่ unmodifiable docs.oracle.com/javase/7/docs/api/java/util/ ...... "ส่งคืนรายการขนาดคงที่ที่สนับสนุนโดยอาร์เรย์ที่ระบุ (เปลี่ยนเป็นรายการที่ส่งคืน" เขียนผ่าน "ไปยังอาร์เรย์)"
Jason S

7
@JasonS ดีArrays.asList()แน่นอนดูเหมือน unmodifiable ในแง่ที่ว่าคุณไม่สามารถaddหรือremoveรายการที่ส่งกลับjava.util.Arrays.ArrayList( เพื่อไม่ให้สับสนกับjava.util.ArrayList) - การใช้งานเหล่านี้ก็ไม่ได้ดำเนินการ อาจ @mauhiz พยายามที่จะพูดแบบนี้? แต่แน่นอนว่าคุณสามารถแก้ไของค์ประกอบที่มีอยู่List<> aslist = Arrays.asList(...); aslist.set(index, element)ได้ดังนั้นjava.util.Arrays.ArrayListแน่นอนว่าไม่สามารถผูกมัดได้ QED ได้เพิ่มความคิดเห็นเพื่อเน้นความแตกต่างระหว่างผลลัพธ์asListและปกติArrayList
NIA

73

คำแนะนำของฉันคือการไม่ใช้อาร์เรย์หรือunmodifiableListแต่ใช้งานฝรั่ง 's ImmutableListซึ่งมีอยู่เพื่อการนี้

ImmutableList<Integer> values = ImmutableList.of(0, 1, 2, 3);

3
+1 ImmavaList ของ Guava ดีกว่า Collections.unmodifiableList เนื่องจากเป็นประเภทที่แยกต่างหาก
sleske

29
ImmListList มักจะดีกว่า (ขึ้นอยู่กับกรณีการใช้งาน) เพราะมันไม่เปลี่ยนรูป Collections.unmodifiableList ไม่สามารถเปลี่ยนไม่ได้ แต่เป็นมุมมองที่ผู้รับไม่สามารถเปลี่ยนแปลงได้ แต่แหล่งข้อมูลดั้งเดิมสามารถเปลี่ยนแปลงได้
Charlie Collins

2
@CharlieCollins หากไม่มีวิธีการเข้าถึงแหล่งดั้งเดิมเกินCollections.unmodifiableListพอที่จะทำให้รายการไม่เปลี่ยนรูปหรือไม่
savanibharat

1
@savanibharat ใช่
แค่นักเรียน

20

ดังที่คนอื่น ๆ สังเกตคุณไม่สามารถมีอาร์เรย์ที่ไม่เปลี่ยนรูปแบบใน Java

หากคุณต้องการวิธีที่คืนค่าอาร์เรย์ที่ไม่ส่งผลกระทบต่ออาร์เรย์เดิมคุณต้องทำการโคลนอาร์เรย์ทุกครั้ง:

public int[] getFooArray() {
  return fooArray == null ? null : fooArray.clone();
}

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

เทคนิคนี้เรียกว่าการทำสำเนาป้องกัน


3
เขาพูดถึงว่าเขาต้องการคนทะลวงที่ไหน?
Erick Robertson

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

7
ดีกว่าที่จะใช้clone()หรือArrays.copy()ที่นี่?
kevinarpe

เท่าที่ฉันจำได้ตาม "Java ที่มีประสิทธิภาพ" ของ Joshua Bloch โคลน () เป็นวิธีที่ต้องการแน่นอน
Vincent

1
ประโยคสุดท้ายของรายการที่ 13 "Override clone อย่างรอบคอบ": "ตามกฎแล้วฟังก์ชั่นการคัดลอกนั้นได้รับการจัดทำโดยผู้สร้างหรือโรงงานที่ดีที่สุดข้อยกเว้นที่น่าสังเกตสำหรับกฎนี้คืออาร์เรย์ซึ่งคัดลอกด้วยวิธีโคลน"
Vincent

14

มีวิธีหนึ่งในการสร้างอาร์เรย์ที่ไม่เปลี่ยนรูปแบบใน Java:

final String[] IMMUTABLE = new String[0];

อาร์เรย์ที่มีองค์ประกอบ 0 (ชัด) ไม่สามารถกลายพันธุ์ได้

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

final static String[] EMPTY_STRING_ARRAY = new String[0];

List<String> emptyList = new ArrayList<String>();
return emptyList.toArray(EMPTY_STRING_ARRAY); // returns EMPTY_STRING_ARRAY

3
แต่นี่ไม่ได้ช่วยให้ OP บรรลุอาเรย์ที่ไม่เปลี่ยนรูปแบบที่มีข้อมูลอยู่ในนั้น
Sridhar Sarnobat

1
@ Sridhar-Sarnobat นั่นเป็นจุดที่คำตอบ Java ไม่มีวิธีในการสร้างอาร์เรย์ที่ไม่เปลี่ยนรูปซึ่งมีข้อมูลอยู่ในนั้น
บริกแฮม

1
ฉันชอบไวยากรณ์ที่เรียบง่ายกว่านี้ดีกว่า: สตริงคงสุดท้ายส่วนตัว [] EMPTY_STRING_ARRAY = {}; (เห็นได้ชัดว่าฉันไม่ได้คิดวิธีการทำ "mini-Markdown" ปุ่มดูตัวอย่างน่าจะดี)
Wes

6

อีกหนึ่งคำตอบ

static class ImmutableArray<T> {
    private final T[] array;

    private ImmutableArray(T[] a){
        array = Arrays.copyOf(a, a.length);
    }

    public static <T> ImmutableArray<T> from(T[] a){
        return new ImmutableArray<T>(a);
    }

    public T get(int index){
        return array[index];
    }
}

{
    final ImmutableArray<String> sample = ImmutableArray.from(new String[]{"a", "b", "c"});
}

การคัดลอกอาเรย์นั้นใช้อะไรในการสร้างเนื่องจากเราไม่ได้เตรียมเมธอด setter ไว้?
vijaya kumar

เนื้อหาของอินพุตอาร์เรย์ยังสามารถแก้ไขได้ในภายหลัง เราต้องการรักษาสถานะดั้งเดิมดังนั้นเราจึงคัดลอก
aeracode

4

ในฐานะของ Java 9 คุณสามารถใช้List.of(...), JavaDoc JavaDoc

วิธีนี้จะส่งกลับไม่เปลี่ยนรูปListและมีประสิทธิภาพมาก


3

หากคุณต้องการ (สำหรับเหตุผลด้านประสิทธิภาพหรือเพื่อบันทึกหน่วยความจำ) เนทีฟ 'int' แทน 'java.lang.Integer' คุณอาจต้องเขียนคลาส wrapper ของคุณเอง มีการใช้งาน IntArray ต่างๆในสุทธิไม่มี (ผมพบว่า) เป็นไม่เปลี่ยนรูปมี แต่: Koders IntArray , Lucene IntArray อาจมีคนอื่น ๆ


3

ตั้งแต่ฝรั่ง 22 จากแพคเกจที่คุณสามารถใช้สามชั้นเรียนใหม่ซึ่งมีรอยความทรงจำที่ต่ำกว่าเมื่อเทียบกับcom.google.common.primitivesImmutableList

พวกเขายังมีผู้สร้าง ตัวอย่าง:

int size = 2;
ImmutableLongArray longArray = ImmutableLongArray.builder(size)
  .add(1L)
  .add(2L)
  .build();

หรือถ้าขนาดเป็นที่รู้จักกันในเวลารวบรวม:

ImmutableLongArray longArray = ImmutableLongArray.of(1L, 2L);

นี่เป็นอีกวิธีหนึ่งในการรับมุมมองที่ไม่เปลี่ยนรูปของอาเรย์สำหรับจาวาแบบดั้งเดิม


1

ไม่เป็นไปไม่ได้ อย่างไรก็ตามเราสามารถทำสิ่งนี้:

List<Integer> temp = new ArrayList<Integer>();
temp.add(Integer.valueOf(0));
temp.add(Integer.valueOf(2));
temp.add(Integer.valueOf(3));
temp.add(Integer.valueOf(4));
List<Integer> immutable = Collections.unmodifiableList(temp);

สิ่งนี้ต้องการการใช้ wrappers และเป็นรายการไม่ใช่อาร์เรย์ แต่เป็นสิ่งที่ใกล้เคียงที่สุดที่คุณจะได้รับ


4
ไม่จำเป็นต้องเขียนสิ่งเหล่าvalueOf()นี้การ autoboxing จะดูแลสิ่งนั้น นอกจากนี้ยังArrays.asList(0, 2, 3, 4)จะกระชับมากขึ้น
Joachim Sauer

@Joachim: จุดใช้valueOf()คือการใช้แคชวัตถุ Integer ภายในเพื่อลดการใช้ / รีไซเคิลหน่วยความจำ
Esko

4
@Esko: อ่านข้อมูลจำเพาะออโต้บ็อกซ์ มันทำสิ่งเดียวกันทุกประการดังนั้นจึงไม่มีความแตกต่างที่นี่
Joachim Sauer

1
@ John คุณจะไม่ได้รับ NPE แปลงintไปยังIntegerแม้ว่า; เพียงแค่ต้องระวังวิธีอื่น ๆ
ColinD

1
@ จอห์น: ถูกต้องมันอาจเป็นอันตรายได้ แต่แทนที่จะหลีกเลี่ยงมันอย่างสมบูรณ์มันน่าจะดีกว่าที่จะเข้าใจถึงอันตรายและหลีกเลี่ยงสิ่งนั้น
Joachim Sauer

1

ในบางสถานการณ์มันจะมีน้ำหนักเบากว่าในการใช้วิธีการคงที่จากห้องสมุด Google Guava: List<Integer> Ints.asList(int... backingArray)

ตัวอย่าง:

  • List<Integer> x1 = Ints.asList(0, 1, 2, 3)
  • List<Integer> x1 = Ints.asList(new int[] { 0, 1, 2, 3})

1

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


1

วิธีการขององค์ประกอบ (E ... )ในJava9สามารถใช้ในการสร้างรายการที่ไม่เปลี่ยนรูปโดยใช้เพียงบรรทัด:

List<Integer> items = List.of(1,2,3,4,5);

วิธีการข้างต้นส่งกลับรายการที่ไม่เปลี่ยนรูปที่มีจำนวนองค์ประกอบโดยพลการ และการเพิ่มจำนวนเต็มใด ๆ ในรายการนี้จะส่งผลให้เกิดjava.lang.UnsupportedOperationExceptionข้อยกเว้น วิธีนี้ยังยอมรับอาร์เรย์เดียวเป็นอาร์กิวเมนต์

String[] array = ... ;
List<String[]> list = List.<String[]>of(array);

0

ในขณะที่มันเป็นความจริงที่ใช้Collections.unmodifiableList()งานได้บางครั้งคุณอาจมีห้องสมุดขนาดใหญ่ที่มีวิธีการที่กำหนดไว้แล้วเพื่อส่งกลับอาร์เรย์ (เช่นString[]) เพื่อป้องกันไม่ให้แตกคุณสามารถกำหนดอาร์เรย์เสริมที่จะเก็บค่า:

public class Test {
    private final String[] original;
    private final String[] auxiliary;
    /** constructor */
    public Test(String[] _values) {
        original = new String[_values.length];
        // Pre-allocated array.
        auxiliary = new String[_values.length];
        System.arraycopy(_values, 0, original, 0, _values.length);
    }
    /** Get array values. */
    public String[] getValues() {
        // No need to call clone() - we pre-allocated auxiliary.
        System.arraycopy(original, 0, auxiliary, 0, original.length);
        return auxiliary;
    }
}

ทดสอบ:

    Test test = new Test(new String[]{"a", "b", "C"});
    System.out.println(Arrays.asList(test.getValues()));
    String[] values = test.getValues();
    values[0] = "foobar";
    // At this point, "foobar" exist in "auxiliary" but since we are 
    // copying "original" to "auxiliary" for each call, the next line
    // will print the original values "a", "b", "c".
    System.out.println(Arrays.asList(test.getValues()));

ไม่สมบูรณ์แบบ แต่อย่างน้อยคุณมี "อาร์เรย์ที่ไม่เปลี่ยนรูปแบบหลอก" (จากมุมมองของชั้นเรียน) และสิ่งนี้จะไม่ทำลายรหัสที่เกี่ยวข้อง


-3

ดี .. อาร์เรย์มีประโยชน์ในการผ่านเป็นค่าคงที่ (ถ้าพวกเขา) เป็นพารามิเตอร์ตัวแปร


"พารามิเตอร์ตัวแปร" คืออะไร ไม่เคยได้ยินเลย
sleske

2
ใช้อาร์เรย์เป็นค่าคงที่เป็นว่าที่หลายคนล้มเหลว การอ้างอิงเป็นค่าคงที่ แต่เนื้อหาของอาร์เรย์จะไม่แน่นอน ผู้โทร / ลูกค้ารายหนึ่งสามารถเปลี่ยนเนื้อหาของ "ค่าคงที่" ของคุณ นี่อาจไม่ใช่สิ่งที่คุณต้องการอนุญาต ใช้ ImmListList
Charlie Collins

@ CharlieCollins แม้สิ่งนี้อาจถูกแฮกผ่านการสะท้อนกลับ Java เป็นภาษาที่ไม่ปลอดภัยในแง่ของความไม่แน่นอน ... และนี่จะไม่เปลี่ยนแปลง
ชื่อที่แสดง

2
@SargeBorsch นั้นเป็นเรื่องจริง แต่ใครก็ตามที่ทำการแฮ็กตามการสะท้อนกลับควรรู้ว่าพวกเขากำลังเล่นด้วยไฟโดยการดัดแปลงสิ่งต่าง ๆ ที่ผู้เผยแพร่ API อาจไม่ต้องการให้พวกเขาเข้าถึง ในขณะที่ถ้า API คืนค่าint[]ผู้เรียกอาจคิดว่าพวกเขาสามารถทำสิ่งที่พวกเขาต้องการกับอาเรย์นั้นได้โดยไม่กระทบกับ internals ของ API
daiscog
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.