0

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);
    }

但是当我尝试更改listadd(),它不会更改基础列表,因此以下测试失败:

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 列表迭代器:

@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);
}

我该怎么做呢?还是我误解了迭代器的工作原理?

4

7 回答 7

6

在人们经历了所有的心理锻炼之后,我几乎不想这么说,但是......问题只是一个错字。

@Override
public boolean contains(Object arg0) {
    return indexOf(arg0) == -1;
}

应该

@Override
public boolean contains(Object arg0) {
    return indexOf(arg0) != -1;
}

containstrue仅当对象不在列表中时才返回!

于 2009-10-15T17:45:12.087 回答
2

它起作用的原因是Java传递引用类型。它们是按值传递的,这使许多人感到困惑。在我看来,当人们开始说 Java 通过引用传递对象和通过值传递原语时,这种混乱就会加剧。如果您曾经使用过同时支持两者的语言,则更是如此。

所以下面我已经开始了一点,并尽我所能描述了它是如何工作的。


Java 按值传递引用类型。Java Ranch 有两篇很棒的文章描述了这一点:

  1. 罩杯尺寸——一个关于变量的故事
  2. 请按价值传递(罩杯尺寸续)

我还使用 ASCII 艺术在此处发布了有关此内容的信息。让我们再次这样做。

我们有方法:

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 --> "Fred"

当您尝试更改“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");

现在我们将看到不同之处。在按值传递引用(Java 支持的)中,我们得到:

b --> ""

fred--> "Fred"

看看我们现在是如何断开它们之间的联系的。然而,在 PBR 中,我们保留了链接。

           "" // Old string builder now float in memory all lost and alone.
b, fred--> "Fred"
于 2009-10-15T16:58:11.230 回答
0

Java 通过引用传递所有对象。它按值传递原语。

您的代码应该更改基础列表。我会检查你的 OdpList 类是否有问题。

于 2009-10-15T16:32:55.667 回答
0

缺少一些代码——定义——OdpList所以很难说这里发生了什么。显示的代码看起来是正确的。在列表实现中,我希望看到如下内容:

public ListIterator<T> listIterator() {
  return new OdpIterator<T>(this);
}

Java 不传递引用,但它确实按值传递引用。所以修改列表应该没有问题,只要有它的引用即可。

于 2009-10-15T16:37:28.900 回答
0

为什么要list.listIterator()返回一个OdpIterator(没有OdpList看到代码)?谁说“Java 不通过引用传递变量”?所有实例参数都是按值传递的,但是对对象的引用是一个指针,它被复制为按值传递。引用此副本将始终更改原始对象!

于 2009-10-15T16:37:29.280 回答
0

我认为这里的问题不在于迭代器本身,而在于底层泛型和相关类型擦除。在此之上加上自动装箱,你就有了痛苦的秘诀。看看你的测试代码:

OdpList<Integer> list = new OdpList<Integer>();
ListIterator<Integer> iter = list.listIterator();
iter.add(42);
assertTrue(list.contains(42));

然后查看此页面此处,了解实际情况。泛型类型由编译器使用,然后被运行时环境忽略。

您正在实例化一个 Integer 类型的列表,但在运行时,JVM 不可能准确地确定列表迭代器中的内容。就像您的显式实例化从未发生过一样,所以您被一个通用对象困住了。这意味着 42 的自动魔术自动装箱永远不会发生,因为该行为与 Integer 类相关。实际上,您的 JVM 可能将“list.contains(42)”中的 42 视为对 Object 类型对象的引用,这解释了测试失败的原因。

反直觉?是的。邋遢?是的。令人沮丧?非常好。像这样的例子就是为什么这么多人说泛型在 Java 中被破坏的原因。

于 2009-10-15T17:23:36.670 回答
0

首先,您不是int按值传递原始类型。您正在传递autoboxed Integer通过引用就地创建的对象。远离自动装箱有很多很好的理由。

不查看完整OdpList列表很难判断,但在检查 时OdpList.add,内部看起来您有一个 列表ListCells,而不是Integers. 因此,从某种意义上说,您正在寻找一篮苹果中的橙子。

于 2009-10-15T17:24:15.737 回答