5

我已经用 Java 实现了一个类,它在内部存储了一个List。我希望这个类是不可变的。但是,我需要对在类上下文中没有意义的内部数据执行操作。因此,我有另一个类定义了一组算法。这是一个简化的示例:

包装器.java

import java.util.List;
import java.util.Iterator;

public class Wrapper implements Iterable<Double>
{
    private final List<Double> list;

    public Wrapper(List<Double> list)
    {
        this.list = list;
    }

    public Iterator<Double> iterator()
    {
        return getList().iterator();
    }

    public List<Double> data() { return getList(); }
}

算法.java

import java.util.Iterator;
import java.util.Collection;

public class Algorithm
{
    public static double sum(Collection<Double> collection)
    {
        double sum = 0.0;
        Iterator<Double> iterator = collection.iterator();

        // Throws NoSuchElementException if the Collection contains no elements
        do
        {
            sum += iterator.next();
        }
        while(iterator.hasNext());

        return sum;
    }
}

现在,我的问题是,有没有一种可靠的方法可以防止有人修改我的内部数据,尽管我的类是不可变的?虽然我提供了一个用于只读目的的data()方法,但没有什么可以阻止某人通过clear()remove()等方法修改数据。现在,我意识到我可以通过迭代器专门提供对我的数据的访问。但是,有人告诉我传递Collection是典型的。其次,如果我有一个算法需要对数据进行多次传递,我将不得不提供多个迭代器,这似乎是一个滑坡。

好的,希望有一个简单的解决方案可以解决我的担忧。我刚刚回到 Java,在处理C++ 中的const之前从未考虑过这些事情。提前致谢!

哦!我刚刚想到的还有一件事。我实际上无法返回内部List的副本。该列表通常包含数十万个元素。

4

4 回答 4

23

您可以使用Collections.unmodifiableList并修改数据方法。

public List<Double> data() { return Collections.unmodifiableList(getList()); }

从javadoc:

返回指定列表的不可修改视图。此方法允许模块为用户提供对内部列表的“只读”访问权限。对返回列表的查询操作“通读”到指定列表,并尝试修改返回的列表,无论是直接还是通过其迭代器,都会导致 UnsupportedOperationException。

于 2009-06-11T03:05:44.500 回答
5

有很多事情可以使您的类正确地不可变。我相信这是在 Effective Java 中讨论的。

正如许多其他答案中提到的,要停止list通过返回的迭代器进行修改,Collections.unmodifiableList提供了一个只读接口。如果这是一个可变类,您可能希望复制数据,以便返回的列表不会更改,即使此对象发生更改。

传递给构造函数的列表稍后可能会被修改,因此需要复制。

该类是可子类化的,因此可以覆盖方法。所以上课final。更好地提供静态创建方法来代替构造函数。

public final class Wrapper implements Iterable<Double> {
    private final List<Double> list;

    private Wrapper(List<Double> list) {
        this.list = Collections.unmodifiableList(new ArrayList<Double>(list));
    }

    public static Wrapper of(List<Double> list) {
         return new Wrapper(list);
    }

    public Iterator<Double> iterator() {
        return list.iterator();
    }

    public List<Double> data() {
        return list;
    }
}

此外,避免制表符并将大括号放在 Java 的正确位置也会有所帮助。

于 2009-06-11T08:53:41.110 回答
5

Java 没有不可变类的语法概念。作为程序员,是否授予对操作的访问权限取决于您,但您始终必须假设有人会滥用它。

一个真正不可变的对象并没有为人们提供改变状态或访问可用于改变状态的状态变量的方法。您的课程并非像现在这样一成不变。

使其不可变的一种方法是返回内部集合的副本,在这种情况下,您应该很好地记录它并警告人们在高性能代码中使用它。

另一种选择是使用包装器集合,如果有人试图更改值,它将在运行时抛出异常(不推荐,但可能,请参阅 apache-collections 示例)。我认为标准库也有一个(查看 Collections 类)。

第三种选择,如果某些客户端更改数据而其他客户端不更改,则为您的类提供不同的接口。假设您有一个 IMyX 和 IMyImmutableX。后者只是定义了“安全”操作,而前者扩展了它并添加了不安全的操作。

以下是制作不可变类的一些技巧。 http://java.sun.com/docs/books/tutorial/essential/concurrency/imstrat.html

于 2009-06-11T03:05:47.480 回答
5

你能用Collections.unmodifiableList吗?

根据文档,它将返回一个不可修改(不可变)的List. 这将阻止使用诸如removeadd通过抛出UnsupportedOperationException.

但是,我认为它不会阻止修改列表本身中的实际元素,所以我不太确定这是否足够不可变。至少列表本身不能被修改。

这是一个示例,其中List返回的内部值unmodifiableList仍然可以更改:

class MyValue {
    public int value;

    public MyValue(int i) { value = i; }

    public String toString() {
        return Integer.toString(value);
    }
}

List<MyValue> l = new ArrayList<MyValue>();
l.add(new MyValue(10));
l.add(new MyValue(42));
System.out.println(l);

List<MyValue> ul = Collections.unmodifiableList(l);
ul.get(0).value = 33;
System.out.println(l);

输出:

[10, 42]
[33, 42]

这基本上表明,如果列表中包含的数据List首先是可变的,则列表的内容可以更改,即使列表本身是不可变的。

于 2009-06-11T03:07:00.613 回答