我被分配在java中创建一个不可变的队列类,在那个过程中我所做的实际上是一个私有的最终arraylist,一个startIndex,一个lastIndex作为字段。每次我执行enqueue我做的是在arraylist顶部添加新值并返回一个新的类实例,其中包含更改的开始和最后一个索引值。出队操作也以类似的方式执行。这样,在每次入队/出队操作之后,新实例具有其自己的起始索引,即阵列列表的最后索引值,而前一个实例保持这些字段的旧值。
我的问题是,我可以将此类称为不可变的吗?谢谢。
答案 0 :(得分:1)
不,这个班不是一成不变的。
基本上,线程和Java内存模型是语言规范的一部分,因此每当您询问类的行为时,都必须考虑语言的这些部分。由于ArrayList
不是线程安全的,并且您对其进行了变异,因此您不能依赖于ArrayList
来展示预期的行为,例如在存在多个线程的情况下返回其数组中的第一个或最后一个元素。
Java Language Specification says:
中拍摄的钱虽然前面章节中的大部分讨论都是关注的 只有执行单个语句或代码的行为 表达式一次,即由单个线程,Java Virtual Machine可以同时支持多个执行线程。这些线程 独立执行对值和对象进行操作的代码 居住在共享主记忆中......
线程由Thread类表示。对用户来说唯一的方式 创建一个线程就是创建这个类的对象;每个线程 与这样的对象相关联。线程将在启动时启动 在相应的Thread对象上调用start()方法。
线程的行为,特别是在未正确同步的情况下, 可能会令人困惑和违反直觉。
更具体地说,如果两个动作共享发生在之前的关系, 它们不一定必须按顺序发生 任何他们不共享发生在之前的代码 关系。写入与读取数据竞争中的一个线程 例如,在另一个线程中,可能看起来不按顺序发生 那些读物。
由于ArrayList
不是线程安全的(没有发生 - 关系之前)写入可以按任何顺序发生 - 当内部数组初始化为空时,您可能会看到初始值,一些以前的写,或最新的写。人们无法分辨。
所以这是反直觉的,因为你认为如果你有一个ArrayList
的最后一个元素的索引,那么ArrayList
将返回它的值最后一个元素,但规范说"没有。"
阅读我链接的规范,并获取Brian Goetz的 Java Concurrency in Practice 的副本。那本书基本上是所有线程和Java的圣经。
答案 1 :(得分:0)
对于不可变性,队列字段的内部状态绝不能因排队和出队元素而改变。一种方法是每次创建一个全新的队列实例并支持ArrayList。与通常的队列实现相比,这将执行得非常差,因为入队和出队操作都将花费O(n)时间。
我不确定这是什么用途,因为通常会假设队列改变状态 - 但也许你正在寻找这样的东西:
public class ImmutableQueue<T> {
private final List<T> elements;
public ImmutableQueue() {
elements = new ArrayList<T>();
}
private ImmutableQueue(List<T> elements) {
this.elements = elements;
}
public ImmutableQueue<T> enqueue(T element) {
List<T> copy = new ArrayList<T>(elements);
copy.add(element);
return new ImmutableQueue<T>(copy);
}
public DequeueResult dequeue() {
if (elements.size() == 0) {
throw new NoSuchElementException();
}
List<T> copy = new ArrayList<T>(elements);
T head = copy.remove(0);
return new DequeueResult(head, new ImmutableQueue<T>(copy));
}
public class DequeueResult {
public final T element;
public final ImmutableQueue<T> queue;
public DequeueResult(T element, ImmutableQueue<T> queue) {
this.element = element;
this.queue = queue;
}
}
}