=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-
https://stackoverflow.com/questions/20694915/apachi-poi-cell-setcellvalue-thows-nullpointerexception
=END=
《 “Java中一些 NPE 报错和定位办法整理” 》 有 2 条评论
万恶的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。
* 提供接口时候需要对非空参数进行说明,并且对非空参数进行校验,不要太相信调用者。
* 调用接口的时候一定要对接口返回值进行判空,不要太相信接口提供者。(这个肯定会有值的)。
* 小心使得万年船
`
我有一个朋友写出了17种触发NPE的代码!避免这些坑
https://mp.weixin.qq.com/s/VGH0pDbsgP5OuX8x7HyuKg
`
虽然无法统计出Java程序员写的最多的异常是哪一个,NullPointerException(NPE)它一定会榜上有名。无论是初次涉足编程世界的新手,还是在代码海洋中久经风浪的老鸟,几乎都写过NPE异常。要防范它,不在高超的编码技巧,在细。
我有一个朋友,写代码的时候常常遭到NPE背刺,痛定思痛,总结了NPE出没的17个场景,哪一个你还没有遇到过?
1.访问空对象的实例变量或者调用空对象的实例方法
从其它方法(如远程hsf方法或者从DB)返回的结果,不做控制判断,直接访问变量或者方法,可能会NPE。
2.访问或修改空数组的元素
3.未初始化的对象数组中的元素默认是null
4.throw一个null 出去
在Java中,通常不会故意使用 throw null 这种表达,因为它实际上没有任何有用的用途。根据Java语言规范,throw 关键字后面应该跟随一个可抛出的对象(Throwable 类或其子类的实例),而 null 并不是一个可抛出的对象。如果你执行 throw null ,将会得到一个NPE。
猜想可能出现的场景:
代码错误:throw null 可能是代码编写错误或者不完整的异常处理。例如,可能打算抛出一个实际的异常对象,但误写成了 null。
测试代码:在单元测试中,有时可能会故意使用throw null , 来确保他们的异常处理代码能够妥善处理意外情况。(不推荐)
5.在null 引用上进行synchronized同步
6.在自动拆箱过程中遇到null
自动装箱不会遇到Null的问题,因为这个过程是把基本类型的值转换成包装类对象,基本类型的值是没有Null的。
定义方法返回一个int 作为出参,但实际return 一个null,也会NPE。
7.从集合/Map中获取null元素并直接使用
从集合/map中获取元素并使用时,建议对Null进行检查,以避免潜在的NPE,特别是在那些隐式触发自动拆箱的场景中。
8.方法链调用中上一步骤返回null
9.枚举的valueOf方法使用null
10.集合操作不支持null元素
HashSet 、LinkedHashSet 都只允许添加一个null。后续无论添加多少null元素,都会被忽视。
TreeSet 不允许添加null值,排序集合依赖元素直接的比较操作,而null元素不能与其它对象进行比较,会抛出NPE;
11.多线程环境中无适当同步可能导致不一致状态
12.依赖注入:注入的对象为null
required属性为false,启动过程中如果没有找到合适的bean,service 会被设置为null。在调用service的任何方法之前都需要判断service是否为null。
13.Lambda表达式或方法引用中目标引用为null
14.Stream API处理时遇到null元素
15.使用增强for循环遍历集合时,没有判空
16.在Junit中使用Mockito时,错误地使用any()匹配基本数据类型参数
在JUnit4中,使用Mockito框架时,any() 是一个参数匹配器,当与基本数据类型一起使用时,需要使用相应的类型特定的匹配器,例如使用anyInt() 而不是any()。因为any()实际上返回的是null,而null不能自动转换为基本数据类型。
17.Optional类的正确使用
Optional类在 Java 8 中被引入,其设计初衷是为了提供一种更优雅的方式来处理可能为Null的值,从而减少空指针异常NPE的发生。
但Optional类设计为减少NPE的可能性,却并不是万能的,比如开发者在使用Optional,不检查是否存在,直接调用Optional.get(),那么会得到一个NoSuchElementException。也仍然存在即使使用了Optional,也可能出现NPE的情况。
`