ฉันจะป้องกันการปรับเปลี่ยนเขตข้อมูลส่วนตัวในชั้นเรียนได้อย่างไร


165

ลองนึกภาพว่าฉันมีคลาสนี้:

public class Test
{
  private String[] arr = new String[]{"1","2"};    

  public String[] getArr() 
  {
    return arr;
  }
}

ตอนนี้ฉันมีคลาสอื่นที่ใช้คลาสด้านบน:

Test test = new Test();
test.getArr()[0] ="some value!"; //!!!

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


10
ที่จริงทำให้มันfinalไม่ป้องกันการแก้ไขของสนาม อย่างไรก็ตามการป้องกันการแก้ไขของObjectฟิลด์ที่อ้างถึงนั้นมีความซับซ้อนมากขึ้น
OldCurmudgeon

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

45
หากเป็นแบบส่วนตัวทำไมต้องเปิดเผยเป็นอันดับแรก
Erik Reppen

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

8
มีใครบ้างที่ประหลาดใจว่าทำไมคำถามนี้ได้รับความสนใจอย่างมาก?
โรมัน

คำตอบ:


163

คุณต้องส่งสำเนาชุดข้อมูลของคุณคืน

public String[] getArr() {
  return arr == null ? null : Arrays.copyOf(arr, arr.length);
}

121
ผมอยากจะเตือนคนที่แม้ว่าเรื่องนี้ได้รับการโหวตให้เป็นคำตอบที่ถูกต้องให้กับคำถามที่ทางออกที่ดีที่สุดในการแก้ไขปัญหาที่เป็นจริงเป็นsp00mกล่าวว่า - Unmodifiable Listไปกลับ
OldCurmudgeon

48
ไม่ใช่ถ้าคุณต้องการส่งคืนอาร์เรย์
Svish

8
ที่จริงแล้วทางออกที่ดีที่สุดสำหรับปัญหาที่กล่าวถึงมักจะเป็น @MikhailVladimirov พูดว่า: - เพื่อให้หรือส่งคืนมุมมองของอาร์เรย์หรือคอลเลกชัน
Christoffer Hammarström

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

16
@Svish ฉันควรจะรุนแรงมากขึ้น: ถ้าคุณส่งคืนอาร์เรย์จากฟังก์ชั่น API คุณทำผิด ในฟังก์ชั่นส่วนตัวภายในห้องสมุดมันอาจจะถูกต้อง ใน API (ซึ่งการป้องกันความสามารถในการปรับเปลี่ยนมีบทบาท) ก็ไม่เป็นเช่นนั้น
Konrad Rudolph

377

หากคุณสามารถใช้รายการแทนอาร์เรย์ชุดสะสมจะแสดงรายการที่ไม่สามารถเปลี่ยนแปลงได้ :

public List<String> getList() {
    return Collections.unmodifiableList(list);
}

เพิ่มคำตอบที่ใช้Collections.unmodifiableList(list)เป็นการกำหนดให้กับฟิลด์เพื่อให้แน่ใจว่ารายการจะไม่ถูกเปลี่ยนแปลงโดยคลาสเอง
Maarten Bodewes

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

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

45

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

public String [] getArr ()
{
    return arr;
}

ถึง:

public String [] getArr ()
{
    return arr.clone ();
}

หรือเพื่อ

public int getArrLength ()
{
    return arr.length;
}

public String getArrElementAt (int index)
{
    return arr [index];
}

5
บทความนี้ไม่โต้เถียงกับการใช้โคลนในอาร์เรย์เฉพาะบนวัตถุที่สามารถ subclassed
Lyn Headley

28

The Collections.unmodifiableListได้รับการกล่าวถึง - Arrays.asList()ไม่แปลก! โซลูชันของฉันจะใช้รายการจากภายนอกและล้อมอาร์เรย์ดังนี้

String[] arr = new String[]{"1", "2"}; 
public List<String> getList() {
    return Collections.unmodifiableList(Arrays.asList(arr));
}

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

หากคุณดูซอร์สโค้ดของArrays.asListและCollections.unmodifiableListมีการสร้างขึ้นไม่มากนัก ตัวแรกจะล้อมอาร์เรย์โดยไม่ทำการคัดลอกส่วนที่สองเพียงแค่ล้อมรอบรายการทำให้การเปลี่ยนแปลงไม่พร้อมใช้งาน


คุณตั้งสมมติฐานที่นี่ว่าอาร์เรย์รวมถึงสตริงจะถูกคัดลอกในแต่ละครั้ง ไม่เพียงคัดลอกการอ้างอิงไปยังสตริงที่ไม่เปลี่ยนรูปได้ ตราบใดที่อาร์เรย์ของคุณไม่ใหญ่เกินไป ถ้าอาร์เรย์มีขนาดใหญ่มากไปหารายการและimmutableListทุกทาง!
Maarten Bodewes

