Java中一些 NPE 报错和定位办法整理

=Start=

缘由:

已经有很长一段时间都没有写Java代码了,最近因为工作需要又要再用Java实现一些功能,因为Java基础的不够扎实以及生产环境的复杂多变导致本地运行测试OK的代码到了线上之后就会遇到一些非预期的报错,最常见的就是NullPointerException空指针异常了。因此这里及时整理总结一下近期遇到的NPE异常及其解决办法,方便后面有需要的时候参考。

正文:

参考解答:

Java中的NPE问题对于我这种Java新手来说真的是太常见了……很烦,不过有一点好就在于报错的行定位非常精准,问题肯定就出在那个地方了,只是根因不一定在那(有可能是之前某个地方处理不妥善最后流转到这里来了),这时需要一层一层的往上定位了。

整理总结一下最近经常遇到的几个 NPE 的点:

1、json中给一个key的value赋值为null或空串其效果是不一样的

  • 在 fastjson 中当一个key的value内容是null时,最后其实是没有这个 key 的,这点需要注意一下,特别是在后期有处理这个json对象的操作时;
  • 在 fastjson 中当一个key的value内容是””时(空字符串),后续处理的时候会存在这个 key ,且内容为””,符合预期。

2、从json中(链式)取不存在的 key ;

首先,要处理的字符串必须符合json的格式要求,否则会报 JSONException 异常,这个比较简单,但也需要确认一下;
其次,如果一层一层处理的话,如果哪一层没有对应的 key 则在那一层返回的就是 null ,所以下层如果继续处理的话就会报 NullPointerException 异常;
最后,如果希望能正确解析json字符串,需要对json字符串的格式有充分了解(有哪些字段,字段对应的是什么类型),符合预期就正确处理,不符合预期抛出异常或是报错即可。

以下是一个测试用的代码片段,方便进行本地验证:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import java.io.IOException;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

public class DoTest {
    public static void main(String[] args) {
        String input = "{\"msg\":\"success\",\"code\":\"200\",\"data\":\"{\\\"score\\\":3,\\\"key1\\\":3,\\\"key2\\\":2,\\\"tags\\\":\\\"a,b,c\\\"}\"}";
        input = "{\"msg\":\"success\",\"code\":\"200\",\"data\":\"{\\\"score\\\":0}\"}"; // 0 null
        input = "{\"msg\":\"success\",\"code\":\"200\",\"data\":\"{\\\"key1\\\":0}\"}"; // null null
        input = "{\"msg\":\"success\",\"code\":\"200\",\"data\":\"{}\"}"; // null null
        // input = "{\"msg\":\"success\",\"code\":\"200\",\"data\":\"\"}"; // NullPointerException
        // input = "{\"msg\":\"success\",\"code\":\"200\",\"data\":\"\""; // JSONException: syntax error, position at 0, name data
        // input = "{\"msg\":\"success\",\"code\":\"200\"}"; // NullPointerException
        JSONObject inputJson = JSON.parseObject(input);
        System.out.println(inputJson);
        Integer score = inputJson.getJSONObject("data").getInteger("score");
        String tagStr = inputJson.getJSONObject("data").getString("tags");
        System.out.println(score + "\t" + tagStr);

        Set<String> tags = new HashSet<>();
        // tags.add("111");
        // tags.add("112");
        System.out.println(tags.toString());

        JSONObject result = new JSONObject();
        result.put("1001", 5);
        System.out.println(result);
        System.out.println(result.getInteger("1001"));

        Integer int1001 = result.getInteger("1001");
        int sum = 0;
        if (sum > 0 || (int1001 != null && int1001 > 0)) {
            sum += int1001;
            result.put("score", sum>100?100:sum);
        }
        System.out.println(result);

        // result.remove("1001");
        System.out.println(result);

        Integer ret = 0;
        ret += result.getInteger("1001");
        System.out.println(ret);

        Properties prop = new Properties();
        try {
            //load a properties file from class path, inside static method
            prop.load(DoTest.class.getClassLoader().getResourceAsStream("config.properties"));

            //get the property value and print it out
            System.out.println(prop.getProperty("json").length());
        }
        catch (IOException ex) {
            ex.printStackTrace();
        }

    }
}

3、在不同类型的变量之间进行操作;

以 int 和 Integer 为例:
int 是基本类型,不是类,为了符合面向对象编程,后来出现了Integer类,它是对int进行封装的。
int 不是对象,是java原始的数据类型,它默认值为0
Integer 是对象,它是对int进行的封装,它默认值为NULL
建议在用 Integer 时,注意检查其值是否为 null ,避免 NPE 报错。

Integer sum = new Integer(0);

4、json取值中 getInteger 和 getIntValue 两者之间的区别和试用场景;

一般情况下(只需要取key对应整数的数值大小时),我个人还是建议用 getIntValue 因为它返回的是 int ,没取到返回 0 ;如果对于内容可能为 0 的那些场景,可以用 getInteger 再加上 null 的判断;或者统一都用getInteger+null判断。

