You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java不借助API实现ArrayList不可变(含类内不可变)的面试解决方案

嘿,这个问题问到点子上了——刚好是面试里很考验基础的理论场景,我来一步步给你捋清楚~

一、先明确核心需求

你要的是不依赖Collections.unmodifiableList()这类API,让ArrayList在「类内部、派生类、外部代码」三个层面都完全不可变,还要搞懂和hashCode()的关联——咱们逐个击破。

二、不依赖API实现完全不可变的具体方案

private final只是基础操作,但它只能保证列表的引用不被替换,里面的元素还是能通过add()/remove()修改。要实现真正的不可变,得从「封装+限制修改入口」入手:

1. 彻底封装底层ArrayList,杜绝任何修改可能

首先,把底层的ArrayList用private final修饰,但关键是类内部也绝对不调用它的任何修改方法,只在初始化时一次性填充所有元素:

public final class ImmutableMyList<T> {
    // 底层容器,完全封装,类内部也只做只读操作
    private final ArrayList<T> innerList;

    // 构造方法一次性传入所有元素,完成初始化
    public ImmutableMyList(Collection<? extends T> elements) {
        // 复制传入的集合,防止外部修改原集合影响内部
        this.innerList = new ArrayList<>(elements);
    }

    // 只暴露只读方法
    public T get(int index) {
        return innerList.get(index);
    }

    public int size() {
        return innerList.size();
    }

    // 迭代器也要做只读限制,禁止remove操作
    public Iterator<T> iterator() {
        return new Iterator<T>() {
            private final Iterator<T> innerIter = innerList.iterator();

            @Override
            public boolean hasNext() {
                return innerIter.hasNext();
            }

            @Override
            public T next() {
                return innerIter.next();
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException("这是不可变列表,禁止修改");
            }
        };
    }
}

这个类里,底层ArrayList完全被“锁死”:外部拿不到它的引用,类内部也只调用get()/size()这类只读方法,根本碰不到修改接口。

2. 为什么和hashCode()有关?

面试里提到这个点,是因为不可变对象的核心契约之一是hashCode固定不变——这样它才能安全地作为HashMap、HashSet的键。

如果你的“不可变”列表实际上还能被修改,那底层ArrayList的hashCode会跟着元素变化,导致用它当键时,HashMap出现找不到值的异常。所以在我们的自定义类里,必须重写hashCode()和equals(),并且因为底层列表不会被修改,hashCode的值永远固定:

@Override
public int hashCode() {
    // 因为innerList不会被修改,所以hashCode永远稳定
    return innerList.hashCode();
}

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    ImmutableMyList<?> that = (ImmutableMyList<?>) o;
    return innerList.equals(that.innerList);
}

反过来,如果允许底层列表被修改,hashCode就会变化,直接违背了不可变对象的设计初衷——这就是两者的关联点。

3. 确保派生类也无法突破限制

如果你的类允许被继承,一定要给类加上final修饰,防止派生类重写方法来偷偷修改底层列表:

// 加final,禁止继承,彻底封死派生类的修改可能
public final class ImmutableMyList<T> {
    // ... 其他代码不变
}

如果不能用final,那就要把所有方法都声明为final,不让派生类修改方法逻辑。

三、类内不可变的关键细节

很多人以为private final就够了,但类内部还是能写innerList.add(item)——所以核心是类内部要自觉遵守只读约定:

  • 只在构造方法里初始化底层列表,之后再也不调用任何修改方法;
  • 绝对不要在类内部的其他方法里操作innerList的修改接口(比如add()/clear());
  • 初始化时一定要复制传入的集合,别直接引用外部的集合(避免外部修改影响内部)。

内容的提问来源于stack exchange,提问作者Dharini

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:33:13