java.util.Map getOrDefault的陷阱

深入探讨Java8中Map.getOrDefault()方法的使用误区,分析其与预期不符的行为,尤其是在处理null值时可能导致的NullPointerException问题,并对比MapUtils.getString()的安全性。

最近使用Java 8新增加的java.util.Map.getOrDefault()方法,结果却发现是一个大坑。

先上代码。

Map<String, String> map = Maps.newHashMap();
map.put("test", null);
System.out.println(map.getOrDefault("test", "default"));
System.out.println(map.getOrDefault("test", "default") == null);
System.out.println(org.apache.commons.collections4.MapUtils.getString(map, "test", "default"));

再看执行结果。

接下来开始分析。根据方法getOrDefault(Object key, V defaultValue),我天真地认为该方法是先从Map中get(key),如果获取到的值为null,那么就会取defaultValue,类似于org.apache.commons.collections4.MapUtils.getString(final Map<? super K, ?> map, final K key, final String defaultValue)。直到使用过后遇到了NPE时,我才认真看了下源码,源码如下。

源码的这个逻辑不是一般得坑人啊!从Map中get(key)的值不为null或者包含key时,就返回get(key)的值。但是这里有一个问题,Map中包含某个key时,该key对应的值也是可以为null的!本来想用getOrDefault来避免null并设置默认值的,结果还是有可能返回null并可能造成NPE问题。

结论:java.util.Map.getOrDefault() 方法并不能很贴切实际业务的使用,还是使用MapUtils.getXxx(final Map<? super K, ?> map, final K key, final Xxx defaultValue)来安全地获取非null值吧!

