Java List Collections.unmodifiableList 与 表示泄露

本文探讨了Java中表示泄露的问题,特别是与List相关的情况。介绍了Immutable对象如何防止表示泄露,以及Collections.unmodifiableList作为防御手段的工作原理。然而,对于Mutable对象,即使使用unmodifiableList,仍可能出现表示泄露,因为可以绕过List直接修改对象内容。解决方案包括深拷贝和限制Mutable类型直接暴露。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

表示泄露:

对于一个ADT来说,表示泄露是一项要坚决避免的事情。表示泄露就是指用户不通过我们ADT提供的方法,就可以修改ADT的内容,出现不必要的麻烦。

例如public的属性是一种表示泄露,mutable类型的数据直接传输是一种表示泄露。
我们会采用包括但不限于private final、防御式拷贝等方式来避免表示泄露。

今天我想讨论一下和List有关的表示泄露问题。

List< Immutable>

如果List当中存放的是Immutable类型的数据,我们采用防御式拷贝的方式就可以处理好表示泄露的问题。因为只要采用了防御式拷贝,对新数组的变化就不会影响到原数组。而Immutable的数据完全打消了我们对于多引用的顾虑。

防御式拷贝需要消耗额外的存储空间,还会拖慢程序的运行速度。我们还可以用Collections.unmodifiableList来进行表示泄露的防御。
下面介绍一下Collections.unmodifiableList

Collections.unmodifiableList

Collections.unmodifiableList是一个decorator,它是对我们常规List的一个包装。使用方法如下:

List<Mutable> list = new ArrayList<>();
。。。
List<Mutable> safe = Collections.unmodifiableList(new ArrayList<>(list));

生成的safe不支持“修改”

下面我们从源码追踪不可修改的原因。我们截取源码的一部分

        public E set(int index, E element) {
            throw new UnsupportedOperationException();
        }
        public void add(int index, E element) {
            throw new UnsupportedOperationException();
        }
        public E remove(int index) {
            throw new UnsupportedOperationException();
        }
        public boolean addAll(int index, Collection<? extends E> c) {
            throw new UnsupportedOperationException();
        }



		public ListIterator<E> listIterator(final int index) {
            return new ListIterator<E>() {
                private final ListIterator<? extends E> i
                    = list.listIterator(index);

                public boolean hasNext()     {return i.hasNext();}
                public E next()              {return i.next();}
                public boolean hasPrevious() {return i.hasPrevious();}
                public E previous()          {return i.previous();}
                public int nextIndex()       {return i.nextIndex();}
                public int previousIndex()   {return i.previousIndex();}

                public void remove() {//注意看这里
                    throw new UnsupportedOperationException();
                }
                public void set(E e) {
                    throw new UnsupportedOperationException();
                }
                public void add(E e) {
                    throw new UnsupportedOperationException();
                }

                @Override
                public void forEachRemaining(Consumer<? super E> action) {
                    i.forEachRemaining(action);
                }
            };
        }

可以看出,所有List的增删改方法都直接会抛出异常,就连Iterator的remove方法也没能逃过一劫。所以Collections.unmodifiableList可以实现一定程度的保护。

List< Mutable>

面对Mutable对象,防御式拷贝和Collections.unmodifiableList全都无能为力。我们用一个小实验来看。

//一个简单的mutable对象
public class Mutable {
    private int num;

    public Mutable(int num) {
        this.num = num;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }
}

public class TestList {
    public static void main(String[] args) {
        List<Mutable> list = new ArrayList<>();
        Mutable tmp = new Mutable(1);
        list.add(tmp);
        System.out.println("初始值:" + list.get(0).getNum());
        List<Mutable> copy = new ArrayList<>(list);
        List<Mutable> safe = Collections.unmodifiableList(new ArrayList<>(list));


        copy.get(0).setNum(2);
        System.out.println("利用防御式拷贝修改后原list值:" + list.get(0).getNum());

        safe.get(0).setNum(3);
        System.out.println("利用unmodifiablelist修改后原list值:" + list.get(0).getNum());

    }
}

得到输出:

初始值:1
利用防御式拷贝修改后原list值:2
利用unmodifiablelist修改后原list值:3

可见所有的方式都可以对原数组修改,表示泄露了。

这里我们所作的修改,不是基于List所提供的方法,而是直接对Mutable类型调用其mutator方法进行修改。我们的List存储的都是一个一个的引用值,而mutator方法和引用无关,它直接改变引用所在内存的内容。所以出现这种表示泄露。

因此除了List之外,我们可以延伸到所有Mutable的类型。只要Mutable的引用暴漏在ADT外部,表示泄露就无法避免。

解决方案:

  1. 对Mutable的类型构造深拷贝函数,再利用防御式拷贝的List。这样每一个对象创建新的地址就不怕修改。
  2. 禁止对Mutable类型的直接暴漏。例如一个管理Piece棋子对象的Board棋盘,Piece是mutable的,那我们只允许客户端在外部用棋子名称String进行操作而不允许直接暴露Piece对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值