记录Java Collections构造器踩坑

记录Java Collections构造器踩坑

问题定义:Java Collections构造器克隆对象后后发现使用过程中未对当前对象进行修改,但是队列内部元素发生改变。

先上实验的问题代码

克隆队列的方法:

    /**
     * clone a queue.
     * @param queue the target queue which should be cloned can't be null .
     * @return the cloned queue.
     */
    private synchronized ConcurrentLinkedQueue<Rung> cloneQueue(ConcurrentLinkedQueue<Rung> queue) {
        assert queue != null;
        return new ConcurrentLinkedQueue<Rung>(queue);
    }

队列元素的定义(mutable

/**
 * mutable.
 * Abstraction function:
 *
 * Representation invariant:
 * the monkey should not be null.
 * the rung should >=0 and <= info.RUNG.
 * Safety from rep exposure:
 * all the rep are safe from rep exposure(monkey is immutable)
 * Thread safe argument:
 * the class has no multi-thread method and all method have no accesses to multi-thread.
 */
public class Rung {
    /**
     * the monkey.
     */
    private final Monkey monkey;
    /**
     * the rung.
     */
    private int rung;
    
    // 省略部分不重要的方法
    /**
     * the method of setting the rung.
     * @param rung the rung should >=0 and <= info.RUNG.
     */
    public void setRung(int rung) {
        assert rung >= 0 && rung <= info.RUNG;
        this.rung = rung;
    }
}

仔细审查代码中的问题,猜测原因:队列元素mutable,而构造器对队列元素进行浅拷贝,实际效果可以理解为对该队列对象生成了一个新的内存别名,而内部元素仍然指向同一块内存。以ArrayList为例,查看JDK文档中的解释:

在这里插入图片描述

可以基本验证猜测是正确的,继续探索Java源码。

    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // defend against c.toArray (incorrectly) not returning Object[]
            // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

又可以看出,执行的重点代码是Arrays.copyOf(elementData, size, Object[].class);这一句,继续查看copyOf的源码:

    @HotSpotIntrinsicCandidate
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

可以看出,重点部分函数时System.arraycopy函数,查看其他资料可知该函数使用范围很广,Sun JDK版本中的ArrayListVector大量使用了System.arraycopy来操作数据,特别是同一数组内元素的移动及不同数组之间元素的复制。

    @HotSpotIntrinsicCandidate
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

从native标签可知System.arraycopy函数是一个本地方法,借鉴别人的JDK源码分析可知,该函数的底层实现有两个版本,一个是针对基本类型数据数组、另一个是针对对象的数组,针对对象的复制即为浅拷贝,复制了对象的引用,因此可以证明上述猜测的正确。附一张盗来的图:

img

解决方式

为队列中每个对象重写clone方法,实现对象的深拷贝,构造队列时先构造空队列,再遍历的将对象的深拷贝加入该队列。

感悟

首先是对mutable和immutable的理解更深了一些,在开发过程中对mutable的对象要时刻的考虑其内部变化所带来的问题,因此,如王先生在软件构造课堂上描述到的:尽量减少mutable类型的对象开发原则十分有必要。
其次是对源码解析的感悟,在分析自己代码的时候很容易就能发现是mutable引发的错误,也能很快的修改完成,但是之后的源码分析加深了我对JDK底层实现的理解,尤其是对System.arraycopy函数的理解,该本地函数使用范围很大,因此也有助于之后的源码分析。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值