JSONObject jsonObject = new JSONObject();
String data = null; // {}
jsonObject.put("data", data);

System.out.println(jsonObject.getInteger("key1")); // null
System.out.println(jsonObject.getIntValue("key1")); // 0
System.out.println(jsonObject); // {}

data = ""; // {"data":""}
jsonObject.put("data", data);
System.out.println(jsonObject); // {"data":""}

jsonObject.put("key1", 1);
System.out.println(jsonObject.getInteger("key1")); // 1
System.out.println(jsonObject.getIntValue("key1")); // 1
System.out.println(jsonObject); // {"key1":1,"data":""}

5、Apache POI中的 setCellValue 操作;

用Apache POI操作时,createCell 创建最多报 IllegalArgumentException 异常,不会有 NPE 异常,那个只会出现在 setCellValue 设置单元格内容时出现,原因就是设置的值类型为 null ,而且简单测可能还不容易发现,比如:

import com.alibaba.fastjson.JSONObject;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.FileOutputStream;
import java.io.IOException;

public class WriteExcel {
    public static void main(String[] args) {
        XSSFWorkbook workbook = new XSSFWorkbook();
        XSSFSheet sheet = workbook.createSheet("ixyzero");

        int rowCount = 0;
        Row row = sheet.createRow(++rowCount);

        JSONObject jsonObject = new JSONObject();

        int columnCount = 0;
        Cell cell = row.createCell(columnCount++);
        System.out.println(jsonObject.getIntValue("key1")); // 0
        cell.setCellValue(jsonObject.getIntValue("key1")); // 正常,因为即便取不到key1的value会默认返回0

        String data = null;
        cell = row.createCell(columnCount++);
        cell.setCellValue(data); // 它说他对null做了容错处理,不会报 NPE 异常

        cell = row.createCell(columnCount++);
        System.out.println(jsonObject.getInteger("key1")); // null
        cell.setCellValue(jsonObject.getInteger("key1")); // NullPointerException

        try (FileOutputStream outputStream = new FileOutputStream("ixyzero.xlsx")) {
            workbook.write(outputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

参考链接:

java中因为 int 和 Integer 不同导致的 NPE 问题
https://stackoverflow.com/questions/218384/what-is-a-nullpointerexception-and-how-do-i-fix-it
https://blog.csdn.net/weixin_41575100/article/details/78953079

https://stackoverflow.com/questions/17487205/how-to-check-if-a-json-key-exists

https://stackoverflow.com/questions/14283684/null-check-for-hashmap-key

http://poi.apache.org/apidocs/dev/org/apache/poi/ss/usermodel/Row.html#createCell-int-

http://apache-poi.1045710.n5.nabble.com/Bug-59199-New-setting-null-value-to-date-cell-leads-to-NPE-td5722365.html

https://www.experts-exchange.com/questions/23180270/How-to-avoid-NullPointerException-in-excel-writing.html

https://stackoverflow.com/questions/20694915/apachi-poi-cell-setcellvalue-thows-nullpointerexception

=END=

声明: 除非注明,ixyzero.com文章均为原创,转载请以链接形式标明本文地址,谢谢!
https://ixyzero.com/blog/archives/4882.html

《Java中一些 NPE 报错和定位办法整理》上的一个想法

  1. 万恶的NPE差点让我半个月工资没了
    https://mp.weixin.qq.com/s/xoOalilXsmeOcvpjf24Z9g
    `
    最近看到《阿里巴巴Java开发手册》第11条规范写到:
    防止 NPE ,是程序员的基本修养

    NPE(Null Pointer Exception)一直是开发中最头疼的问题,也是最容易忽视的地方。记得刚开始工作的时候所在的项目组线上出现最多的bug不是逻辑业务bug而是NPE,所以后面项目组出了一个奇葩的规矩,线上如果谁出现一个NPE的问题就罚款100元,用作团建费用。如果项目组每个人一个月都出现个两三个NPE的话。那么项目组是不是每个月都可以去团建下(自己掏钱海吃海喝,心不心疼)。不过自从这个规矩实施以来,线上的NPE就渐渐的少了,从最初的一个月团建一次到最后的半年团建一次。大家写代码都比较谨慎了,只要用到对象或者集合的时候二话不说上来先判空,所以产生的NPE就少了。

    # 那么如何有效的避免NPE呢?
    * 使用对象或者集合之前记得先判空。
    * 使用JDK一些API的方法记得要点进源码去大概看看,不要随便拿来就用。
    * 单元测试要对空值进行测试,保证程序的健壮性。
    * 合理的使用JDK1.8提供的Optional来避免NPE。
    * 提供接口时候需要对非空参数进行说明,并且对非空参数进行校验,不要太相信调用者。
    * 调用接口的时候一定要对接口返回值进行判空,不要太相信接口提供者。(这个肯定会有值的)。
    * 小心使得万年船
    `

发表评论

电子邮件地址不会被公开。 必填项已用*标注