Java中的不可变对象和访问数据

时间:2009-06-11 02:58:24

标签: java iterator accessibility collections immutability

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

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

Algorithm.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()等方法修改数据除去()的。现在,我意识到我可以通过迭代器提供对我的数据的访问。但是,我被告知通常会传递 Collection 。其次,如果我有一个算法需要多次传递数据,我将不得不提供多个迭代器,这似乎是一个滑坡。

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

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

4 个答案:

答案 0 :(得分:23)

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

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

来自javadoc:

  

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

答案 1 :(得分:5)

Java没有不可变类的语法概念。作为程序员,您可以自由操作,但是您总是必须假设有人会滥用它。

真正的不可变对象不能为人们提供改变状态或访问可用于改变状态的状态变量的方法。你的班级现在不是一成不变的。

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

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

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

以下是有关创建不可变类的一些提示。 http://java.sun.com/docs/books/tutorial/essential/concurrency/imstrat.html

答案 2 :(得分:5)

您可以使用Collections.unmodifiableList吗?

根据文档,它将返回List的不可修改(不可变)视图。这会通过抛出remove来阻止使用addUnsupportedOperationException等方法。

但是,我没有看到它不会阻止修改列表本身中的实际元素,所以我不太确定它是否足够不变。至少列表本身不能修改。

以下是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中包含的数据首先是可变的,那么即使列表本身是不可变的,也可以更改列表的内容。

答案 3 :(得分: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的正确位置会很有帮助。