summary
When I was free, I would take time to learn Groovy and Scala running on jvm, and found that they handle null much more cautiously than earlier versions of Java. In Java 8, Optional gives a very elegant solution for functional programming null processing. This article will explain the long-standing bad handling of null in Java, and then introduce the use of Optional to implement Java functional programming.
The null that bothered us in those years
There is a legend in the Java world: Only when you truly understand the null pointer exception can you be considered a qualified Java developer. In our java character career, we will encounter various null processing every day. We may write codes like the following repeatedly every day:
if(null != obj1){ if(null != obje2){ // do something }}If you have a little vision, Javaer will do some pretty things and 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 "" mean?
Then inertial thinking tells us, "" and null are both empty string codes? I simply upgraded 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 check out how much code you have written in the current project or your past code and what is similar to the above code.
I wonder if you have seriously thought about a question: What does a null mean?
Recalling, how many times did we encounter java.lang.NullPointerException exceptions in our previous gramming career? NullPointerException is a RuntimeException level exception that does not need to be captured. If we accidentally handle it, we 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 it is not the place where the NullPointerException was thrown, which caused the problem. We have to go deeper to find out where this null was generated, and the logs are often unable to be tracked at this time.
Sometimes it is even more tragic that the places where null values are produced are often not in our own project code. This has a more embarrassing fact - when we call various third-party interfaces of different quality, it is hard to tell if an interface returns a null by chance...
Go back to the previous cognitive problem of null. Many Javaers believe that null means "nothing" or "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 give me. If this condition cannot find the corresponding "value", then of course I will return a null to you that there is no "thing". Let's take a look at the following code, which is 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, and daily business code is definitely much more complicated than this, but in fact, a large number of our Java codes are written according to this routine. People who know how to use goods can see at a glance that they will definitely throw a NullPointerException in the end. However, when we write business code, we rarely think of dealing with this possible null (maybe the API document has been written very clearly and will return null in some cases, but do you make sure you will carefully read the API document before starting to write the code?), until we reached a certain stage of testing, a NullPointerException suddenly popped up, and we realized that we had to add a judgment like below to solve the null value that might 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 some null problems cannot be discovered until the testing phase, then the problem is now - how many nulls are not properly processed in those complex and well-structured business codes?
The maturity and rigor of a project can often be seen when dealing with null. For example, Guava gave an elegant null processing method before JDK1.6, which shows how deep the skills are.
Ghostly nulls prevent us from progressing
If you are a Javaer focusing on traditional object-oriented development, you may be used to the various problems caused by null. But many years ago, the great god said that null is a pit.
Tony Hall (Don't you know who this guy is? Go check it 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." (The general meaning is: "I call it the invention of null a priceless mistake. Because in the wilderness of computers in 1965, empty references were too easy to implement, so I couldn't resist the temptation to invent the null pointer.").
Then, let's see what other problems will null introduce.
Check out the following code:
String address = person.getCountry().getProvince().getCity();
If you have played some functional languages (Haskell, Erlang, Clojure, Scala, etc.), the above is a very natural way of writing. Of course, the above writing method can be implemented using Java.
But in order to handle all possible null exceptions perfectly, we have to change this elegant function programming paradigm to this:
if (person != null) { Country country = person.getCountry(); if (country != null) { Province province = country.getProvince(); if (province != null) { address = province.getCity(); } }}In an instant, the high-quality functional programming Java8 returned to 10 years ago. Such nested judgments are still trivial, increasing the amount of code and being inelegant. More likely to happen: For most of the time, people will forget to judge the null that may occur, even elderly people who have written code for many years are no exception.
The above section of null processing, which is nested layer by layer, is also a part of traditional Java criticized for a long time. If you use early Java versions as your enlightenment language, this odor of get->if null->return will affect you for a long time (remember in a foreign community, which is called: entity-oriented development).
Using Optional to implement Java functional programming
Okay, after talking about all kinds of problems, we can enter a new era.
Long before the launch of Java SE 8 version, other similar functional development languages had their own various solutions. Here is Groovy's code:
String version = computer?.getSoundcard()?.getUSB()?.getVersion(): "unkonwn";
Haskell uses a Maybe type class identifier to process null values. Scala, known as a multi-paradigm development language, provides an Option[T] that is similar to Maybe, for wrapping and processing null.
Java8 introduces java.util.Optional<T> to handle the null problem of functional programming. The processing ideas of Optional<T> are similar to those of Haskell and Scala, but there are some differences. Let's take a look at the following example of Java code:
public class Test { public static void main(String[] args) { final String text = "Hallo world!"; Optional.ofNullable(text)//Show to create an Optional shell.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)//Return null .ifPresent(System.out::println); } // Print and intercept the string after str[5] private static String print(String str) { System.out.println(str); return str.substring(6); }}//Consol output //num1:Hallo world!//num2:world!//num3://num4:Hallo world!(You can copy the above code into your IDE, provided that JDK8 must be installed.)
Two Optionals are created in the above code, and the functions implemented are basically the same. They both use Optional as the String shell to truncate the String. When a null value is encountered during processing, the processing will no longer be continued. We can find that after s->null appears in the second Optional, the subsequent ifPresent will no longer be executed.
Pay attention to the output //num3:, which means that a "" character is output, not a null.
Optional provides rich interfaces to handle various situations, such as modifying the code to:
public class Test { public static void main(String[] args) { final String text = "Hallo World!"; System.out.println(lowerCase(text));//Method one lowerCase(null, System.out::println);//Method two} 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)); }}//Output//hallo java!//NaNIn this way, we can dynamically process a string. If the value is found to be null at any time, we use orElse to return the preset default "NaN".
In general, we can wrap any data structure in Optional and then process it in a functional way without having to worry about nulls that may appear at any time.
Let's take a look at how Person.getCountry().getProvince().getCity() mentioned earlier does not require a bunch of ifs to handle it.
The first method is to not change the previous 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 provisionec;}class Province { City city;}class City { String name;}Here, Optional is used as the shell returned every time. If a certain position returns null, you will get "unkonwn".
The second method is to define all values in 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> provisionc;}class Province { Optional<City> city; Optional<City> getCity(){//For :: return city; }}class City { Optional<String> name;}The first method can be smoothly integrated with existing JavaBeans, Entity or POJA without any changes, and can be more easily integrated into third-party interfaces (such as spring beans). It is recommended that the first Optional method is used as the main method. After all, not everyone in the team can understand the purpose of each get/set with an Optional.
Optional also provides a filter method for filtering data (in fact, stream-style interfaces in Java 8 provide filter methods). For example, in the past we judged that the value existed and made corresponding processing:
if(Province!= null){ City city = Province.getCity(); if(null != city && "guangzhou".equals(city.getName()){ System.out.println(city.getName()); }else{ System.out.println("unkonwn"); }}Now we can modify it to
Optional.ofNullable(province) .map(x->x.city) .filter(x->"guangzhou".equals(x.getName())) .map(x->x.name) .orElse("unkonw");At this point, the functional programming introduction is completed using Optional. In addition to the methods mentioned above, Optional also provides methods based on more needs, orElseGet, orElseThrow, etc. orElseGet will throw a null pointer exception because of the null value, and orElseThrow will throw a user-defined exception when null occurs. You can view the API documentation to learn more about all methods.
Written at the end
Optional is just the tip of the iceberg of Java functional programming. It is necessary to combine features such as lambda, stream, and Functioninterface to truly understand the effectiveness of Java8 functional programming. I originally wanted to introduce some Optional source code and operating principles, but Optional itself has very little code and not much API interface. If you think about it carefully, there is nothing to say and you will omit it.
Although Optional is elegant, I personally feel that there are some efficiency issues, but it has not been verified yet. If anyone has any data, please let me know.
I am not a "functional programming supporter". From the perspective of team managers, for every little learning difficulty is increased, the cost of personnel usage and team interaction will be higher. Just like in 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 to go and how much does it cost to get these guys who use Lisp?
But I highly encourage everyone to learn and understand the ideas of functional programming. Especially developers who used to be invaded by Java 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 the current project. A team that cooperates for a long time and an ancient programming language need to constantly inject new vitality, otherwise you will retreat if you don’t advance.
The above is the collection of Java Optional information. We will continue to add relevant information in the future. Thank you for your support for this website!