Java:通过引用传递/ListIterator.add()

发布于 2024-08-07 19:48:12 字数 2021 浏览 5 评论 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);
    }

但是当我尝试通过 add() 更改 list 时,它不会更改底层列表,因此以下测试失败:

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

我该怎么做?或者我误解了迭代器的工作原理?

Java doesn't pass variables by reference. In that case, how do data structures like ListIterator make changes to their corresponding list?

Here is an example iterator I am writing:

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

But then when I try to change list through add(), it doesn't change the underlying list, so the following test fails:

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

OdpList add: I believe that it is correct, as it passes its unit tests.

@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 constructor:

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 contains:

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

How do I do this? Or am I misunderstanding how iterators work?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

生寂 2024-08-14 19:48:12

在人们经历了所有的心理练习之后,我几乎不想这么说,但是……问题只是一个拼写错误。

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

应该是

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

contains 仅当对象不在列表中时才返回 true

I almost hate to say this after all the mental exercises people have been going through, but... the problem is simply a typo.

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

should be

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

contains was returning true only if the object was not in the list!

只为守护你 2024-08-14 19:48:12

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

因此,下面我会稍微介绍一下它,并尽可能地描述它的工作原理。


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

  1. 罩杯大小——一个关于变量的故事
  2. < a href="http://www.javaranch.com/campfire/StoryPassBy.jsp" rel="nofollow noreferrer">请按值传递(杯子大小续)

我也发布了有关此此处使用 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”指向的位置时, 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"

The reason it works is Java passes reference types about. They are passed by value and this is a source of confusion for many. In my opinion this confusion is then enhanced when people start saying Java passing objects by reference and primitives by value. Even more so if you ever use a language that does support both.

So below I've gone off on one a little and described the best I can how it works.


Java passes reference types by value. Java Ranch has two great articles describing this:

  1. Cup Size -- a story about variables
  2. Pass-by-Value Please (Cup Size continued)

I have also posted about this here using ASCII art. Let us do so again.

We have the method:

void test(StringBuilder fred) {
    fred.append("Fred");
}

And the following code:

StringBuilder b = new StringBuilder();
test(b);

StringBuilder b = new StringBuilder();

In memory this gives something like:

b -- > ""

test(b);

This then creates a new variable "b", but this variable points to the same string buffer.

In memory this gives something like:

b -- +
     +-> ""
fred-+

fred.append("Fred");

While "fred" and "b" are different variables, the point to the same thing. So changing "fred" also changes "b".

In memory this gives something like:

b -- +
     +-> "Fred"
fred-+

 }

Now "fred" drops out of scope and is eaten.

b -- > "Fred"

This differs from "pass by reference" in that PBR b and fred become one. In the example above it would have little difference, except anywhere something looks like:

b -- +
     +-> "Fred"
fred-+

in PBR it looks like:
b, fred --> "Fred"

PBR really shows itself when you try to change where "fred" points too. If we alter the method to:

void test(StringBuilder fred) {
    fred = new StringBuilder("Fred");
}

we can see the difference.


StringBuilder b = new StringBuilder();

In memory this gives something like:

b -- > ""

test(b);

For Passing reference types by value we get something like:

b -- +
     +-> ""
fred-+

but for PBR we get:

b, fred--> ""

    fred = new StringBuilder("Fred");

Now we will see the difference. In Pass Reference By Value (what Java supports) we get:

b --> ""

fred--> "Fred"

See how we have now broken the link between them. In PBR however we keep the link.

           "" // Old string builder now float in memory all lost and alone.
b, fred--> "Fred"
箜明 2024-08-14 19:48:12

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

您的代码应该更改底层列表。我会检查您的 OdpList 类是否存在问题。

Java passes all Objects by reference. It passes primitives by value.

Your code should change the underlying list. I would check your OdpList class for the problem.

删除会话 2024-08-14 19:48:12

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

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

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

Some of the code is missing—the definition of OdpList—so it's hard to say what is happening here. The code that is shown looks correct. In the list implementation, I'd expect to see something like this:

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

Java doesn't pass references, but it does pass references by value. So there should be no problem in modifying the list, as long as its reference is available.

三岁铭 2024-08-14 19:48:12

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

Why should list.listIterator() return an OdpIterator (no code of OdpList seen)? And who says "Java doesn't pass variables by reference"? All instance parameters are passed by value, but a reference to an object is a pointer copied to be passed by value. Refering this copy will always change the original object!

意犹 2024-08-14 19:48:12

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

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

First of all, you are not passing primitive int type by value. You are passing autoboxed Integer objects created in-place by reference. There are many good reasons to stay away from autoboxing.

Without looking at full OdpList listing it's hard to tell, but on examination of OdpList.add, looks like internally you have a list of ListCells, not a list of Integers. So in a sense you are looking for an orange in a basket of apples.

往事风中埋 2024-08-14 19:48:12

我认为这里的问题不在于迭代器本身,而在于底层泛型和关联的类型擦除。再加上自动装箱,你就会遇到痛苦。看看你的测试代码:

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

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

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

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

I think the issue here isn't with the iterator itself, but with the underlying generics and associated type erasure. Throw autoboxing on top of this, and you have a recipe for pain. Take a look at your test code:

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

Then look at this page and here for some insight into what's really going on. Generic types are used by the compiler and then ignored by the runtime environment.

You're instantiating a list with type Integer, but at runtime, it's impossible for the JVM to suss out exactly what's in the list iterator. It's like your explicit instantiation never happened, so you're stuck with a generic object. This means the auto-magic autoboxing of 42 is never going to happen, since that behavior is tied to the Integer class. In fact, your JVM is probably treating the 42 in "list.contains(42)" as a reference to an object of type Object, which explains why the test fails.

Counter-intuitive? Yes. Sloppy? Yep. Frustrating? Tremendously. Examples like this are the reason why so many people say generics are broken in Java.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文