Java中避免空指针异常的方法
没人会喜欢空指针异常!有什么方法可以避免它们吗?或许吧。。
本文将讨论到以下几种技术
1.Optional类型(Java8中新引入的)
2.Objects类(Java7中原有的)
Java8中的Optional类
它是什么?
1.Java8中新引入的类型
2.它是作为某个指定类型的对象的包装器或者用于那些不存在对象(null)的场景
简单来说,它是处理空值的一个更好的替代品(警告:乍一看可能并没有那么明显)
基本用法
它是一种类型(一个类)——那么,怎么才能创建一个这个类型的实例?
使用下它的三个静态方法就可以了:
publicstaticOptional<String>stringOptional(Stringinput){
returnOptional.of(input);
}
简单明了——创建一个包含这个值的Optional包装器。记住——如果这个值是null的话,它会抛出NPE!
publicstaticOptional<String>stringNullableOptional(Stringinput){
if(!newRandom().nextBoolean()){
input=null;
}
returnOptional.ofNullable(input);
}
我个人认为是要更好一点。这样就不会有NPE的风险了——如果输入为null的话,会返回一个空的Optional。
publicstaticOptional<String>emptyOptional(){
returnOptional.empty();
}
如果你真的就是希望返回一个”空"值的话。“空”值并不意味着null。
好吧,那如何去消费/使用Optional呢?
publicstaticvoidconsumingOptional(){
Optional<String>wrapped=Optional.of("aString");
if(wrapped.isPresent()){
System.out.println("Gotstring-"+wrapped.get());
}
else{
System.out.println("Gotcha!");
}
}
简单的方法就是检查Optional包装器是否真的有值(使用isPresent方法)——你会怀疑这和使用if(myObj!=null)相比有什么好处。别担心,这个我会解释清楚的。
publicstaticvoidconsumingNullableOptional(){
Stringinput=null;
if(newRandom().nextBoolean()){
input="iCanBeNull";
}
Optional<String>wrapped=Optional.ofNullable(input);
System.out.println(wrapped.orElse("default"));
}
你可以使用orElse方法,这样万一封装的确实是一个null值的话可以用它来返回一个默认值——它的好处显而易见。在提取出真实值的时候可以避免调用ifPresent方法这样明显多余的方式了。
publicstaticvoidconsumingEmptyOptional(){
Stringinput=null;
if(newRandom().nextBoolean()){
input="iCanBeNull";
}
Optional<String>wrapped=Optional.ofNullable(input);
System.out.println(wrapped.orElseGet(
()->{
return"defaultBySupplier";
}
));
}
这个我就有点搞不清楚了。为什么有两个同样目的的不同方法?orElse和orElseGet明明可以重载的(同名但不同参数)。
不论如何,这两个方法明显的区别就在于它们的参数——你可以选择使用lambda表达式而不是Supplier的实例来完成这个(一个函数式接口)
为什么使用Optional要比常见的null检查强?
1.使用Optional最大的好处就是可以更明白地表述你的意图——返回null值的话会让消费者感到疑惑(当真的出现NPE的时候)这是不是故意返回的,因此还得查看javadoc来进一步定位。而使用Optional就相当明了了。
2.有了Optional你就可以彻底避免NPE了——如上所提,使用Optional.ofNullable,orElse以及orElseGet可以让我们远离NPE。
另一个救星!
看下这个代码片段
packagecom.abhirockzz.wordpress.npesaviors;
importjava.util.Map;
importjava.util.Objects;
publicclassUsingObjects{
StringgetVal(Map<String,String>aMap,Stringkey){
returnaMap.containsKey(key)?aMap.get(key):null;
}
publicstaticvoidmain(String[]args){
UsingObjectsobj=newUsingObjects();
obj.getVal(null,"dummy");
}
}
哪个可能会为空?
1.Map对象
2.进行搜索使用的key
3.方法调用的这个实例
如果抛出NPE的话,我们怎么能确定到底是哪个是null的?
packagecom.abhirockzz.wordpress.npesaviors;
importjava.util.Map;
importjava.util.Objects;
publicclassUsingObjects{
StringgetValSafe(Map<String,String>aMap,Stringkey){
Map<String,String>safeMap=Objects.requireNonNull(aMap,
"Mapisnull");
StringsafeKey=Objects.requireNonNull(key,"Keyisnull");
returnsafeMap.containsKey(safeKey)?safeMap.get(safeKey):null;
}
publicstaticvoidmain(String[]args){
UsingObjectsobj=newUsingObjects();
obj.getValSafe(null,"dummy");
}
}
requireNonNull方法
1.如果对象不为null的话就返回它本身
2.如果值为null的话,返回的NPE会带有指定的消息
为什么比if(myObj!=null)要好?
你所看到的栈跟踪信息会很清楚地看见Objects.requireNonNull的方法调用。这个再配合你自己的错误日志,可以让你更快地定位问题。。。至少在我看来是更快。
你还可以自己自义校验器,比如说实现一个简单的校验器来确保没有空值。
importjava.util.Collections;
importjava.util.List;
importjava.util.Objects;
importjava.util.function.Predicate;
publicclassRandomGist{
publicstatic<T>TrequireNonEmpty(Tobject,Predicate<T>predicate,StringmsgToCaller){
Objects.requireNonNull(object);
Objects.requireNonNull(predicate);
if(predicate.test(object)){
thrownewIllegalArgumentException(msgToCaller);
}
returnobject;
}
publicstaticvoidmain(String[]args){
//Usage1:anemptystring(intentional)
Strings="";
System.out.println(requireNonEmpty(Objects.requireNonNull(s),(s1)->s1.isEmpty(),"MyStringisEmpty!"));
//Usage2:anemptyList(intentional)
Listlist= Collections.emptyList();
System.out.println(requireNonEmpty(Objects.requireNonNull(list),(l)->l.isEmpty(),"ListisEmpty!").size());
//Usage3:anemptyUser(intentional)
Useruser=newUser("");
System.out.println(requireNonEmpty(Objects.requireNonNull(user),(u)->u.getName().isEmpty(),"UserisEmpty!"));
}
privatestaticclassUser{
privateStringname;
publicUser(Stringname){
this.name=name;
}
publicStringgetName(){
returnname;
}
}
}
不要让NPE在错误的地方成为痛苦。我们有许多工具能更好地处理NPE,甚至彻底地根除它们!