谈下Java集合框架存储是否能存储null

本文探讨了Java集合框架中Map、List和Set对null值的处理。HashMap允许存储一个null键,但键不能重复;Hashtable不允许存储null键和值;TreeMap允许存储null值,但null键会抛出异常。ArrayList和LinkedList可以存储null值,而HashSet有且仅能存储一个null值。Hashtable的设计不支持null是为了保证多线程操作的原子性。

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

前言:
记得初学 Java Collections Framework时,每次需要记一些如Hashtable不能存储null,HashMap能存储,还有List和Set能不能存储null,这在我初学阶段都是得死记硬背的,最近看了下集合框架的一些源码加上网上各路大神的看法,所以总结了下,如有误,请及时指正。

看看Map

看看HashMap的put方法


public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
}

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

这里put时需要计算hash()方法,当key为null时,hash()直接返回0,不会调用key。hashCode()方法。不会产生空指针报错。不过HashMap的key只能存储一个null,因为key是不能重复的,所以有且只能有一个null。
注意:HashMap 允许有一个 Node 的 Key 为 null,该 Node 一定会放在第 0 个桶的位置,因为这个 Key 无法计算 hashCode(),因此只能规定一个桶让它存放。

下面看看Hashtable

 public synchronized V put(K key, V value) {
        // Make sure the value is not null
        if (value == null) {
            throw new NullPointerException();
        }

        // Makes sure the key is not already in the hashtable.
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);
        return null;
    }

在方法开头也对value进行了null的判断,如果为空直接抛出空指针异常。而且这里没有管是否key为null,所以在int hash = key.hashCode();这行中如果key为null,会报空指针异常。从源码直接看到Hashtable不能存储null的key和value。

看看TreeMap

public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

还有这段代码中的compare()方法:

@SuppressWarnings("unchecked")
    final int compare(Object k1, Object k2) {
        return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
            : comparator.compare((K)k1, (K)k2);
    }

可以看到key为空时会抛出空指针异常,这里没有直接看出value是否能为空,但是通过自己写单元测试后发现是可以存储value为null的值的。还有从compare方法中的compreTo方法可以看出key为null也会抛出空指针异常。

接下来看看List

看看ArrayList的源码:

public boolean add(E e) {
    	// 看是否需要扩容,需要则进行扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

这里是没有对是否为null有所要求的,且List底层为Object数组。无论存不存储null是没有影响的。而对于LinkedList,底层为双向链表,节点的值node.value=null是没有影响的。

对于Vector,和ArrayList源码很相似,add方法几乎相同也没有对null的限制。

Set

由于HashSet的底层是HashMap,Set存储的值都是在key中,所以可以看出Set不能存储重复值的原因就来自于Key不能重复,由于是HashMap,所以key是可以为null的即HashSet有且仅有一个null值。
而对于TreeSet,其底层也是TreeMap,所以对于null的限制也同TreeMap,这里就不多说了。

为什么Hashtable键和值都设计成不能为null

参考https://www.jianshu.com/p/c32e192e371d中的引自牛客网的大佬的解释:
因为Hashtable,ConcurrentHashMap它们是用于多线程的,并发的,如果map.get(key)得到了null ,不能判断到底是映射的value是nll,还是因为没有找到对应的key而为空,而用于单线程状态的Hashmap却可以用containsKey ( key )去判断到底是否包含了这个null。而为什么Hashtable不能用containsKey()方法呢?因为要考虑到原子性问题:一个线程先get(key)再containsKey(key),这两个方法的中间时刻,其他线程怎么操作这个key都会可能发生,例如删掉这个key。就不能保证操作的原子性了。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值