当前位置: 主页 > JAVA语言

java空指针异常处理-空指针异常怎么解决

发布时间:2023-02-09 07:11   浏览次数:次   作者:佚名

Java界流传着这样一个传说:只有真正理解空指针异常,你才算是一个合格的Java开发者。 在我们浮华的java代码字符生涯中,每天都会遇到各种各样的null处理。 每天可能会重复写类似下面的代码:

if(null != obj1){
  if(null != obje2){
     // do something
  }
}

登录复制

如果你有点javaer的眼光,可以做点比较有说服力的,想办法判断null

boolean checkNotNull(Object obj){
  return null == obj ? false : true; 
}
void do(){
  if(checkNotNull(obj1)){
     if(checkNotNull(obj2)){
        //do something
     }
  }
}

登录复制

那么java空指针异常处理,问题又来了:如果null表示空串,那么""是什么意思呢?

那么惯性思维告诉我们,""和null不都是空串码吗? 简单升级判断空值:

boolean checkNotBlank(Object obj){  return null != obj && !"".equals(obj) ? true : false; 
}void do(){  if(checkNotBlank(obj1)){     if(checkNotNull(obj2)){        //do something
     }
  }
}

登录复制

有时间的话可以看看现在的项目或者自己过去的代码,写了多少和上面类似的代码。

不知道大家有没有认真想过一个问题:null是什么意思?

简单理解——null当然就是“该值不存在”的意思。

内存管理的一点心得——null表示内存还没有分配,指针指向空地址。

稍微透彻一点的理解——null可能表示某处处理有问题,也可能表示某个值不存在。

被虐过几千次的意识——哎哟,又是一个NullPointerException,看来我还得加一个if(null != value)。

回想一下,在我们之前的编码生涯中,我们遇到过多少次 java.lang.NullPointerException? NullPointerException 作为RuntimeException 级别的异常,不需要显示和捕获。 如果处理不当,我们会经常在生产日志中看到NullPointerException导致的各种异常堆栈输出。 并且根据这个异常堆栈信息,我们根本无法定位到问题的原因,因为并不是NullPointerException抛出的地方导致了问题。 我们要更深入的去查看这个null是从哪里产生的,而这个时候日志往往是无法追踪到的。

报空指针异常_空指针异常怎么解决_java空指针异常处理

有时候更悲剧的是,产生空值的地方往往不在我们自己的项目代码中。 这是一个更尴尬的事实——当我们调用各种好的和坏的第三方接口时,不清楚某个接口会在某种巧合下返回null……

回到之前null的认知问题。 许多 javaer 认为 null 意味着“无”或“值不存在”。 按照这种惯性思维,我们的代码逻辑是:你调用我的接口,根据你给我的参数返回对应的“值”。 如果在这种情况下找不到对应的“值”,那我当然会返回一个null给你。 不再有“任何东西”。 让我们看一下下面的代码,它是用非常传统和标准的 Java 编码风格编写的:

class MyEntity{   int id;
   String name;   String getName(){      return name;
   }
}// mainpublic class Test{  
 public static void main(String[] args) 
       final MyEntity myEntity = getMyEntity(false);
       System.out.println(myEntity.getName());
   }   private getMyEntity(boolean isSuc){       
if(isSuc){        
   return new MyEntity();
       }else{       
       return null;
       }
   }
}

登录复制

这段代码很简单,日常的业务代码肯定比这复杂,但其实我们很多Java编码都是按照这个套路写的,懂货的人一看就知道是一个最后肯定会抛出NullPointerException。 但是我们在写业务代码的时候,很少会想到去处理这个可能的null(可能API文档已经写的很清楚了在某些情况下会返回null,但是你确定在开始写代码之前仔细阅读了API文档? ), 直到我们进行到某个测试阶段,突然跳出一个NullPointerException,我们才意识到,我们必须要像下面这样添加一个判断来去掉可能返回的null值。

// mainpublic class Test{   public static void main(String[] args) 
       final MyEntity myEntity = getMyEntity(false);       if(null != myEntity){
           System.out.println(myEntity.getName());
       }else{
           System.out.println("ERROR");
       }
   }
}

登录复制

