Java随谈(二)对空指针异常的碎碎念
本文适合对 Java 空指针痛彻心扉的人阅读,推荐阅读时间25分钟。
若有一些Java8 函数式编程的基础可以当成对基础知识的巩固。
一、万恶的null
今天,我们简单谈谈null的问题。因为null是无的意思,引用null值会让计算机无法处理。在Java语言中对null值的引用也是如此——会导致NPE。
二、null引起的空指针异常
NullPointerException(NPE)是在Java里普遍存在的异常,通常通过下面两种方式引起。
- 调用null对象的实例方法
- 访问或修改null对象的字段
那么,如何处理空指针异常异常呢?
三、程序员的应对方式
(1)最初,一些程序员通过自己的编程经验,在某些出现过NPE的地方进行参数校验——null值判断(方式2),使用方式一 常量.equals(入参) 省略掉null值判断,或者给入参添加默认值。
//方式1
public String fooOne(String bar) {
if ("dev".equals(bar) {
//正确处理
}
else {
//错误处理
}
}
//方式2
public String fooTwo(String bar) {
if (null != bar && bar.equals("dev")) {
//正确处理
}
else {
//错误处理
}
} //方式3 添加默认值
public String fooThree(String bar) {
if (null == bar) {
bar = "default value"
}
//正确处理
}
(2)经过越来越多的代码的编写,有经验的程序员逐步开发了工具包(util),包括很多项目都通用的代码,其中也有null判断的方法。这些工具包中最有名的莫过于 Apache 的commons包和 Google Guava 包
Apache的公共包isEmpty()方法
org.apache.commons.lang.StringUtils类中
public static boolean isEmpty(final CharSequence cs) {
return cs == null || cs.length() == 0;
}
代码示例
if (StringUtils.isEmpty(foo)) {
//错误处理
}
//正确处理
或者采用 Google 的 Guava 的快速失败(fast-fail)机制
public static <T> T requireNonNull(T obj) {
//判断参数为null 则抛出异常信息
if (obj == null)
throw new NullPointerException();
//参数不为null,则返回参数本身
return obj;
}
四、Java注解
在Java引入了注解机制之后,程序员可以通过语法层面控制入参的空指针异常。
从语法方面控制可以让用户编写代码时,可以将注意力尽可能放于自己的业务代码中,而不是语言的语法。
用于对象的校验常用的是javax.validation包,包含了校验空值,null值,长度等等。
javax.validation包简介
@Data
public class Bar {
@NotNull
private String name;
} //另一个类的某个方法
public foo(@Valid Bar bar) {
//直接处理,无需写校验name属性的代码
}
当执行foo(bar) 方法时,会先校验bar.name 是否为空, 若name为空,则会抛出org.springframework.web.bind.MethodArgumentNotValidException异常(例子是spring项目,不同项目可能导致异常不一致),并在日志中会有以人类可读的日志提示。
五、Java Objects类
而Java7引入Java.util.Objects类,相当于大一统了第三方库的一些常规处理(可以类比为游戏的mod由于过于优秀被游戏转“正”了)
比如快速失败机制
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
public static <T> T requireNonNull(T obj, Supplier<String> messageSupplier) {
if (obj == null)
throw new NullPointerException(messageSupplier.get());
return obj;
}
校验null
public static boolean isNull(Object obj) {
return obj == null;
}
public static boolean nonNull(Object obj) {
return obj != null;
}
无需校验null的equal
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
有了这个类,就无需引用各种第三方的校验包了。
六、Java Optional类
这是在Java8加入的包装类,可以有效地处理null(通过Optional.empty包装类来替代null,避免空指针异常)
首先先贴一下Optional的代码
package java.util; import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier; public final class Optional<T> { private static final Optional<?> EMPTY = new Optional<>(); private final T value; private Optional() {
this.value = null;
} public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
} private Optional(T value) {
this.value = Objects.requireNonNull(value);
} public static <T> Optional<T> of(T value) {
return new Optional<>(value);
} public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
} public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
} public boolean isPresent() {
return value != null;
} public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
} public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
} public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
} public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
} public T orElse(T other) {
return value != null ? value : other;
} public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
} public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
} @Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} if (!(obj instanceof Optional)) {
return false;
} Optional<?> other = (Optional<?>) obj;
return Objects.equals(value, other.value);
} @Override
public int hashCode() {
return Objects.hashCode(value);
} @Override
public String toString() {
return value != null
? String.format("Optional[%s]", value)
: "Optional.empty";
}
}
在 Optional 类中可以看到很多方法,初学者可能会对其中的繁多的代码有些发憷。我们把这个类分为创建,操作,返回和其他四个模块来简单介绍
(一)创建Optional
- empty(): 创建一个空的Optional
- of(value): 将value包装进Optional
- ofNullable(value): 将value包装进Optional,若为空时自动生成Optional.empty
(二)操作Optional
- filter(Predicate): 对Optional中的内容应用于Predicate并返回,若不满足则返回Optional.empty
- map(Function): 若Optional不为空,应用Function与Optional中的内容,并返回对象,否则,返回Optional.empty
- flatMap(Function): 若Optional不为空,应用Function与Optional中的内容,否则,返回Optional.empty
(三)返回
orElse(value):如果值存在则直接返回,否则返回 value。orElseGet(Supplier):如果值存在则直接返回,否则使用 Supplier 函数返回一个可替代对象。orElseThrow(Supplier):如果值存在直接返回,否则使用 Supplier 函数返回一个异常。
(四)其他
ifPresent(Consumer):当值存在时调用 Consumer。
注:(Predicate、Function、Supplier和Consumer是函数式接口,和Java8的流式可以一起使用,在今后的Java随谈 stream中会仔细描述)
//Function: 传入一个T类型返回一个R类型
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
} //Consumer: 传入一个T,不返回
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
} //Predicate: 传入一个T类型,返回布尔值
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
} //Supplier: 执行,返回T类型
@FunctionalInterface
public interface Supplier<T> {
T get();
}
下面是代码示例
可以看到,使用了Optional类并配合stream,就可以灵活地获取参数的值。
public class Test {
public static void main(String[] args) throws NullPointerException {
//假设param为函数的入参
String param = null;
//直接报 java.lang.NullPointerException
// String valueOne = Optional.of(param).get();
// System.out.println(valueOne);
//若 param 为 null, 将 default value 作为 valueTwo 的值
String valueTwo = Optional.ofNullable(param).orElse("default value");
System.out.println(valueTwo);
//若 param 为 null, 则执行方法并将返回值作为 valueThree 的值
String valueThree = Optional.ofNullable(param).orElseGet(() -> {
System.out.println("执行方法获取默认值");
return "default value";
});
System.out.println(valueThree);
//若 param 为 null,则抛出异常
String valueFour = Optional.ofNullable(param).orElseThrow(() -> new RuntimeException("抛出异常"));
System.out.println(valueFour);
}
}
//以下是输出
default value
执行方法获取默认值
default value
Exception in thread "main" java.lang.RuntimeException: 抛出异常
at com.example.demo.util.annotation.Test.lambda$main$1(Test.java:26)
at java.util.Optional.orElseThrow(Optional.java:290)
at com.example.util.Test.main(Test.java:26)
Process finished with exit code 1
最佳实践
1.首先需要判断null首先使用官方库(官方库是每一个Java程序员都必须了解的,降低了沟通、阅读代码的门槛,而且具有持续更新,持续优化的好处)
2.对于spring web项目的 http请求最好先使用注解进行最初的参数校验。
3.在和stream的操作中,若可能涉及null值处理,最好使用Optional.ofNullable(),保证代码风格一致。
Java随谈(二)对空指针异常的碎碎念的更多相关文章
- 在Java中谈尾递归--尾递归和垃圾回收的比较(转载)
我不是故意在JAVA中谈尾递归的,因为在JAVA中谈尾递归真的是要绕好几个弯,只是我确实只有JAVA学得比较好,虽然确实C是在学校学过还考了90+,真学得没自学的JAVA好 不过也是因为要绕几个弯,所 ...
- 【转】java提高篇(二)-----理解java的三大特性之继承
[转]java提高篇(二)-----理解java的三大特性之继承 原文地址:http://www.cnblogs.com/chenssy/p/3354884.html 在<Think in ja ...
- 在Java中谈尾递归--尾递归和垃圾回收的比较
我不是故意在JAVA中谈尾递归的,因为在JAVA中谈尾递归真的是要绕好几个弯,只是我确实只有JAVA学得比较好,虽然确实C是在学校学过还考了90+,真学得没自学的JAVA好 不过也是因为要绕几个弯,所 ...
- Java连载35-类总结、空指针异常
一.总结 1.栈内存中主要存储的是方法体中的局部变量 2.对象内部有实例变量,实例变量存储在堆内存中 3.变量分类:局部变量(方法体中声明):成员变量(方法体外声明) 实例变量(前边修饰符没有stat ...
- 实战Java虚拟机之二“虚拟机的工作模式”
今天开始实战Java虚拟机之二:“虚拟机的工作模式”. 总计有5个系列 实战Java虚拟机之一“堆溢出处理” 实战Java虚拟机之二“虚拟机的工作模式” 实战Java虚拟机之三“G1的新生代GC” 实 ...
- Java设计模式(二) 工厂方法模式
本文介绍了工厂方法模式的概念,优缺点,实现方式,UML类图,并介绍了工厂方法(未)遵循的OOP原则 原创文章.同步自作者个人博客 http://www.jasongj.com/design_patte ...
- Java 验证码、二维码
Java 验证码.二维码 资源 需要: jelly-core-1.7.0.GA.jar网站: http://lychie.github.io/products.html将下载下来的 jelly ...
- Java入门(二)——果然断更的都是要受惩罚的。。。
断更了一个多月,阅读量立马从100+跌落至10-,虽说不是很看重这个,毕竟只是当这个是自己的学习笔记,但有人看,有人评论,有人认同和批评的感觉还是很巴适的,尤其以前有过却又被剥夺的,惨兮兮的. 好好写 ...
- Java实验报告二:Java面向对象程序设计
Java实验报告二:Java面向对象程序设计 ...
随机推荐
- Golang中使用set
两种 go 实现 set 的思路, 分别是 map 和 bitset. map 的 key 肯定是唯一的,而这恰好与 set 的特性一致,天然保证 set 中成员的唯一性.而且通过 map 实现 se ...
- 操作系统-I/O(3)SPOOLing技术
为了缓和CPU的高速性与I/O设备低速性间的矛盾而引入了脱机输入.输出技术.该技术是利用专门的外围控制机,将低速I/O设备上的数据传送到高速磁盘上:或者相反. 事实上,当系统中引入了多道程序技术后,完 ...
- Vector-based navigation using grid-like representations in artificial agents
郑重声明:原文参见标题,如有侵权,请联系作者,将会撤销发布! Nature May 17, 2018 Received: 5 July 2017; Accepted: 3 April 2018;Pub ...
- 从Vessel到二代裸金属容器,云原生的新一波技术浪潮涌向何处?
摘要:云原生大势,深度解读华为云四大容器解决方案如何加速技术产业融合. 云原生,可能是这两年云服务领域最火的词. 相较于传统的应用架构,云原生构建应用简便快捷,部署应用轻松自如.运行应用按需伸缩,是企 ...
- 6套MSP430开发板资料共享 | 免费下载
目录 1-MSP430 开发板I 2-MSP430mini板资料 3-MSP430F149开发板资料 4-KB-1B光盘资料 5-LT-1B型MSP430学习板光盘 6-MSP-EXP430F552 ...
- Hello-Annie
Annie theme Annie是一个简单的Hexo博客主题,如果你喜欢散文.诗歌.小说......那么它可能合你心意! ☞预览-1,☞预览-2 Features 文艺.优雅.简洁的博客主题 页头随 ...
- C++字符串与指针
字符串初始化 在C++中基本数据类型并不包括string,string类型其实是一种类类型,通过STL函数库中的模板类basic_string 实例化得到. int main () { // stri ...
- MySQL主从同步简单介绍&配置
介绍: 现在mysql集群基本上都是使用一主多从方式,实现读写分离(主库写.从库读).负载均衡.数据备份,以前只是使用从未配置过,今天简单配置一下! mysql主从复制是通过binary log日志实 ...
- 洛谷P3817 小A的糖果 贪心思想
一直觉得洛谷的背景故事很....直接题解吧 #include <bits/stdc++.h> //万能头文件 using namespace std; int a[100002]; // ...
- 《神经网络的梯度推导与代码验证》之FNN(DNN)的前向传播和反向推导
在<神经网络的梯度推导与代码验证>之数学基础篇:矩阵微分与求导中,我们总结了一些用于推导神经网络反向梯度求导的重要的数学技巧.此外,通过一个简单的demo,我们初步了解了使用矩阵求导来批量 ...