V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
amiwrong123
V2EX  ›  Java

为什么 AbstractList 的 ListIterator 接口实现类, add 方法要把 lastRet 置为-1?

  •  
  •   amiwrong123 · 2019-11-23 14:45:35 +08:00 · 3513 次点击
    这是一个创建于 1830 天前的主题,其中的信息可能已经有所发展或是发生改变。
            public void add(E e) {
                //add 不用关心 lastRet 是否为-1,即不在乎之前有没有 remove 或 add
                checkForComodification();
    
                try {
                    int i = cursor;
                    AbstractList.this.add(i, e);
                    lastRet = -1;
                    cursor = i + 1;
                    expectedModCount = modCount;
                } catch (IndexOutOfBoundsException ex) {
                    throw new ConcurrentModificationException();
                }
            }
    

    疑问就是,为什么 add 方法一定要把 lastRet 置为-1 呢,remove 方法把 lastRet 置为-1 我理解,因为 lastRet 代表的那个元素已经被删除了。但 add 方法并不会删除元素啊,lastRet 指向元素还是存在的呢。

    所以我觉得源码里,这句 lastRet = -1;可以删掉啊。

    原效果: Mb3Gf1.png 删除后效果: Mb3dmD.png

    9 条回复    2019-11-23 22:52:35 +08:00
    zhoulifu
        1
    zhoulifu  
       2019-11-23 16:26:40 +08:00
    看一下 ListIterator 接口的 set()和 remove()的文档
    by73
        2
    by73  
       2019-11-23 17:19:04 +08:00
    其实嘛,考虑下如果去掉后,`next/previous(); add(); set/remove();` 到底会是怎样的结果。总的来说,因为 remove/add 之后都会改变整个迭代器的状态,导致 lastRet 失效(就是原来调用的 next 或者 previous 不再能指向正确的结果了,remove 会影响 next,而 add 会影响 previous )
    amiwrong123
        3
    amiwrong123  
    OP
       2019-11-23 19:41:38 +08:00 via Android
    @by73
    谢谢回复,我懂了,因为删除掉以后,你再执行 previous,会返回错误的元素(会返回图中第二个绿色节点,但其实应该返回那个浅蓝色节点)
    amiwrong123
        4
    amiwrong123  
    OP
       2019-11-23 20:41:46 +08:00
    @by73
    不对啊,什么叫 add 会影响 previous 啊, 就算刚执行了 add 也能再执行 previous 啊。previous 的执行前提又不需要 lastRet 不是-1。

    看第二个图,第一个状态执行了 next,然后 add,然后 add。假如`lastRet = -1;`删掉了,就是第二个图的效果。

    现在由于 lastRet 不是-1,那么也可以执行 remove 和 set 了(因为这两个操作执行的前提就是 lastRet 不是-1 )。而且删除掉后,也符合 lastRet 的含义啊:Index of element returned by most recent call to next or previous.现在 lastRet 确实是指向上一次 next 返回的元素的索引啊。
    by73
        5
    by73  
       2019-11-23 21:15:54 +08:00
    @amiwrong123 emm 我指的影响是对当前状态的影响(因为你问的是 lastRet ),“add 会影响 previous” 指的是 `previous(); add();` 在执行完 add 之后前面 previous 产生的 lastRet 就无效了。就是说在你**首先**执行完 previous 后,lastRet 指向了返回的值,之后你执行 add 往数组前面插值,那么 lastRet 指向的还是原来返回的值而非刚插入的那个,这是我所说的 lastRet 失效。next 和 remove 同理。

    add 之后执行 previous/next 当然都没问题啦,你图片里的 cursor 挺明确的了。
    amiwrong123
        6
    amiwrong123  
    OP
       2019-11-23 21:16:31 +08:00
    @zhoulifu
    看了,看完以后更加觉得 add 方法里面这句 lastRet = -1;可以删除掉了。删除掉后,还能继续执行 remove 或者 set 呢(这两个方法需要 lastRet 不为-1 ),而且删除掉后 lastRet 也是符合 Index of element returned by most recent call to next or previous 的这个含义的啊。
    amiwrong123
        7
    amiwrong123  
    OP
       2019-11-23 21:56:25 +08:00
    @by73
    好吧,是我没想到这一点。`previous(); add();` 这样执行以后,如果把那句删除掉了,那么 lastRet 指向的元素就不对了,所以需要这句把 lastRet = -1。

    那我现在有一个大胆的想法,如果改成这样是不是就合理了:
    https://paste.ubuntu.com/p/PTBDz9qDsK/
    ```java
    public void add(E e) {
    //add 不用关心 lastRet,即不在乎之前有没有 remove 或 add
    checkForComodification();

    try {
    int i = cursor;
    AbstractList.this.add(i, e);
    if (lastRet = -1)//刚执行了 remove,因为现在只有 remove 可以置为-1
    lastRet = -1;//lastRet 不变
    else if (lastRet < cursor)//刚执行了 next
    lastRet = lastRet;//lastRet 不变
    else if (lastRet = cursor)//刚执行了 previous
    lastRet = i + 1;//让 lastRet 也跟着移动,这样 lastRet 就能指向被移动到后面一格的那个元素了

    cursor = i + 1;
    expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
    throw new ConcurrentModificationException();
    }
    }

    // 总结一下逻辑,就可以改成下面这样
    public void add(E e) {
    //add 不用关心 lastRet,即不在乎之前有没有 remove 或 add
    checkForComodification();

    try {
    int i = cursor;
    AbstractList.this.add(i, e);
    if ( (lastRet != -1) & (lastRet = cursor) )
    lastRet = i + 1;
    cursor = i + 1;
    expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
    throw new ConcurrentModificationException();
    }
    }
    ```
    by73
        8
    by73  
       2019-11-23 22:31:24 +08:00
    @amiwrong123 嘛,这样的话就会跟 lastRet 的定义( Index of element returned by most recent call to next or previous )以及 remove 的定义( Removes from the list the last element that was returned by next or previous )相违背。

    我个人觉得这其实更多的是一个设计上的考虑,也不是啥致命的问题,可能当时有考虑到如 `add(); remove();` 这种二义性问题(该删除最近添加的那个还是 next/previous 的那个,你的问题中直接删除这个方案就是后者,回复中提供的代码就是前者),索性直接搞个 IllegalStateException 规避这种状态的发生。
    amiwrong123
        9
    amiwrong123  
    OP
       2019-11-23 22:52:35 +08:00
    @by73
    你说的也有道理,而且我突然觉得,对于把 lastRet 修改成一个有效状态这件事,应该只允许让 next/previous 来,因为 lastRet 的定义就是 Index of element returned by most recent call to next or previous。所以在 add 里面除了置 lastRet 为-1 外,是不允许 add 里面把 lastRet 修改成一个有效状态的(就是我 7 楼代码的 lastRet = i + 1;)
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1044 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 21:03 · PVG 05:03 · LAX 13:03 · JFK 16:03
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.