仔细想想这几年,我们都这样做过吗? 如果说一些null导致的问题直到测试阶段才发现,那么问题来了——那些优雅复杂的业务代码中,有多少null没有被正确处理?

对待null的态度,往往能体现一个项目的成熟和严谨。 比如Guava早在JDK1.6之前就提供了优雅的null处理方法,可见其底蕴之深。

幽灵般的 null 阻碍了我们的进步

如果你是一个专注于传统面向对象开发的Javaer,或许你已经习惯了null带来的问题。 但是早在很多年前,大神就说过null是个坑。

Tony Hall(你不知道这个人是谁吗?自己去找吧)曾经说过:“我称之为我的十亿美元错误。这是 1965 年空引用的发明。我无法抗拒诱惑放入空引用,仅仅是因为它很容易实现。” Jean 意识到,无法抗拒发明空指针的诱惑。”)。

那么,我们看看null会引入哪些问题。

报空指针异常_空指针异常怎么解决_java空指针异常处理

看看这段代码:

String address = person.getCountry().getProvince().getCity();

登录复制

如果你玩过一些函数式语言(Haskell、Erlang、Clojure、Scala等),以上是一种很自然的写法。 当然,上面的写法也可以用Java来实现。

但是为了完美的处理所有可能的空异常,我们不得不把这个优雅的函数式编程范式改成这样:

if (person != null) {
	Country country = person.getCountry();
	if (country != null) {
		Province province = country.getProvince();
		if (province != null) {
			address = province.getCity();
		}
	}
}

登录复制

一瞬间,优质的函数式编程Java8又回到了10年前。 这样层层嵌套的判断,增加了代码量,不够优雅,还是小事。 更可能的情况是,大多数时候,人们会忘记判断可能出现的null,即使是写了多年代码的老人也不例外。

上段中的嵌套null处理也是传统Java长期被诟病的地方。 如果你使用早期版本的Java作为你的启蒙语言,这个get->if null->return的臭味会影响你很长一段时间(记得在国外的社区,这叫做:面向实体开发)。

使用Optional实现Java函数式编程

好吧,各种纠结都说了,然后我们就可以进入新时代了。

早在 Java SE 8 发布之前,其他类似的函数式开发语言就有了自己的各种解决方案。 这是 Groovy 代码:

String version = computer?.getSoundcard()?.getUSB()?.getVersion():"unkonwn";

登录复制

Haskell 使用 Maybe 类型类标志处理空值。 号称多范式开发语言的Scala提供了一个类似于Maybe的Option[T],用来包装和处理null。

Java8引入java.util.Optional来处理函数式编程的null问题。 Optional的处理思路和Haskell、Scala类似,但也有一些区别。 看看下面的 Java 代码示例:

