Java HashMap在get()调用时返回null

在尝试使用HashMap中的给定键获取值时,我们观察到了NullPointerException.

以下是我将用于说明问题的示例代码.

public class Test {

    private Map<String,Integer> employeeNameToAgeMap = new HashMap<String,Integer>();

    public int getAge(String employeeName) { 
        if (!employeeNameToAgeMap.containsKey(employeeName)) {
            int age = getAgeFromSomeCustomAPI(employeeName);
            employeeNameToAgeMap.put(employeeName,age);
        }

        return employeeNameToAgeMap.get(employeeName);
    }
}

方法的最后一行获取NullPointerException,即“return employeeNameToAgeMap.get(employeeName);”

正如我们所看到的,employeeNameToAgeMap不是null,并且调用者也没有将employeeName作为null传递(这是我们已经接受了调用代码本身).

这个方法将从不同的线程以非常快的速度调用(来自一些计划运行的计时器任务,每100ms左右)

这个NullPointerException的原因似乎是为给定员工提供的值(年龄)为空,但事实并非如此,因为保证自定义API方法(getAgeFromSomeCustomAPI())返回给定员工的某个年龄,即使它返回null,那么异常stacktrace应该显示日志中的对应行而不是最后一行.

我唯一的假设是,当一个线程T1试图填充该缓存时,T2出现了,由于某种原因,它能够发现缓存已经拥有了employeeName,但是当它试图获得年龄时,它就抛出了一个NPE.但是我并不是100%确信当put()操作正在进行给定的键和值时,相同键的containsKey()返回true.

我知道需要增强此代码以满足同步问题(通过使用ConcurrentHashMap或锁),但期待知道此问题的真正原因.

我真的很感激帮助.

解决方法

我相信你所经历的是在你打电话时对HashMap的重复
return employeeNameToAgeMap.get(employeeName);

可以相信,如果HashMap#containsKey(key)返回true,那么应该保证,调用HashMap #get(key)也应该返回一个有效值,只要该键不从HashMap中删除即可.这可以通过以下事实来论证:如果密钥对应于有效值,则HashMap#containsKey(key)确实会检查:

public boolean containsKey(Object key) {
    return getEntry(key) != null;
}

但这是一个致命的误解. HashMap#containsKey(key)只能保证密钥在调用之前已经与某个值相关联.但是,如果多个线程正在访问地图,则不能保证HashMap #get(key)也会返回相应的值.这种差异的原因是,其他线程访问HashMap#put(key,value)与任何键值对,可能会强制重新散列HashMap,这会导致重新创建内部哈希表.如果在调用HashMap#get(key)期间发生了这样的重组,那么即使你的HashMap在调用HashMap#containsKey(key)时返回true,HashMap#get(key)也可能返回null.

如果你只是想避免NullPointerException,你可以这样做:

public class Test {

    private Map<String,Integer>();

    public int getAge(String employeeName) {
        final Integer age = employeeNameToAgeMap.get(employeeName);
        if (age == null) {
            age = getAgeFromSomeCustomAPI(employeeName);
            employeeNameToAgeMap.put(employeeName,age);
        }
        return (int)age;
    }
}

这当然不会使您的代码线程保存,但您将不再获得您现在遇到的NullPointerException.

相关文章

ArrayList简介:ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增...
一、进程与线程 进程:是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。 线程...
本文为博客园作者所写:&#160;一寸HUI,个人博客地址:https://www.cnblogs.com/zsql/ 简单的一个类...
#############java面向对象详解#############1、面向对象基本概念2、类与对象3、类和对象的定义格式4、...
一、什么是异常? 异常就是有异于常态,和正常情况不一样,有错误出错。在java中,阻止当前方法或作用域...
Collection接口 Collection接口 Collection接口 Collection是最基本的集合接口,一个Collection代表一组...