Home  >  Article  >  Java  >  Java's elegant Optional null pointer handling

Java's elegant Optional null pointer handling

高洛峰
高洛峰Original
2016-11-22 15:33:481347browse

There is a legend circulating in the Java arena: You cannot be considered a qualified Java developer until you truly understand the null pointer exception. In our dazzling Java code character career, we encounter various null processing every day. We may write code like the following repeatedly every day:

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

If you have a little bit of vision as a javaer, just do something a little bit more savvy. Let's find a way to judge null

boolean checkNotNull(Object obj){
  return null == obj ? false : true; 
}

void do(){
  if(checkNotNull(obj1)){
     if(checkNotNull(obj2)){
        //do something
     }
  }
}

Then, the question comes again: if a null represents an empty string, what does "" represent?

Then inertial thinking tells us, aren't "" and null both empty string codes? I simply upgraded the judgment of the null value:

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

If you have time, you can take a look at the current project or your own past code to see how much code similar to the above has been written.

I don’t know if you have seriously thought about a question: What does a null mean?

Simple understanding - null of course means "the value does not exist".

Have some experience understanding of memory management - null means that the memory has not been allocated and the pointer points to a null address.

A little more thorough understanding - null may indicate that there is a problem with processing somewhere, or it may indicate that a certain value does not exist.

Recognition after being abused thousands of times - Oops, another NullPointerException, it seems I have to add an if (null != value).

Recall, how many times have we encountered java.lang.NullPointerException in our previous coding career? As a RuntimeException level exception, NullPointerException does not need to be explicitly captured. If not handled carefully, we will often see various exception stack outputs caused by NullPointerException in the production log. And based on this exception stack information, we cannot locate the cause of the problem at all, because the problem was not caused by the place where NullPointerException was thrown. We need to go deeper to find out where this null is generated, and at this time the logs often cannot be traced.

Sometimes what’s more tragic is that the place where null values ​​are generated is often not in our own project code. There is an even more embarrassing fact - when we call various third-party interfaces of varying quality, it is unclear whether a certain interface will return a null by some chance...

Return to the front to face null cognitive issues. Many Javaers think that null means "nothing" or "the value does not exist". According to this inertial thinking, our code logic is: you call my interface and return the corresponding "value" according to the parameters you gave me. If the corresponding "value" cannot be found under this condition, then of course I will return a null to you. There is no "anything" anymore. Let’s take a look at the following code, written in a very traditional and standard Java coding style:

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;
       }
   }
}

This piece of code is very simple. Daily business code is definitely much more complicated than this, but in fact a lot of our Java coding is based on this Written in this routine, a knowledgeable person can tell at a glance that a NullPointerException will definitely be thrown. But when we write business code, we rarely think of dealing with this possible null (perhaps the API documentation has been clearly written and null will be returned in some cases, but make sure you read the API documentation carefully before Start writing code?), until we reached a certain testing stage and a NullPointerException suddenly popped up, we realized that we had to add a judgment like the following to deal with the null value that may be returned.

// 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");
       }
   }
}

Think carefully about the past few years, have we all done this? If the problems caused by some nulls cannot be discovered until the testing phase, then the question now arises - how many nulls are not handled correctly in those graceful, complex, and well-layered business codes?

The attitude towards null processing can often tell the maturity and rigor of a project. For example, Guava provided an elegant null processing method long before JDK1.6, which shows its deep knowledge.

The ghostly null hinders our progress

If you are a Javar who focuses on traditional object-oriented development, maybe you are already used to the various problems caused by null. But many years ago, the master said that null is a trap.

Tony Hall (don’t you know who this guy is? Go check it out yourself) once said: "I call it my billion-dollar mistake. It was the invention of the null reference in 1965. I couldn' t resist the temptation to put in a null reference, simply because it was so easy to implement." (To the effect: "Brother called the invention of null a priceless mistake. Because in the barbaric era of computers in 1965, null references It’s so easy to implement that Brother Jean couldn’t resist the temptation and invented the null pointer thing.”).

Then, let’s look at what problems null will introduce.

Look at the code below:

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

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

    但是为了完满的处理所有可能出现的null异常,我们不得不把这种优雅的函数编程范式改为这样:

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 的臭毛病会影响你很长的时间(记得在某国外社区,这被称为:面向entity开发)。

利用Optional实现Java函数式编程

    好了,说了各种各样的毛病,然后我们可以进入新时代了。

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

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

    Haskell用一个 Maybe 类型类标识处理null值。而号称多范式开发语言的Scala则提供了一个和Maybe差不多意思的Option[T],用来包裹处理null。

    Java8引入了 java.util.Optional8742468051c85b06f0a0af9e3e506b5c来处理函数式编程的null问题,Optional8742468051c85b06f0a0af9e3e506b5c的处理思路和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 ->{ 
				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!

    (可以把上面的代码copy到你的IDE中运行,前提是必须安装了JDK8。)

    上面的代码中创建了2个Optional,实现的功能基本相同,都是使用Optional作为String的外壳对String进行截断处理。当在处理过程中遇到null值时,就不再继续处理。我们可以发现第二个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<String> consumer) {
		consumer.accept(lowerCase(str));
	}
}
//输出
//hallo java!
//NaN

    这样,我们可以动态的处理一个字符串,如果在任何时候发现值为null,则使用orElse返回预设默认的"NaN"。

    总的来说,我们可以将任何数据结构用Optional包裹起来,然后使用函数式的方式对他进行处理,而不必关心随时可能会出现的null。

    我们看看前面提到的Person.getCountry().getProvince().getCity()怎么不用一堆if来处理。

    第一种方法是不改变以前的entity:

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作为每一次返回的外壳,如果有某个位置返回了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"));
	}
}class Person {
	Optional<Country> country = Optional.empty();
}class Country {
	Optional<Province> provinec;
}class Province {
	Optional<City> city;	Optional<City> getCity(){//用于::
		return city;
	}
}class City {
	Optional<String> name;
}

    第一种方法可以平滑的和已有的JavaBean、Entity或POJA整合,而无需改动什么,也能更轻松的整合到第三方接口中(例如spring的bean)。建议目前还是以第一种Optional的使用方法为主,毕竟不是团队中每一个人都能理解每个get/set带着一个Optional的用意。

    Optional还提供了一个filter方法用于过滤数据(实际上Java8里stream风格的接口都提供了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会因为出现null值抛出空指针异常,而orElseThrow会在出现null时,抛出一个使用者自定义的异常。可以查看API文档来了解所有方法的细节。

写在最后的

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

    Optional虽然优雅,但是个人感觉有一些效率问题,不过还没去验证。如果有谁有确实的数据,请告诉我。

I am not a "functional programming supporter" either. From the perspective of a team manager, every time the learning difficulty increases, the cost of using personnel and the cost of team interaction will be higher. Just like in the legend, Lisp can have thirty times less code than C++ and is more efficient in development. But if a domestic conventional IT company really uses Lisp to do projects, where can you go and how much does it cost to get these Lisp-based software? Dude?

But I highly encourage everyone to learn and understand the ideas of functional programming. Especially for developers who have only indulged in Java as a language in the past and still don't know what changes Java8 will bring, Java8 is a good opportunity. It is also encouraged to introduce new Java8 features into current projects. A long-term cooperation team and an ancient programming language need to continuously inject new vitality, otherwise they will retreat if they do not advance.


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn