Java 中 getter 的访问安全

Access safety of getters in Java

因此,我们创建了一个简单的 class,其中包含一些私有 class 成员,并为其自动生成了 getter。但是 getter 实际上返回了对该成员的引用,从而获得了对私有成员的完全访问权限。可以吗? 这是 class:

的代码
public class User {

    private ArrayList<String> strings = new ArrayList(){ {
            add("String1");
            add("String2");
        } };

    public User() {
    }

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

    public void setStrings(ArrayList<String> strings) {
        this.strings = strings;
    }
}

主要方法代码:

    public class Main {
    public static void main(String[] args){
        User user = new User();

        System.out.println(user.getStrings());
        user.getStrings().add("String3");
        System.out.println(user.getStrings());
    }
}

并输出:

[字符串 1,字符串 2]

[字符串 1、字符串 2、字符串 3]

我已经将 getter 更改为这个:

public ArrayList<String> getStrings() {
    return (ArrayList<String>)strings.clone();
}

但问题仍然存在,如果不是为了安全,getter 是什么?正确的写法是什么?

不,这不行,因为它破坏了封装,因此 class 无法维护自己的不变量。与构造函数相同。

但问题不在于 getters/setters,而在于自动生成它们的代码。

长话短说:不要盲目地使用自动生成的访问器,如果他们正在处理可变结构,请制作防御性副本(或不可变的等价物)。


顺便说一句,我不会有 getter 和 ArrayList return 类型,即使它只是一个副本。通常 none 客户的业务是什么类型的列表 return,所以我的 getter 看起来像这样:

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

或者使用不可变视图:

public List<String> getStrings() {
    return Collections.unmodifiableList(strings);
}

或者使用 Guava 的 ImmutableList class:

public List<String> getStrings() {
    return ImmutableList.copyOf(strings);
}

三种解决方案之间存在细微差别,因此最佳解决方案可能会有所不同。作为一般规则,我更喜欢 returning 不可变结构,因为这清楚地表明对结构所做的更改不会反映出来,即 user.getStrings().add( "X" ); 将失败并出现异常。


您向我们展示的代码的另一个细微问题是双括号初始化。想象一下这样的 class:

public class Foo {
   private List<String> strings = new ArrayList() {{ add("bar");}};
   private Object veryLargeField; //the object stored here consumes a lot of memory

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

现在假设我们正在这样做:

private class Bar {
   private List<String> fooStrings;

   public Bar() {
     this.fooStrings = new Foo().getStrings();
   }
}

Bar 会消耗多少内存(或使用准确的术语:保留)?嗯,事实证明很多,因为你对双括号初始化所做的是创建一个匿名内部 class,它将包含对其外部 class (Foo) 的引用,因此虽然列表 returned 是可访问的,但 Foo 的所有其他字段将不符合垃圾收集的条件。

在我看来 getters 通常应该有两个目的:

  • 首先他们应该保护实施细节。
  • 其次,他们应该提供一种轻松扩展的方法(例如验证或检测)

如果您的示例违反这些原则取决于上下文:

  1. 如果您的 class 应该拥有这些字符串,那么可能每个人都应该与容器对象交互来修改列表,而不是与列表本身交互。要公开集合(例如,为了在需要集合的方法中进行处理),您可以使用例如Collections.unmodifiableList()。另一方面,如果 class 仅拥有字符串列表,那么拥有列表不是实现细节。
  2. 使用 getter 而不是直接访问字段允许您轻松添加数据对话、跟踪检测和其他内容,而无需更改所有使用字段的地方。