ไม่ฉันไม่ได้สมมุติว่า - ที่ไหน มันเกี่ยวกับการอ้างอิงที่ถูกคัดลอก - ไม่สำคัญว่ามันจะเป็น String หรือวัตถุอื่นใด เพียงแค่บอกว่าสำหรับอาร์เรย์ขนาดใหญ่ฉันจะไม่แนะนำให้คัดลอกอาร์เรย์และการดำเนินการArrays.asListและCollections.unmodifiableListอาจมีราคาถูกกว่าสำหรับอาร์เรย์ขนาดใหญ่ บางทีใครบางคนสามารถคำนวณจำนวนองค์ประกอบที่จำเป็น แต่ฉันได้เห็นรหัสในวงวนที่มีอาร์เรย์ที่มีองค์ประกอบหลายพันรายการที่ถูกคัดลอกเพียงเพื่อป้องกันการดัดแปลง - มันบ้า!
michael_s

6

นอกจากนี้คุณยังสามารถใช้ซึ่งควรจะดีกว่ามาตรฐานImmutableList unmodifiableListชั้นเรียนเป็นส่วนหนึ่งของห้องสมุดGuavaที่สร้างโดย Google

นี่คือคำอธิบาย:

แตกต่างจาก Collections.unmodifiableList (java.util.List) ซึ่งเป็นมุมมองของคอลเลกชันแยกที่ยังคงสามารถเปลี่ยนแปลงอินสแตนซ์ของ ImmutableList มีข้อมูลส่วนตัวของตัวเองและจะไม่เปลี่ยนแปลง

นี่คือตัวอย่างง่ายๆของการใช้งาน:

public class Test
{
  private String[] arr = new String[]{"1","2"};    

  public ImmutableList<String> getArr() 
  {
    return ImmutableList.copyOf(arr);
  }
}

ใช้วิธีการแก้ปัญหานี้ถ้าคุณไม่สามารถไว้วางใจTestระดับที่จะออกจากรายการกลับไม่เปลี่ยนแปลง คุณต้องให้แน่ใจว่าImmutableListจะถูกส่งคืนและองค์ประกอบของรายการนั้นไม่เปลี่ยนรูปเช่นกัน มิฉะนั้นทางออกของฉันควรจะปลอดภัยเช่นกัน
Maarten Bodewes

3

ในมุมมองนี้คุณควรใช้การคัดลอกอาเรย์ระบบ:

public String[] getArr() {
   if (arr != null) {
      String[] arrcpy = new String[arr.length];
      System.arraycopy(arr, 0, arrcpy, 0, arr.length);
      return arrcpy;
   } else
      return null;
   }
}

-1 เนื่องจากไม่ได้ทำสิ่งใดเกินกว่าการโทรcloneและไม่กระชับ
Maarten Bodewes

2

คุณสามารถส่งคืนสำเนาของข้อมูล ผู้โทรที่เลือกที่จะเปลี่ยนแปลงข้อมูลจะเปลี่ยนเฉพาะสำเนา

public class Test {
    private static String[] arr = new String[] { "1", "2" };

    public String[] getArr() {

        String[] b = new String[arr.length];

        System.arraycopy(arr, 0, b, 0, arr.length);

        return b;
    }
}

2

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

โดยทั่วไปแล้วขอบเขตของวัตถุไม่ได้ป้องกันการเปลี่ยนแปลงหากวัตถุเหล่านั้นไม่แน่นอน ปัญหาทั้งสองนี้คือ "การจูบลูกพี่ลูกน้อง"


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

1

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

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

ดังนั้นในตัวอย่างของคุณมันอาจนำไปสู่รหัสต่อไปนี้:

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Test {
    public static final List<String> STRINGS =
        Collections.unmodifiableList(
            Arrays.asList("1", "2"));

    public final List<String> getStrings() {
        return STRINGS;
    }
}

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

คุณสามารถกำหนดสตริงให้กับprivate final List<String>ฟิลด์ที่ไม่สามารถแก้ไขได้ในระหว่างการสร้างอินสแตนซ์ของคลาส การใช้อาร์กิวเมนต์คงที่หรืออินสแตนซ์ (ของนวกรรมิก) จะขึ้นอยู่กับการออกแบบของคลาส

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Test {
    private final List<String> strings;

    public Test(final String ... strings) {
        this.strings = Collections.unmodifiableList(Arrays
                .asList(strings));
    }

    public final List<String> getStrings() {
        return strings;
    }
}

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