Bir sınıftaki özel alanın değiştirilmesini nasıl önleyebilirim?


165

Bu sınıfa sahip olduğumu düşünün:

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

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

Şimdi, yukarıdaki sınıfı kullanan başka bir sınıf var:

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

İşte sorun şu: Dışarıdan bir sınıfın özel alanına eriştim! Bunu nasıl önleyebilirim? Yani bu diziyi değişmez hale nasıl getirebilirim? Bu, her alıcı yöntemiyle özel alana erişmek için çalışabileceğiniz anlamına mı geliyor? (Guava gibi herhangi bir kütüphane istemiyorum. Bunu yapmanın doğru yolunu bilmem yeterli).


10
Aslında hale getirirken finalyaptığı alanın değişiklik yapılmasını önlemek . Bununla birlikte, Objectatıfta bulunulan alanın değiştirilmesini önlemek daha karmaşıktır.
OldCurmudgeon

22
Özel bir alanda sakladığınız bir referansa sahip olduğunuz bir diziyi değiştirebilmenin özel bir alanı değiştirmeyle aynı olduğunu düşünüyorsanız, zihinsel modelinizde bir sorun vardır.
Joren

45
Özelse, neden ilk etapta ifşa etmeliyim?
Erik Reppen

7
Bunu anlamak için biraz zaman aldı ama tüm veri yapıları nesnelerinin içinde tamamen kapsüllenmiş olmasını sağlamak zaman kodumu her zaman geliştirir - yani "arr" almak "için bir yolu yoktur, bunun yerine içinde gerekir sınıf veya yineleyici sağlar.
Bill K

8
Bu sorunun neden bu kadar fazla dikkat çektiğini başka biri de şaşırttı mı?
Roma

Yanıtlar:


163

Dizinizin bir kopyasını döndürmelisiniz .

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

121
İnsanlara, bu sorunun doğru cevabı seçilmiş olmasına rağmen, sorunun en iyi çözümünün aslında sp00m'nin söylediği gibi bir geri dönüş olduğunu hatırlatmak isterimUnmodifiable List .
OldCurmudgeon

48
Gerçekten bir dizi döndürmeniz gerekiyorsa değil.
Svish

8
Aslında, tartışılan soruna en iyi çözüm genellikle @MikhailVladimirov'un dediği gibi: - dizi veya koleksiyonun bir görünümünü sağlamak veya geri vermek için .
Christoffer Hammarström

20
ancak verilerin sığ bir kopyası değil, derin bir kopyası olduğundan emin olun. Dizeler için fark etmez, örneğin örneğin içeriğinin değiştirilebileceği Date gibi diğer sınıflar için fark yaratabilir.
jwenting

16
@Svish Daha sert olmalıydım: Bir API işlevinden bir dizi döndürürseniz yanlış yapıyorsunuz. Bir kitaplık içindeki özel işlevlerde doğru olabilir . Bir API'da (değiştirilebilirliğe karşı korumanın bir rol oynadığı), asla değildir.
Konrad Rudolph

377

Dizi yerine bir Liste kullanabiliyorsanız Koleksiyonlar değiştirilemez bir liste sağlar :

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

Collections.unmodifiableList(list)Listenin sınıfın kendisi tarafından değiştirilmediğinden emin olmak için alana atama olarak kullanılan bir yanıt eklendi .
Maarten Bodewes

Sadece merak ediyorum, eğer bu yöntemi kodalarsanız çok sayıda yeni anahtar kelime alırsınız. GetList () çok fazla erişim sağladığında bu performansa zarar vermez. Örneğin, render kodunda.
Thomas15v

2
Dizilerin elemanları olduğu gibi kendi içinde değişmezse String, bunun işe yarayabileceğini, ancak değişebilirlerse arayanın onları değiştirmesini önlemek için bir şeyler yapılması gerekebileceğini unutmayın.
Lii

45

niteleyici private yalnızca alanın kendisine diğer sınıflardan erişilmesini engeller, ancak bu alanın nesne referanslarını korumaz. Başvurulan nesneyi korumanız gerekiyorsa, vermeyin. Değişiklik

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

için:

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

veya

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

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

5
Bu makale dizilerde klon kullanılmasına karşı değil, yalnızca alt sınıflara ayrılabilen nesnelerde.
Lyn Headley

28

Collections.unmodifiableListDaha önce de belirtildiği - Arrays.asList()garip değil! Benim çözüm de listeyi dışarıdan kullanmak ve diziyi aşağıdaki gibi sarmak olacaktır:

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

Diziyi kopyalamakla ilgili sorun şudur: koda her eriştiğinizde ve dizi büyük olduğunda yapıyorsanız, çöp toplayıcı için çok fazla iş yaratabilirsiniz. Yani kopya basit ama gerçekten kötü bir yaklaşım - "ucuz" diyebilirim, ama hafıza pahalı! Özellikle 2'den fazla elemana sahip olduğunuzda.

