Java不通过引用传递变量。在这种情况下,ListIterator
等数据结构如何更改其对应的列表?
这是我写的一个示例迭代器:
public class OdpIterator<E> implements ListIterator<E> {
private OdpList<E> list;
private int cursor;
public OdpIterator(OdpList<E> list) {
this.list = list;
}
@Override
public void add(E arg0) {
list.add(arg0);
}
但是当我尝试将list
更改为add()
时,它不会更改基础列表,因此以下测试失败:
OdpList<Integer> list = new OdpList<Integer>();
ListIterator<Integer> iter = list.listIterator();
iter.add(42);
assertTrue(list.contains(42));
OdpList add:我认为它是正确的,因为它通过了它的单元测试。
@Override
public boolean add(E arg0) {
ListCell<E> cell = new ListCell<E>(arg0);
if (size() > 0) { //if something is already in the list
tail.setNext(cell);
tail = cell;
}
else {
head = cell;
tail = cell;
}
return true;
}
ListCell构造函数:
public class ListCell<T> {
public ListCell(T arg0) {
this.datum = arg0;
next = null;
}
}
OdpList listIterator:
@Override
public ListIterator<E> listIterator() {
return new OdpIterator<E>(this);
}
OdpList包含:
@Override
public boolean contains(Object arg0) {
return indexOf(arg0) == -1;
}
@Override
public int indexOf(Object arg0) {
return findAfter(head, arg0, 0);
}
private int findAfter(ListCell<E> o, Object search, int soFar) {
if (o == null) {
return -1;
}
if (o.getDatum() == null && search != null) {
return findAfter(o.getNext(), search, soFar + 1);
}
if ((o.getDatum() == null && search == null) || o.getDatum().equals(search)) {
return soFar;
}
return findAfter(o.getNext(), search, soFar + 1);
}
我该怎么做?或者我误解了迭代器是如何工作的?
答案 0 :(得分:6)
在人们经历过的所有心理练习之后,我几乎不愿意这样说,但是...问题只是一个错字。
@Override
public boolean contains(Object arg0) {
return indexOf(arg0) == -1;
}
应该是
@Override
public boolean contains(Object arg0) {
return indexOf(arg0) != -1;
}
仅当对象在列表中不时, contains
才会返回true
!
答案 1 :(得分:2)
它的工作原理是Java传递引用类型。它们通过价值传递,这是许多人混淆的根源。在我看来,当人们开始说Java通过引用传递对象和按值传递基元时,这种混乱会得到加强。如果您使用的语言同时支持两者,那就更是如此。
所以下面我稍微介绍了一下,并描述了我能做到的最佳方式。
Java按值传递引用类型。 Java Ranch有两篇很好的文章描述了这个:
我还使用ASCII艺术发布了这个here。让我们再来一次。
我们有方法:
void test(StringBuilder fred) {
fred.append("Fred");
}
以下代码:
StringBuilder b = new StringBuilder();
test(b);
StringBuilder b = new StringBuilder();
在记忆中,这给出了类似的东西:
b -- > ""
test(b);
然后创建一个新变量“b”,但此变量指向相同的字符串缓冲区。
在记忆中,这给出了类似的东西:
b -- +
+-> ""
fred-+
fred.append("Fred");
虽然“fred”和“b”是不同的变量,但指向同一件事。所以改变“fred”也会改变“b”。
在记忆中,这给出了类似的东西:
b -- +
+-> "Fred"
fred-+
}
现在“fred”退出了范围并被吃掉了。
b -- > "Fred"
这与“通过引用传递”的不同之处在于PBR b和fred变为一。在上面的示例中,它几乎没有区别,除了以下任何地方:
b -- +
+-> "Fred"
fred-+
在PBR中它看起来像: b,fred - &gt; “佛瑞德”
当您尝试更改“fred”指向的位置时,PBR确实显示出来。如果我们将方法改为:
void test(StringBuilder fred) {
fred = new StringBuilder("Fred");
}
我们可以看到差异。
StringBuilder b = new StringBuilder();
在记忆中,这给出了类似的东西:
b -- > ""
test(b);
对于按值传递引用类型,我们得到类似的内容:
b -- +
+-> ""
fred-+
但对于PBR我们得到:
b, fred--> ""
fred = new StringBuilder("Fred");
现在我们将看到差异。在Pass Reference By Value(Java支持)中,我们得到:
b --> ""
fred--> "Fred"
看看我们现在如何打破它们之间的联系。然而,在PBR中我们保留链接。
"" // Old string builder now float in memory all lost and alone.
b, fred--> "Fred"
答案 2 :(得分:0)
Java通过引用传递所有对象。它按值传递基元。
您的代码应更改基础列表。我会检查你的OdpList类是否存在问题。
答案 3 :(得分:0)
缺少部分代码 - OdpList
的定义 - 所以很难说这里发生了什么。显示的代码看起来正确。在列表实现中,我希望看到类似的东西:
public ListIterator<T> listIterator() {
return new OdpIterator<T>(this);
}
Java不传递引用,但它确实按值传递引用。因此,只要其引用可用,修改列表应该没有问题。
答案 4 :(得分:0)
为什么list.listIterator()
会返回OdpIterator
(找不到OdpList
的代码)?谁说“Java不通过引用传递变量”?所有实例参数都按值传递,但对对象的引用是复制为按值传递的指针。推荐此副本将始终更改原始对象!
答案 5 :(得分:0)
我认为这里的问题不在于迭代器本身,而在于底层泛型和相关类型擦除。在此之上抛出自动装箱,你就会有痛苦的配方。看看你的测试代码:
OdpList<Integer> list = new OdpList<Integer>();
ListIterator<Integer> iter = list.listIterator();
iter.add(42);
assertTrue(list.contains(42));
然后查看this page和here,了解实际情况。通用类型由编译器使用,然后由运行时环境忽略。
您正在实例化一个类型为Integer的列表,但在运行时,JVM无法确切地说明列表迭代器中的内容。这就像你的显式实例化从未发生过,所以你坚持使用通用对象。这意味着42的自动魔术自动装箱永远不会发生,因为这种行为与Integer类相关联。实际上,您的JVM可能将“list.contains(42)”中的42视为对Object类型对象的引用,这解释了测试失败的原因。
反直观的?是。马虎?是的。沮丧?极大。像这样的例子就是为什么这么多人说用Java打破泛型。
答案 6 :(得分:0)
首先,您没有按值传递原始int
类型。您正在通过引用传递就地创建的autoboxed
Integer
个对象。远离自动装箱有很多好理由。
如果没有查看完整的OdpList
列表,很难说清楚,但在审核OdpList.add
时,内部看起来有ListCells
列表,而不是Integers
列表}。所以从某种意义上说,你正在寻找一篮子苹果中的橙子。