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

5 java collections iterator accessibility 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(); }
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

Ale*_*x B 23

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

public List<Double> data() { return Collections.unmodifiableList(getList()); }
Run Code Online (Sandbox Code Playgroud)

来自javadoc:

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


Uri*_*Uri 5

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

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

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

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

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

这里有一些关于制作不可变类的技巧. http://java.sun.com/docs/books/tutorial/essential/concurrency/imstrat.html


coo*_*ird 5

你能用Collections.unmodifiableList吗?

根据文档,它将返回一个不可修改的(不可变的)视图List.这将阻止使用方法等remove,并add抛出一个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);
Run Code Online (Sandbox Code Playgroud)

输出:

[10, 42]
[33, 42]
Run Code Online (Sandbox Code Playgroud)

这基本上表明,如果首先包含的数据List是可变的,那么即使列表本身是不可变的,也可以改变列表的内容.


Tom*_*ine 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

同时避免使用标签并将大括号放在Java的正确位置会很有帮助.