带有自定义对象的Collections.unmodifiableList,可防止来自外部的状态更改

时间:2015-05-16 10:00:48

标签: java

假设有一个模块。它包含一个管理类Foo,其中包含类Bar的实例列表。

public class Foo {

    private List<Bar> bars;

    public doStuff() { ... }
    private doOtherStuff() { ... }

    // method to get bars
    public List<Bar> getBars() { ... }
}

然后你有一个带有公共getter的类Bar来读取Bar的状态。该类还包含用于更改其状态的mutators。这些mutator可以是公共的和包级的。 public mutator的原因是此模块类包含另一个模块(其他包)中其他类使用的状态。因此,Bar的每个实例都是可变的。

public class Bar {
    private int myInt; // simple example, but there are more attributes

    // constructor kept on package level
    Bar(...) { ... }

    // getters on public access 
    public int getMyInt() { ... }

    // change state
    public void doThis(int i) { ... }

    // update state
    void doThat(int j) { ... }
}

我遇到了一个问题,当您要显示Bar(它有多个属性!)的所有实例的概述时,外部用户(UI)可能无法修改此对象的状态。这是可能的,因为其他模块需要一些public mutators。

搜索后,似乎我可以使用unmodifiableList(List<? extends T> list)来提供只读列表。像

public List<Bar> getBars() {
    return Collections.unmodifiableList(bars);
}

但是这种方法不起作用,因为这可以确保列表本身是只读的。这意味着您无法向其添加元素。但是,一旦你可以检索一个对象,你仍然可以修改它。

List<Bar> myBars = foo.getBars();
myBars.get(0).doThis(...);

方法doThis()是公共可访问的,因此用户可以在不允许这样做的情况下更改状态。

所以问题

(抱歉长篇介绍) 有没有一个很好的方法来解决这个安全问题?当然,我可以使方法doThis非公开,但是我必须在Foo中添加方法,以便可以从其他模块修改Bar。在这种情况下,我必须使用所有模块中的所有公共访问方法来做这件事(有很多......)

我想将unmodifiableList方法与Bar的clone()结合起来,就像下一个

public List<Bar> getBars() {
    List<Bar> returnList = new ArrayList<Bar>(bars.size());
    for (Bar b : bars) {
        returnList.add(b.clone()); // clone an instance of bar
    }
    return Collections.unmodifiableList(returnList);
}

这应该有用,但这是一个好方法吗?特别是当类bars中的列表Foo可能是一个巨大的列表时,这可能会在尝试显示概述(克隆每个实例)时减慢运行时间。

或者有一个我不知道的好选择吗?

3 个答案:

答案 0 :(得分:0)

Bar实现仅提供getter的Immutable Interface,例如ImmutableBar。将Bar的实例作为ImmutableBar传递给GUI。

答案 1 :(得分:0)

从上面澄清我的评论,关于&#34;不可变包装器&#34;:想法是ImmutableBar实现你的Bar接口,它拦截修改,例如:

/** Wraps a Bar, but prevents all modification operations. */
public final class ImmutableBar implements Bar {
    private final Bar wrapped;
    public ImmutableBar(Bar bar) {
        wrapped = bar;
    }
    @Override
    public int getMyInt() {
        return wrapped.GetMyInt();
    }
    @Override
    public void doThis(int i) {
        throw new UnsupportedOperationException("Modifications are not allowed");
    }
    // etc.
}

警告:编译时不禁止修改,而是在运行时禁止修改。但是,您模仿的行为与您已经使用的Collections#unmodifiableList相同(在尝试修改集合时也会抛出UnsupportedOperationException)。所以这可能是一个可行的解决方案。

另外请记住,如果你掌握了包裹的Bar,对这些实例的修改仍会反映在包装器上。如果您设计了一个API,您可以在其中控制要公开的实例,如果您注意,这应该不是问题。

建议:我仍然更喜欢@muued建议的解决方案,它只提供&#34;读取&#34;公共接口Bar内的操作,它具有不可变的实现(仅实现来自Bar的getter)和可变实现(另外提供mutator),MutableBarImmutableBar。看看Apache Commons&#39; Pair举个例子。这避免了开发过程中的任何歧义,因为不可变实例将不提供任何修改API。

答案 2 :(得分:0)

如果我错了,请纠正我,据我所知,如果创建不可变的话,我们不提供任何setter或修改state方法。如果dothis()方法做同样的事情,我们就无法使它成为不可变的。