167

想象一下,我有这门课:

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

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

现在,我有另一个使用上述类的类:

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

所以这就是问题所在:我从外部访问了一个类的私有字段!我怎样才能防止这种情况?我的意思是如何使这个数组不可变?这是否意味着您可以使用每个 getter 方法逐步访问私有字段?(我不想要任何像 Guava 这样的库。我只需要知道正确的方法)。

4

11 回答 11

381

如果您可以使用 List 而不是数组,Collections 提供了一个不可修改的列表

public List<String> getList() {
    return Collections.unmodifiableList(list);
}
于 2013-02-11T09:38:38.493 回答
165

您必须返回数组的副本

public String[] getArr() {
  return arr == null ? null : Arrays.copyOf(arr, arr.length);
}
于 2013-02-11T09:34:10.633 回答
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];
}
于 2013-02-11T09:34:34.987 回答
28

Collections.unmodifiableList已经提到过 -Arrays.asList()奇怪的是没有!我的解决方案也是使用外部列表并将数组包装如下:

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

复制数组的问题是:如果每次访问代码时都这样做并且数组很大,那么肯定会为垃圾收集器创建大量工作。所以复制是一种简单但非常糟糕的方法 - 我会说“便宜”,但内存昂贵!尤其是当您拥有超过 2 个元素时。

如果查看源代码,其实Arrays.asListCollections.unmodifiableList没有多少创建。第一个只是包装数组而不复制它,第二个只是包装列表,使其无法更改。

于 2013-02-11T21:49:34.220 回答
6

你也可以使用ImmutableListwhich should be better than the standard unmodifiableList。该类是Google 创建的Guava库的一部分。

这是描述:

与 Collections.unmodifiableList(java.util.List) 不同,后者是仍然可以更改的单独集合的视图,ImmutableList 的实例包含自己的私有数据并且永远不会更改

这是一个如何使用它的简单示例:

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

  public ImmutableList<String> getArr() 
  {
    return ImmutableList.copyOf(arr);
  }
}
于 2013-02-12T08:16:49.800 回答
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;
   }
}
于 2013-02-12T05:00:14.420 回答
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;
    }
}
于 2013-02-11T09:41:08.860 回答
2

问题的关键在于您正在返回一个指向可变对象的指针。哎呀。要么使对象不可变(不可修改的列表解决方案),要么返回对象的副本。

一般来说,如果对象是可变的,对象的最终确定性并不能保护对象不被更改。这两个问题是“亲吻表亲”。

于 2013-02-12T01:43:24.927 回答
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;
    }
}
于 2014-07-29T01:42:33.410 回答
0

是的,您应该返回数组的副本:

 public String[] getArr()
 {
    return Arrays.copyOf(arr);
 }
于 2013-02-11T09:34:50.350 回答
0

从 Java 9 开始,也可以从静态工厂方法构造不可变列表,List.of()从而减少导入和代码:

在可以修改getUsers()原始字段时返回别名:users

class Computer {
    private String[] users = new String[] {"user1", "user2", "user3"};
    public String[] getUsers;

    String[] getUsers() {
        return this.users;
    }
}

Computer c = new Computer();
c.getUsers()[0] = "me";
for (String user: c.getUsers()) {
    System.out.println(user);
}

输出:

me
user2
user3

使用不可变列表:

import java.util.List;

class Computer {
    private String[] users = new String[] {"user1", "user2", "user3"};
    public List<String> getUsers;

    List<String> getUsers() {
        return List.of(this.users);
    }
}

Computer c = new Computer();
c.getUsers().set(0, "me");
for (String user: c.getUsers()) {
    System.out.println(user);
}

输出:

user1
user2
user3
于 2021-01-17T15:44:09.573 回答