package com.kucun.Config; import java.io.IOException; import java.io.InputStream; import java.util.Map; import javax.servlet.Filter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import com.fasterxml.jackson.databind.ObjectMapper; // 2. 基础安全配置 @Configuration @EnableWebSecurity // 启用Web安全功能 public class SecurityConfig extends WebSecurityConfigurerAdapter{ /** * 核心安全过滤器链配置 * @param http HTTP安全构建器 * @return 安全过滤器链 * @throws Exception 配置异常 * * █ 配置逻辑说明: * 1. authorizeHttpRequests: 定义访问控制规则 * 2. formLogin: 配置表单登录 * 3. logout: 配置注销行为 * 4. exceptionHandling: 处理权限异常[^3] */ // 修正后的配置方法 @Override protected void configure(HttpSecurity http) throws Exception { http .addFilterBefore(jsonAuthFilter(), UsernamePasswordAuthenticationFilter.class) .authorizeRequests() .antMatchers("/login.html").permitAll() .antMatchers(HttpMethod.POST, "/users/login").permitAll() .antMatchers("/users/guanli/**").hasRole("ADMIN") .antMatchers("/js/**", "/css/**", "/fonts/**", "/images/**","/*").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login.html") .loginProcessingUrl("/users/login") .successHandler(ajaxAuthenticationSuccessHandler()) // 自定义成功处理器 .failureHandler(ajaxAuthenticationFailureHandler()) // 自定义失败处理器 .defaultSuccessUrl("/index.html") .failureUrl("/login.html?error=true") .usernameParameter("andy") // 修改用户名参数名 .passwordParameter("pass") // 修改密码参数名 .and() .logout() .logoutUrl("/logout") .logoutSuccessUrl("/login.html") .and() .csrf() .ignoringAntMatchers("/users/login") .and() .headers() .frameOptions().sameOrigin() .and() .exceptionHandling() .accessDeniedHandler(accessDeniedHandler()); // 统一使用Handler } // 返回JSON格式的成功响应 @Bean public AuthenticationSuccessHandler ajaxAuthenticationSuccessHandler() { return (request, response, authentication) -> { response.setStatus(HttpStatus.OK.value()); response.getWriter().write("{\"code\":200, \"message\":\"登录成功\"}"); }; } // 返回401状态码和错误信息 @Bean public AuthenticationFailureHandler ajaxAuthenticationFailureHandler() { return (request, response, exception) -> { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write("{\"code\":401, \"message\":\"认证失败\"}"); }; } // 处理未认证请求 @Bean public AuthenticationEntryPoint ajaxAuthenticationEntryPoint() { return (request, response, authException) -> { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write("{\"code\":401, \"message\":\"未登录\"}"); }; } @Bean public JsonUsernamePasswordAuthenticationFilter jsonAuthFilter() throws Exception { JsonUsernamePasswordAuthenticationFilter filter = new JsonUsernamePasswordAuthenticationFilter(); filter.setAuthenticationManager(authenticationManagerBean()); filter.setUsernameParameter("andy"); // 设置自定义参数名 filter.setPasswordParameter("pass"); filter.setFilterProcessesUrl("/users/login"); return filter; } /** * 密码编码器(必须配置) * 使用BCrypt强哈希算法加密 */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AccessDeniedHandler accessDeniedHandler() { System.out.println("0000"); return (request, response, ex) -> { if (!response.isCommitted()) { response.sendRedirect("/error/403"); } }; } } class JsonUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private final ObjectMapper objectMapper = new ObjectMapper(); @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) { try { // 解析JSON请求体 InputStream is = request.getInputStream(); Map<String, String> authMap = objectMapper.readValue(is, Map.class); String username = authMap.get(getUsernameParameter()); String password = authMap.get(getPasswordParameter()); UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } catch (IOException e) { throw new AuthenticationServiceException("认证请求解析失败", e); } } else { return super.attemptAuthentication(request, response); } } } /users/login 用的是ajak+post+json请求,现在是后端接收到数据也在数据库了匹配到用户,但就是返回401
05-29
private List<DtoWorkSheetData> getAllSampleForProject(List<DtoWorkSheetData> workSheetDataList, Map<String, List<String>> projectId2sampleIdsMap, List<String> missYyIds, int i, String projectId, boolean qcFilterTest) { //该项目所有相关的样品 List<DtoWorkSheetData> dataForProject = workSheetDataList.stream().filter(p -> p.getProjectId().equals(projectId)).collect(Collectors.toList()); List<String> yyIds = dataForProject.stream().map(DtoWorkSheetData::getSampleId).distinct().collect(Collectors.toList()); List<String> extSmpIdsForProject = projectId2sampleIdsMap.getOrDefault(projectId, new ArrayList<>()); for (String extSmpId : extSmpIdsForProject) { if (!yyIds.contains(extSmpId)) { yyIds.add(extSmpId); } } if (i == 0 && StringUtil.isNotEmpty(missYyIds)) { yyIds.addAll(missYyIds); } List<String> sampleIdsInThePro = getSampleIdsTheProject(workSheetDataList, yyIds); //过滤掉已删除的样品 List<DtoSample> existSampleList = StringUtil.isNotEmpty(sampleIdsInThePro) ? commonRepository.find("select a from DtoSample a where a.id in :ids and a.isDeleted = 0", Collections.singletonMap("ids", sampleIdsInThePro)) : new ArrayList<>(); final List<String> sampleIdsInTheProFinal = existSampleList.stream().map(DtoSample::getId).collect(Collectors.toList()); //项目相关的所有样品 List<DtoWorkSheetData> allSampleInThePro = workSheetDataList.stream() .filter(p -> sampleIdsInTheProFinal.contains(p.getSampleId())).collect(Collectors.toList()); //曲线校核样有关联样品id,需要单独过滤出来作为不与项目相关的样品 List<DtoWorkSheetData> jhCurveDataList = workSheetDataList.stream().filter(p -> EnumLIM.EnumQCType.曲线校核.getValue().equals(p.getQcType()) && EnumLIM.EnumQCGrade.内部质控.getValue().equals(p.getQcGrade())).collect(Collectors.toList()); allSampleInThePro.removeAll(jhCurveDataList); List<String> testIdForProject = allSampleInThePro.stream().map(DtoWorkSheetData::getTestId).filter(StringUtil::isNotEmpty).distinct().collect(Collectors.toList()); List<DtoWorkSheetData> qcSampleList = workSheetDataList.stream().filter(p -> p.getIsQC() && p.getAssociateSampleId().equals(UUIDHelper.GUID_EMPTY)).collect(Collectors.toList()); for (DtoWorkSheetData workSheetData : jhCurveDataList) { if (qcSampleList.stream().noneMatch(p -> p.getAnalyseDataId().equals(workSheetData.getAnalyseDataId()))) { qcSampleList.add(workSheetData); } } //除了室内空白样,标样以外,其他不与项目相关的样品,只在遍历第一个项目的样品时获取 //过滤出室内空白样,标样,和其他样品 List<DtoWorkSheetData> inKbSampleList = new ArrayList<>(); List<DtoWorkSheetData> bzSampleList = new ArrayList<>(); List<DtoWorkSheetData> otherSampleList = new ArrayList<>(); for (DtoWorkSheetData workSheetData : qcSampleList) { if (EnumLIM.EnumQCGrade.内部质控.getValue().equals(workSheetData.getQcGrade()) && new QualityBlank().qcTypeValue().equals(workSheetData.getQcType())) { inKbSampleList.add(workSheetData); } else if (EnumLIM.EnumQCGrade.内部质控.getValue().equals(workSheetData.getQcGrade()) && new QualityStandard().qcTypeValue().equals(workSheetData.getQcType())) { bzSampleList.add(workSheetData); } else { otherSampleList.add(workSheetData); } } if (qcFilterTest) { //生成多份记录单时,室内空白样,标样按照项目下样品的测试项目进行过滤 inKbSampleList = inKbSampleList.stream().filter(p -> testIdForProject.contains(p.getTestId())).collect(Collectors.toList()); bzSampleList = bzSampleList.stream().filter(p -> testIdForProject.contains(p.getTestId())).collect(Collectors.toList()); } allSampleInThePro.addAll(inKbSampleList); allSampleInThePro.addAll(bzSampleList); if (i == 0) { if (qcFilterTest) { //生成多份记录单时,室内空白样,标样按照项目下样品的测试项目进行过滤 otherSampleList = otherSampleList.stream().filter(p -> testIdForProject.contains(p.getTestId())).collect(Collectors.toList()); } allSampleInThePro.addAll(otherSampleList); } getYyFromQc(workSheetDataList, allSampleInThePro, qcSampleList); if (qcFilterTest) { //遍历每一个项目时,都把曲线校核,仪器空白,试剂空白,采样介质空白,阴性对照试验,阳性对照试验,校正系数检验样品添加进来 addSamplesForEachProject(workSheetDataList, allSampleInThePro, testIdForProject, qcFilterTest); } return allSampleInThePro; }仔细阅读这段代码。看一下有没有可能 根据参数projectId 的不同,最后返回的结果是两个一样的数据。
最新发布
09-02
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值