开发规范 - 空指针异常等低级问题注意点 📅 发布时间:2026/8/28 13:40:18 👁 浏览次数: 影响范围可能造成一整个接口瘫痪如果是核心接口瘫痪那么后果不堪设想涉及到数据记录的接口则会导致客户丢失异常时间范围内的数据且无法恢复(大数据得不到推送)通用判断这种写法有点就是可以直接拿int和Integer来做比较否则就需要先把int转为Integer但风险点就在于有可能直接拿数值和枚举去做比较了开发和检查过程中药重点留意下这里if(Objects.equals(task.getStatus(),MeetpointsLMAnalyseStatusEnum.PROCESSED.getCode()){}字符串字符串转jsonObj转之前一定要判断字符串是非空的因为如果字符串为null或者那么parseObject就的结果就为null后面就可能导致空指针异常字符串非空但如果是非json格式转换过程中就会报错但这通常是调用上游获取的结果不规范导致的,但如果没有非空判断那就是你的问题if(StringUtils.isNotEmpty(resultVo.getExternal())){JSONObjectparseObjectJSONObject.parseObject(resultVo.getExternal());resultVo.setLocation(parseObject.getString(geometry));}空字符判断空参考org.apache.commons.lang3.StringUtilspublicstaticbooleanisEmpty(finalCharSequencecs){returncsnull||cs.length()0;}字符串相等判断通过String类型做方法调用比如做equals判断时一定要保证方法左边的字符串对象非null右边的可以省略判断有常量的话常量一定是要放到左边的如果是两个变量做判断那么左边的变量要做非空判断用于判断对象(枚举)时可能两个对象不是同一个包下的导致预期应该相等但实际不相等尽量避免这种同名不同包的情况如果无法避免开发中作为敏感点对待if(channelCode.equals(com.lazada.ad.media.common.enums.ChannelCode.FACEBOOK_MPA)){字符串拼接字符串拼接null值情况直接用号的话如果被的是一个为null的对象那么会直接把null作为字符串拼接上如果是用append的话拼接一个null值会出现控制字异常转字符串非String类型的基本数据类型和常量使用toString做转换时也要注意非空判断String.valueOf(null);会报空指针异常底层原因是该方法内存没有对null的特殊处理字符串转为数值类型标准代码if(StringUtils.isNumeric(accountId)part.compareTo(Long.parseLong(hashPart.toString()))0){FbMpaSellerTokenEntitysellerTokennewFbMpaSellerTokenEntity();}对null操作注意点对null强转不会空指针强转后的结果依然是nullfor循环/遍历注意点集合为空不报错集合为null会报错,对集合做循环前需要判断集合非空非null循环前对集合做了非空非null判断并不意味着已经没有风险了因为集合中如果存在为null的对象我们从集合取出对象后对该对象做操作会被空指针异常。集合操作注意点stream相关mapListDriverVisualPictureDTOgroupedDtosnewArrayList();DriverVisualPictureDTOdtonewDriverVisualPictureDTO();DriverVisualPictureDTOdto2newDriverVisualPictureDTO();DriverVisualPictureDTOdto3newDriverVisualPictureDTO();dto3.setPicTime(1765798564600L);groupedDtos.add(dto);groupedDtos.add(dto2);groupedDtos.add(dto3);ListLongtimestampRangegroupedDtos.stream().map(DriverVisualPictureDTO::getPicTime).collect(Collectors.toList());System.out.println(JSON.toJSONString(timestampRange));打印结果我们会发现timestampRange集合前两个都是null所以要么我们通过fiter过滤出一个不存在null的集合要么在遍历timestampRange集合时注意非空判断Optional相关findFirst返回的是Optional类型如果集合中没有元素或者集合中只有null元素那么直接调用Optional的get元素不仅仅是取不到元素还会抛出NoSuchElementException异常规范的写法应该如下ListLongtimestampRangenewArrayList();// 不规范写法System.out.println(timestampRange.stream().findFirst().get());// 规范写法System.out.println(timestampRange.stream().findFirst().orElse(null));从集合中取最大值,涉及到Optional(max和min返回的是个Optional)所以集合中要保证不能有null元素才行否则会报错ListLongtimestampListnewArrayList();timestampList.add(1765798564600L);timestampList.add(null);timestampList.add(null);// 过滤null值后集合不为空才能做获取最大最小值处理ListLongfilterListtimestampList.stream().filter(timestamp-Objects.nonNull(timestamp)).collect(Collectors.toList());if(CollectionUtils.isNotEmpty(filterList)){System.out.println(filterList.stream().max(Long::compare).get());System.out.println(filterList.stream().min(Long::compare).get());}集合判空对集合操作前一定要先做集合判空如果使用的集合类型的引用引用没有被初始化默认值为null那么一定也不要忘记后续对集合做初始化赋值参考org.springframework.util.CollectionUtilspublicstaticbooleanisEmpty(NullableCollection?collection){return(collectionnull||collection.isEmpty());}其中最常用的集合HashMap和ArrayList均是publicbooleanisEmpty(){returnsize0;}所以往集合放元素的时候一定要注意不能放入值为null的元素存入集合元素的时机存入对象元素数据源来源可能如下如果把这些数据源存入集合前一定要做非null判断从mysqles数据库取数据从redis 缓存取数据从threadlocalhashmap本地缓存取数据通过依赖注入注入属性Spring中取数据new/反射/克隆/序列化反序列化创建一个对象对象操作对一个对象操作时对象可能是作为入参、从redis或者本地集合缓存取出的对其读写前一定要做对象非空判断Objects.nonNull()尽量不要使用基本数据类型作为对象的属性类型使用基本数据类型的封装类型例如public class CpasAdSetEntity { /** * 主键 */ TableId(value id, type IdType.AUTO) private Long id; private Date gmtCreate; private Date gmtModified; private String accountId; private String adSetId; private String campaignId; private String productSetId; private long startTime; }private Long toTimestamp(String dateTimeStr) { if (StringUtils.isBlank(dateTimeStr)) { return null; } // 定义日期时间字符串的解析器指定偏移量格式为 HHmm DateTimeFormatter formatter DateTimeFormatter.ofPattern(yyyy-MM-ddTHH:mm:ssZ); // 将字符串解析为 OffsetDateTime 对象 OffsetDateTime offsetDateTime OffsetDateTime.parse(dateTimeStr, formatter); // 转换为时间戳 return offsetDateTime.toInstant().toEpochMilli(); } public static void main(String[] args) { cpasAdSetDO.setStartTime(toTimestamp(adSetDTO.getStartTime())); }上述代码如果toTimestamp方法的合法性校验失败返回null那么cpasAdSetDO调用setStartTime方法时就会出现空指针异常mysql注意点关联集合查询除非是直接和查询页面交互的接口如果业务逻辑中需要做关联查询一定要对条件做非空判断否则很容易全表查询浪费性能字段插入如果是json类型的字段那么在把jsonObject或jsonArray字段转为字符串前要对其做非空判断不然容易插入’{}‘或者’[]‘对其做非空判断的话还要额外考虑为’{}‘或’[]‘的情况dto与vo某些字段类型不一致需要转换时单值返回可以用pojo非单值一定要用集合如果插入字段入参dto对象和实体对象vo字段类型不一样数据库字段是Integer,入参字段是String且入参字段是空字符串或null那么会报异常NumberFormatException数据库字段是String,入参字段是Integer,如果入参字段是null那么可能会把字符串null插入到mysql总之如果插入字段入参dto对象和实体对象vo字段类型不一样那么做类型转换前一点要做下非空判断查询条件如果没有非空判断带入了查询条件为null的场景那么实际情况就是这样是查不出数据的即便是mysql中有associated_order_id为null的值SELECT * FROM smart_analysis_work_order_major where associated_order_id null;因为语法错了需要使用SELECT * FROM smart_analysis_work_order_major where associated_order_id is null;pojo从接口入参中取出的对象尽量使用pojo来做接收事实上目前标准接口的入参都是json类型的必须用pojo类型入参如果是pojo类型的话默认情况下不会出现null除非用RequestParam/RequestBody(required false) 注解表示允许接受值为null的pojo这种情况下才要对入参做判断String类型来获取参数参数可能为null对String类型的入参操作时需要提前做非空判断对pojo类型对象如果有需要提前校验的由此特征衍生出规范即标准操作(读写前的判断)if(Objects.nonNull(pojo)){}易错对pojo中的某个属性做非空判断前做setter和getter前一定要做非null判断if(Objects.nonNull(pojo) pojo.getXxx() ! null)) {}从属性中取值可以用如下写法String fileValue obj null ? : obj.getFileValue();List/map自定义集合场景集合在使用前一定要提前初始化否则直接往里添加元素直接就会报空指针异常从已有方法中获取集合如果是标准方法包括mybatis中的方法中(redis方法待验证)如果集合为空那么返回的是一个空集合对象而非null对象如果是null对象那么放外层还得加一个集合非空的判断嫌啰嗦不加非空判断那么在对集合做遍历、取值的时候会容易出现空指针集合操作生成集合Array.asLsit(“xxx”)集合stream操作集合addAll操作集合循环如果集合为null时无论集合是操作树还是被操作数都会爆空指针场景对集合遍历或从集合中取值的时候注意点先判断非空再去判断集合中是否没有元素if(ocrmUserList ! null ocrmUserList.size() ! 0)官方写法public static boolean isEmpty(Collection coll) { return coll null || coll.isEmpty(); } 例如list集合的isEmpty方法就是判断list.size是否为0Arraysif (null ! arrs arrs.length 0) {}总结对任何对象(从request(前端、aop)中从缓存/数据库中拿)拿出来时、做读写getter/setter处理操作的时候都把它当成空的对待先做非空判断和空值处理不要把null作为方法参数至少传入一个空对象一个方法被调用十几次(复用性角度)其中只有一个参数为null极易出现空指针异常switch语句switch选择参数不能为null需要做非空判断除非特殊要求否则每个case执行完毕要及时break设置默认情况标准switch语句ApiConfigViewFuncEnumapiConfigViewFuncEnumApiConfigViewFuncEnum.get(type);if(nullapiConfigViewFuncEnum){returnnull;}switch(apiConfigViewFuncEnum){caseSUB_STRING:resultsubString(nodeModel,context);break;caseSPLIT:resultsplit(nodeModel,context);break;caseSORT:resultsort(nodeModel,context);break;caseJOIN:resultjoin(nodeModel,context);break;default:break;}mybatis应对复杂sql查询时if中的查询条件一定要明确不能模模糊糊的教训与标准mysql修改数据修改数据时一般都会先查再改如果某些公司测试环境和线上环境公用一套数据库那么测试的时候除非能确认只改一个例如对于mybatis-plus中的one筛选出唯一一个否则要先确认查询数据范围没问题再去做修改防止成批量的误改基本数据类型注意点用作属性最后不要直接使用基本数据类型作为属性假如使用了基本数据类型作为属性例如private int itemCount;那么如果是要做非null判断的if (xxx.getItemCount() ! null) {}就会出现报错所以尽量避免使用基本数据类型作为属性private Integer itemCount;判断是否值相等如果是Integer类型的属性Integer compareValue new Integer(111); if (xxx.getItemCount.equals(compareValue)) {}或者是第二种写法Integer compareValue new Integer(111); if (xxx.getItemCount compareValue.intValue()) {}return 提前终止方法功能使用注意点代码中凡是使用return提前返回的地方都有重点进行逻辑检查需要判断当时是否已经满足return使用条件尤其是在循环过程中更是要额外注意如果使用不当可能导致后面的循环无法执行