Eğer kaynak koduna bakarsanız Arrays.asListve Collections.unmodifiableListaslında çok fazla yaratılmamışsa. Birincisi diziyi kopyalamadan sarar, ikincisi listeyi sarar ve değişiklik yapamaz.


Burada , dizeleri içeren dizinin her seferinde kopyalandığını varsayarsınız . Sadece değişmez dizelere yapılan referanslar kopyalanmaz. Diziniz çok büyük olmadığı sürece, ek yük ihmal edilebilir. Dizi çok büyükse, listeler ve immutableListyol boyunca gidin !
Maarten Bodewes

Hayır, bu varsayımı yapmadım - nerede? Kopyalanan referanslarla ilgilidir - bir String veya başka bir nesne olması önemli değildir. Sadece büyük diziler için dizi kopyalama tavsiye etmem ve Arrays.asListve Collections.unmodifiableListişlemleri büyük diziler için muhtemelen daha ucuz olduğunu söyleyerek . Belki birileri kaç elementin gerekli olduğunu hesaplayabilir, ancak zaten modifikasyonları önlemek için kopyalanmış binlerce eleman içeren dizilerle döngülerde kod gördüm - bu delilik!
michael_s

6

ImmutableListStandarttan daha iyi olanı da kullanabilirsiniz unmodifiableList. Sınıf Guava'nın bir parçasıdır Google tarafından oluşturulan kütüphanelerinin bir .

İşte açıklama:

Hala değişebilen ayrı bir koleksiyonun görünümü olan Collections.unmodifiableList'in (java.util.List) aksine, bir ImmutableList örneği kendi özel verilerini içerir ve asla değişmez

İşte nasıl kullanılacağına dair basit bir örnek:

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

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

Döndürülen listeyi değiştirmeden bırakmak için sınıfa güvenemiyorsanızTest bu çözümü kullanın . Bunun ImmutableListdöndürüldüğünden ve listedeki öğelerin de değişmez olduğundan emin olmanız gerekir . Aksi takdirde çözümüm de güvenli olmalı.
Maarten Bodewes

3

bu bakışta sistem dizisi kopyasını kullanmalısınız:

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 arama üzerinden hiçbir şey yapmaz cloneve özlü değildir.
Maarten Bodewes

2

Verilerin bir kopyasını döndürebilirsiniz. Verileri değiştirmeyi seçen arayan yalnızca kopyayı değiştirir

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

Sorunun özü, değiştirilebilir bir nesneye bir işaretçi döndürüyor olmasıdır. Hata. Ya nesneyi değişmez kılarsınız (değiştirilemez liste çözümü) ya da nesnenin bir kopyasını döndürürsünüz.

Genel bir konu olarak, nesnelerin kesinliği, nesneleri değiştirilebildiğinde değiştirilmekten korumaz. Bu iki sorun "kuzenleri öpmek" tir.


Java'nın referansları var, C'nin işaretçileri var. Yeterince söylendi. Ayrıca, "son" değiştiricinin uygulandığı yere bağlı olarak birden çok etkisi olabilir. Bir sınıf üyesine başvurmak, üyeye "=" (atama) operatörü ile tam olarak bir kez değer atamanıza zorlar. Bunun değişmezlikle bir ilgisi yok. Bir üyenin başvurusunu yenisiyle değiştirirseniz (aynı sınıftan başka bir örnek), önceki örnek değişmeden kalır (ancak başka bir referans tutmuyorsa GCed olabilir).
gyorgyabraham

1

Değiştirilemez bir listenin geri gönderilmesi iyi bir fikirdir. Ancak, getter yöntemine yapılan çağrı sırasında değiştirilemeyen bir liste yine de sınıf veya sınıftan türetilen sınıflar tarafından değiştirilebilir.

Bunun yerine, sınıfı genişleten herkesin listenin değiştirilmemesi gerektiğini açıkça belirtmelisiniz.

Yani örneğinizde aşağıdaki koda yol açabilir:

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;
    }
}

Yukarıdaki örnekte STRINGS alanı herkese açık , prensip olarak, değerler zaten bilindiği için yöntem çağrısını ortadan kaldırabilirsiniz.

Ayrıca dizeleri bir private final List<String> , sınıf örneğinin oluşturulması sırasında değiştirilemez hale alana . Sabit veya örnekleme bağımsız değişkenlerinin kullanılması (yapıcıdan) sınıfın tasarımına bağlıdır.

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;
    }
}

0

Evet, dizinin bir kopyasını döndürmelisiniz:

 public String[] getArr()
 {
    return Arrays.copyOf(arr);
 }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.