开发规范 - 空指针异常等低级问题注意点

影响范围

可能造成一整个接口瘫痪,如果是核心接口瘫痪那么后果不堪设想,涉及到数据记录的接口则会导致客户丢失异常时间范围内的数据且无法恢复(大数据得不到推送)

通用判断

这种写法有点就是可以直接拿int和Integer来做比较,否则就需要先把int转为Integer,但风险点就在于有可能直接拿数值和枚举去做比较了,开发和检查过程中药重点留意下这里

if (Objects.equals(task.getStatus(), MeetpointsLMAnalyseStatusEnum.PROCESSED.getCode()) {}

字符串

字符串转jsonObj

转之前一定要判断字符串是非空的,因为如果字符串为null或者"",那么parseObject就的结果就为null,后面就可能导致空指针异常,字符串非空,但如果是非json格式转换过程中就会报错,但这通常是调用上游获取的结果不规范导致的,但如果没有非空判断,那就是你的问题

if (StringUtils.isNotEmpty(resultVo.getExternal())) {
                JSONObject parseObject = JSONObject.parseObject(resultVo.getExternal());
                resultVo.setLocation(parseObject.getString("geometry"));
            }

空字符判断空

参考org.apache.commons.lang3.StringUtils

public static boolean isEmpty(final CharSequence cs) {
        return cs == null || 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) {
                    FbMpaSellerTokenEntity sellerToken = new FbMpaSellerTokenEntity();  
            }

对null操作注意点

对null强转不会空指针,强转后的结果依然是null

for循环/遍历注意点

集合为空不报错,集合为null会报错,对集合做循环前需要判断集合非空非null

循环前对集合做了非空非null判断并不意味着已经没有风险了,因为集合中如果存在为null的对象,我们从集合取出对象后对该对象做操作会被空指针异常。

集合操作注意点

stream相关

map

 List<DriverVisualPictureDTO> groupedDtos = new ArrayList<>();
 DriverVisualPictureDTO dto = new DriverVisualPictureDTO();
 DriverVisualPictureDTO dto2 = new DriverVisualPictureDTO();
 DriverVisualPictureDTO dto3 = new DriverVisualPictureDTO();
 dto3.setPicTime(1765798564600L);
 groupedDtos.add(dto);
 groupedDtos.add(dto2);
 groupedDtos.add(dto3);
 List<Long> timestampRange = groupedDtos.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异常,规范的写法应该如下

  List<Long> timestampRange = new ArrayList<>();
  // 不规范写法
  System.out.println(timestampRange.stream().findFirst().get());
  // 规范写法
  System.out.println(timestampRange.stream().findFirst().orElse(null));

从集合中取最大值,涉及到Optional(max和min返回的是个Optional),所以集合中要保证不能有null元素才行,否则会报错

 List<Long> timestampList = new ArrayList<>();
        timestampList.add(1765798564600L);
        timestampList.add(null);
        timestampList.add(null);
        // 过滤null值后集合不为空,才能做获取最大最小值处理
        List<Long> filterList = timestampList.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.CollectionUtils

public static boolean isEmpty(@Nullable Collection<?> collection) {
		return (collection == null || collection.isEmpty());
	}

其中最常用的集合HashMap和ArrayList均是

public boolean isEmpty() {
        return size == 0;
    }

所以,往集合放元素的时候,一定要注意不能放入值为null的元素

存入集合元素的时机

存入对象元素,数据源来源可能如下,如果把这些数据源存入集合前,一定要做非null判断

  • 从mysql,es数据库取数据
  • 从redis 缓存取数据
  • 从threadlocal,hashmap本地缓存取数据
  • 通过依赖注入注入属性(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-dd'T'HH: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是否为0

Arrays

if (null != arrs && arrs.length > 0) {}

总结

  • 对任何对象(从request(前端、aop)中,从缓存/数据库中拿)拿出来时、做读写getter/setter处理操作的时候,都把它当成空的对待,先做非空判断和空值处理
  • 不要把null作为方法参数,至少传入一个空对象,一个方法被调用十几次(复用性角度),其中只有一个参数为null,极易出现空指针异常

switch语句

  • switch选择参数不能为null,需要做非空判断
  • 除非特殊要求,否则每个case执行完毕要及时break
  • 设置默认情况

标准switch语句

  ApiConfigViewFuncEnum apiConfigViewFuncEnum = ApiConfigViewFuncEnum.get(type);
        if (null == apiConfigViewFuncEnum) {
            return null;
        }

        switch (apiConfigViewFuncEnum) {
            case SUB_STRING:
                result = subString(nodeModel, context);
                break;
            case SPLIT:
                result = split(nodeModel, context);
                break;
            case SORT:
                result = sort(nodeModel, context);
                break;
            case JOIN:
                result = join(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使用条件,尤其是在循环过程中,更是要额外注意,如果使用不当,可能导致后面的循环无法执行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值