Java中getter的访问安全性



因此,我们创建了一个带有私有类成员的简单类,并为它自动生成getter。但是getter实际上返回了对该成员的引用,从而获得了对私有成员的完全访问。可以吗?下面是一个类的代码:

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

和输出:

[String1 String2相等)

[String1, String2, String3]

我把getter改成了这个

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

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

不行,因为它破坏了封装,因此类不能维护自己的不变量。构造函数也一样。

问题不在于getter/setter,而在于自动生成它们的代码。

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


作为题外话,我不会有一个具有ArrayList返回类型的getter,即使它只是一个副本。返回什么样的列表通常与客户端无关,所以我的getter应该是这样的:
public List<String> getStrings() {
    return new ArrayList<>(strings);
}

或者使用不可变视图:

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

或者使用Guava的ImmutableList类:

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

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


您所展示的代码的另一个微妙问题是双括号初始化。想象一个这样的类:

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将消耗多少内存(或者使用确切的术语:保留)?嗯,事实证明,相当多,因为您使用双括号初始化所做的是创建一个匿名内部类,它将包含对其外部类(Foo)的引用,因此,虽然返回的列表是可访问的,但Foo的所有其他字段将不适合垃圾收集。

从我的角度来看,getter通常有两个目的:

  • 首先他们应该保护实现细节。
  • 第二,他们应该提供一种容易扩展的方法(例如验证或仪器)

如果你的例子违反了这些原则取决于上下文:

    如果你的类应该拥有字符串,那么可能每个人都应该与容器对象交互来修改列表,而不是列表本身。要公开一个集合(例如,在需要一个集合的方法中进行处理),你可以使用collections . unmodiableelist()。另一方面,如果类只拥有字符串列表,那么拥有列表就不是一个实现细节。
  1. 使用getter而不是直接访问字段,可以让您轻松添加数据对话,跟踪仪表和其他东西,而无需更改所有使用字段的地方。

最新更新