public class Test {
	public static void main(String[] args) {
		final String text = "Hallo world!";
		Optional.ofNullable(text)//显示创建一个Optional壳
		    .map(Test::print)
			.map(Test::print)
			.ifPresent(System.out::println);
		Optional.ofNullable(text)
			.map(s ->{ 

空指针异常怎么解决_报空指针异常_java空指针异常处理

System.out.println(s); return s.substring(6); }) .map(s -> null)//返回 null .ifPresent(System.out::println); } // 打印并截取str[5]之后的字符串 private static String print(String str) { System.out.println(str); return str.substring(6); } } //Consol 输出 //num1:Hallo world! //num2:world! //num3: //num4:Hallo world!

登录复制

(你可以复制上面的代码在你的IDE中运行,前提是必须安装JDK8。)

上面代码中创建了两个Optional,实现的功能基本相同。 它们都使用 Optional 作为 String 的外壳来截断 String。 当在处理过程中遇到空值时,不再进行进一步的处理。 我们可以发现,在第二个Optional中出现s->null之后,后面的ifPresent就不再执行了。

注意输出//num3:,意思是输出一个“”字符,而不是null。

Optional 提供了丰富的接口来处理各种情况,例如,代码可以修改如下:

public class Test {
	public static void main(String[] args) {
		final String text = "Hallo World!";
		System.out.println(lowerCase(text));//方法一
		lowerCase(null, System.out::println);//方法二
	}
	private static String lowerCase(String str) {
		return Optional.ofNullable(str).map(s -> s.toLowerCase()).map(s->s.replace("world", "java")).orElse("NaN");
	}
	private static void lowerCase(String str, Consumer consumer) {

java空指针异常处理_空指针异常怎么解决_报空指针异常

consumer.accept(lowerCase(str)); } } //输出 //hallo java! //NaN

登录复制

这样java空指针异常处理,我们就可以动态地处理一个字符串。 如果任何时候发现该值为 null,则使用 orElse 返回默认的“NaN”。

一般来说,我们可以用Optional包裹任何数据结构,然后用函数式的方式处理它,而不用关心随时可能出现的null。

让我们看看前面提到的 Person.getCountry().getProvince().getCity() 是如何在没有一堆 if 的情况下处理的。

第一种方法是不更改以前的实体:

import java.util.Optional;public class Test {	public static void main(String[] args) {		
System.out.println(Optional.ofNullable(new Person())
			.map(x->x.country)
			.map(x->x.provinec)
			.map(x->x.city)
			.map(x->x.name)
			.orElse("unkonwn"));
	}
}class Person {	Country country;
}class Country {	Province provinec;
}class Province {	City city;
}class City {	String name;
}

登录复制

在这里,Optional 用作每次返回的 shell。 如果某个位置返回null,则直接获取“unkonwn”。

第二种方式是用Optional定义所有的值 :

import java.util.Optional;public class Test {	public static void main(String[] args) {
		System.out.println(new Person()
				.country.flatMap(x -> x.provinec)
				.flatMap(Province::getCity)
				.flatMap(x -> x.name)
				.orElse("unkonwn"));
	}

报空指针异常_java空指针异常处理_空指针异常怎么解决

}class Person { Optional country = Optional.empty(); }class Country { Optional provinec; }class Province { Optional city; Optional getCity(){//用于:: return city; } }class City { Optional name; }

登录复制

第一种方式无需任何改动即可与现有的JavaBean、Entity或POJA顺利集成,更容易集成到第三方接口(如spring beans)。 目前推荐关注第一种使用Optional的方法。 毕竟,不是团队中的每个人都能理解每个带有 Optional 的 get/set 的目的。

Optional还提供了过滤数据的filter方法(其实Java8中所有的流式接口都提供了filter方法)。 例如,以往我们判断值存在,并进行相应的处理:

if(Province!= null){
  City city = Province.getCity();  if(null != city && "guangzhou".equals(city.getName()){
    System.out.println(city.getName());
  }else{
    System.out.println("unkonwn");
  }
}

登录复制

现在我们可以修改为

Optional.ofNullable(province)
   .map(x->x.city)
   .filter(x->"guangzhou".equals(x.getName()))
   .map(x->x.name)
   .orElse("unkonw");

登录复制

至此,使用Optional进行函数式编程的介绍就完成了。 除了上面提到的方法,Optional还根据更多的需要提供了orElseGet、orElseThrow等方法。 orElseGet会因为空值抛出空指针异常,orElseThrow会在出现空值时抛出自定义异常。 有关所有方法的详细信息,请参阅 API 文档。

写在最后

Optional只是Java函数式编程的冰山一角。 需要结合lambda、stream、Funcationinterface等特性才能真正理解Java8函数式编程的实用性。 本来想介绍一下Optional的一些源码和运行原理,但是Optional本身代码很少,API接口也很少。 仔细想了想,也没什么好说的,就省略了。

Optional虽然优雅,但个人感觉效率上有一些问题,暂时还没有验证。 如果谁有实际数据,请告诉我。

我也不是“函数式编程支持者”。 从一个团队管理者的角度来看,每增加一次学习难度,使用人员和团队互动的成本就会更高。 就像传说中的,Lisp可以比C++少30倍的代码,开发效率更高,但是如果国内的常规IT公司真的用Lisp做项目,我去哪里找这些Lisp需要多少钱?基于软件? 伙计?

但是我非常鼓励大家去学习和理解函数式编程的思想。 尤其是对于过去只接触Java语言,还没有搞清楚Java8会带来什么变化的开发者来说,Java8是一个很好的机会。 更鼓励在当前项目中引入新的 Java8 特性。 一个长期合作的团队,一门古老的编程语言,需要不断注入新的活力,否则不进则退。