Anleitung zu Java 8 Optional

1. Übersicht

In diesem Tutorial zeigen wir die optionale Klasse, die in Java 8 eingeführt wurde.

Der Zweck der Klasse besteht darin, eine Lösung auf Typebene für die Darstellung optionaler Werte anstelle von Nullreferenzen bereitzustellen .

Lesen Sie den offiziellen Oracle-Artikel, um ein tieferes Verständnis dafür zu erhalten, warum wir uns für die optionale Klasse interessieren sollten .

2. Optionale Objekte erstellen

Es gibt verschiedene Möglichkeiten, optionale Objekte zu erstellen .

Um ein leeres optionales Objekt zu erstellen , müssen wir lediglich die statische Methode empty () verwenden:

@Test public void whenCreatesEmptyOptional_thenCorrect() { Optional empty = Optional.empty(); assertFalse(empty.isPresent()); }

Beachten Sie, dass wir die Methode isPresent () verwendet haben, um zu überprüfen, ob das optionale Objekt einen Wert enthält . Ein Wert ist nur vorhanden, wenn wir Optional mit einem Wert ungleich Null erstellt haben. Wir werden uns im nächsten Abschnitt die isPresent () -Methode ansehen .

Wir können auch ein optionales Objekt mit der statischen Methode () erstellen :

@Test public void givenNonNull_whenCreatesNonNullable_thenCorrect() { String name = "baeldung"; Optional opt = Optional.of(name); assertTrue(opt.isPresent()); }

Das an die of () -Methode übergebene Argument darf jedoch nicht null sein. Andernfalls erhalten wir eine NullPointerException :

@Test(expected = NullPointerException.class) public void givenNull_whenThrowsErrorOnCreate_thenCorrect() { String name = null; Optional.of(name); }

Falls wir jedoch einige Nullwerte erwarten , können wir die ofNullable () -Methode verwenden:

@Test public void givenNonNull_whenCreatesNullable_thenCorrect() { String name = "baeldung"; Optional opt = Optional.ofNullable(name); assertTrue(opt.isPresent()); }

Wenn wir auf diese Weise eine Nullreferenz übergeben , wird keine Ausnahme ausgelöst, sondern ein leeres optionales Objekt zurückgegeben:

@Test public void givenNull_whenCreatesNullable_thenCorrect() { String name = null; Optional opt = Optional.ofNullable(name); assertFalse(opt.isPresent()); }

3. Überprüfen der Wertpräsenz : isPresent () und isEmpty ()

Wenn ein optionales Objekt von einer Methode zurückgegeben oder von uns erstellt wurde, können wir mit der Methode isPresent () überprüfen, ob ein Wert darin enthalten ist oder nicht :

@Test public void givenOptional_whenIsPresentWorks_thenCorrect() { Optional opt = Optional.of("Baeldung"); assertTrue(opt.isPresent()); opt = Optional.ofNullable(null); assertFalse(opt.isPresent()); }

Diese Methode gibt true zurück , wenn der umschlossene Wert nicht null ist.

Ab Java 11 können wir mit der isEmpty- Methode auch das Gegenteil tun :

@Test public void givenAnEmptyOptional_thenIsEmptyBehavesAsExpected() { Optional opt = Optional.of("Baeldung"); assertFalse(opt.isEmpty()); opt = Optional.ofNullable(null); assertTrue(opt.isEmpty()); }

4. Bedingte Aktion mit ifPresent ()

Mit der ifPresent () -Methode können wir Code für den umschlossenen Wert ausführen, wenn festgestellt wird, dass er nicht null ist . Vor Optional würden wir Folgendes tun:

if(name != null) { System.out.println(name.length()); }

Dieser Code prüft, ob die Namensvariable null ist oder nicht, bevor er Code darauf ausführt. Dieser Ansatz ist langwierig und nicht das einzige Problem - er ist auch fehleranfällig.

Was garantiert in der Tat, dass wir diese Variable nach dem Drucken nicht mehr verwenden und dann vergessen, die Nullprüfung durchzuführen?

Dies kann zur Laufzeit zu einer NullPointerException führen, wenn ein Nullwert in diesen Code gelangt . Wenn ein Programm aufgrund von Eingabeproblemen fehlschlägt, ist dies häufig auf schlechte Programmierpraktiken zurückzuführen.

Optional können wir explizit mit nullbaren Werten umgehen, um gute Programmierpraktiken durchzusetzen.

Schauen wir uns nun an, wie der obige Code in Java 8 überarbeitet werden kann.

In einem typischen funktionalen Programmierstil können wir eine Aktion für ein tatsächlich vorhandenes Objekt ausführen:

@Test public void givenOptional_whenIfPresentWorks_thenCorrect() { Optional opt = Optional.of("baeldung"); opt.ifPresent(name -> System.out.println(name.length())); }

Im obigen Beispiel werden nur zwei Codezeilen verwendet, um die fünf im ersten Beispiel verwendeten zu ersetzen: eine Zeile zum Umschließen des Objekts in ein optionales Objekt und die nächste zum Durchführen einer impliziten Validierung sowie zum Ausführen des Codes.

5. Standardwert Mit orElse ()

Die orElse () -Methode wird verwendet, um den in einer optionalen Instanz eingeschlossenen Wert abzurufen . Es wird ein Parameter benötigt, der als Standardwert fungiert. Die orElse () -Methode gibt den umschlossenen Wert zurück, wenn er vorhanden ist, und das Argument ansonsten:

@Test public void whenOrElseWorks_thenCorrect() { String nullName = null; String name = Optional.ofNullable(nullName).orElse("john"); assertEquals("john", name); }

6. Standardwert Mit orElseGet ()

Die orElseGet () -Methode ähnelt orElse () . Anstatt jedoch einen Wert für die Rückgabe zu verwenden, wenn der optionale Wert nicht vorhanden ist, wird eine Lieferantenfunktionsschnittstelle verwendet, die aufgerufen wird und den Wert des Aufrufs zurückgibt:

@Test public void whenOrElseGetWorks_thenCorrect() { String nullName = null; String name = Optional.ofNullable(nullName).orElseGet(() -> "john"); assertEquals("john", name); }

7. Unterschied zwischen orElse und orElseGet ()

Für viele Programmierer, die Optional oder Java 8 noch nicht kennen, ist der Unterschied zwischen orElse () und orElseGet () nicht klar. Tatsächlich erwecken diese beiden Methoden den Eindruck, dass sie sich in ihrer Funktionalität überlappen.

Es gibt jedoch einen subtilen, aber sehr wichtigen Unterschied zwischen den beiden, der die Leistung unseres Codes drastisch beeinflussen kann, wenn er nicht gut verstanden wird.

Erstellen wir in der Testklasse eine Methode namens getMyDefault () , die keine Argumente akzeptiert und einen Standardwert zurückgibt:

public String getMyDefault() { System.out.println("Getting Default Value"); return "Default Value"; }

Sehen wir uns zwei Tests an und beobachten ihre Nebenwirkungen, um festzustellen, wo sich orElse () und orElseGet () überschneiden und wo sie sich unterscheiden:

@Test public void whenOrElseGetAndOrElseOverlap_thenCorrect() { String text = null; String defaultText = Optional.ofNullable(text).orElseGet(this::getMyDefault); assertEquals("Default Value", defaultText); defaultText = Optional.ofNullable(text).orElse(getMyDefault()); assertEquals("Default Value", defaultText); }

Im obigen Beispiel wird ein Nulltext in ein optionales Objekt eingeschlossen und versucht, den umgebrochenen Wert mit jedem der beiden Ansätze abzurufen.

Der Nebeneffekt ist:

Getting default value... Getting default value...

Die Methode getMyDefault () wird jeweils aufgerufen. Nun ist es so , dass , wenn der umwickelte Wert nicht vorhanden ist , dann beide OrElse () und orElseGet () Arbeit genau die gleiche Art und Weise.

Lassen Sie uns nun einen weiteren Test ausführen, bei dem der Wert vorhanden ist. Im Idealfall sollte der Standardwert nicht einmal erstellt werden:

@Test public void whenOrElseGetAndOrElseDiffer_thenCorrect() { String text = "Text present"; System.out.println("Using orElseGet:"); String defaultText = Optional.ofNullable(text).orElseGet(this::getMyDefault); assertEquals("Text present", defaultText); System.out.println("Using orElse:"); defaultText = Optional.ofNullable(text).orElse(getMyDefault()); assertEquals("Text present", defaultText); }

Im obigen Beispiel wird kein Nullwert mehr umbrochen , und der Rest des Codes bleibt gleich.

Schauen wir uns nun die Nebenwirkungen des Ausführens dieses Codes an:

Using orElseGet: Using orElse: Getting default value...

Beachten Sie, dass bei Verwendung von orElseGet () zum Abrufen des umschlossenen Werts die Methode getMyDefault () nicht einmal aufgerufen wird, da der enthaltene Wert vorhanden ist.

Bei Verwendung von orElse () wird jedoch das Standardobjekt erstellt, unabhängig davon, ob der umschlossene Wert vorhanden ist oder nicht. In diesem Fall haben wir nur ein redundantes Objekt erstellt, das niemals verwendet wird.

In diesem einfachen Beispiel entstehen beim Erstellen eines Standardobjekts keine erheblichen Kosten, da die JVM weiß, wie mit solchen umzugehen ist. Wenn jedoch eine Methode wie getMyDefault () einen Webdienstaufruf ausführen oder sogar eine Datenbank abfragen muss, werden die Kosten sehr offensichtlich.

8. Ausnahmen mit orElseThrow ()

Die Methode orElseThrow () folgt aus orElse () und orElseGet () und fügt einen neuen Ansatz für die Behandlung eines fehlenden Werts hinzu.

Anstatt einen Standardwert zurückzugeben, wenn der umschlossene Wert nicht vorhanden ist, wird eine Ausnahme ausgelöst:

@Test(expected = IllegalArgumentException.class) public void whenOrElseThrowWorks_thenCorrect() { String nullName = null; String name = Optional.ofNullable(nullName).orElseThrow( IllegalArgumentException::new); }

Methodenreferenzen in Java 8 sind hier nützlich, um den Ausnahmekonstruktor zu übergeben.

Java 10 führte eine vereinfachte No- Arg -Version der orElseThrow () -Methode ein . Im Falle einer leeren Option wird eine NoSuchElelementException ausgelöst :

@Test(expected = NoSuchElementException.class) public void whenNoArgOrElseThrowWorks_thenCorrect() { String nullName = null; String name = Optional.ofNullable(nullName).orElseThrow(); }

9. Rückgabewert Mit get ()

Der letzte Ansatz zum Abrufen des umschlossenen Werts ist die Methode get () :

@Test public void givenOptional_whenGetsValue_thenCorrect() { Optional opt = Optional.of("baeldung"); String name = opt.get(); assertEquals("baeldung", name); }

However, unlike the previous three approaches, get() can only return a value if the wrapped object is not null; otherwise, it throws a no such element exception:

@Test(expected = NoSuchElementException.class) public void givenOptionalWithNull_whenGetThrowsException_thenCorrect() { Optional opt = Optional.ofNullable(null); String name = opt.get(); }

This is the major flaw of the get() method. Ideally, Optional should help us avoid such unforeseen exceptions. Therefore, this approach works against the objectives of Optional and will probably be deprecated in a future release.

So, it's advisable to use the other variants that enable us to prepare for and explicitly handle the null case.

10. Conditional Return With filter()

We can run an inline test on our wrapped value with the filter method. It takes a predicate as an argument and returns an Optional object. If the wrapped value passes testing by the predicate, then the Optional is returned as-is.

However, if the predicate returns false, then it will return an empty Optional:

@Test public void whenOptionalFilterWorks_thenCorrect() { Integer year = 2016; Optional yearOptional = Optional.of(year); boolean is2016 = yearOptional.filter(y -> y == 2016).isPresent(); assertTrue(is2016); boolean is2017 = yearOptional.filter(y -> y == 2017).isPresent(); assertFalse(is2017); }

The filter method is normally used this way to reject wrapped values based on a predefined rule. We could use it to reject a wrong email format or a password that is not strong enough.

Let's look at another meaningful example. Say we want to buy a modem, and we only care about its price.

We receive push notifications on modem prices from a certain site and store these in objects:

public class Modem { private Double price; public Modem(Double price) { this.price = price; } // standard getters and setters }

We then feed these objects to some code whose sole purpose is to check if the modem price is within our budget range.

Let's now take a look at the code without Optional:

public boolean priceIsInRange1(Modem modem) { boolean isInRange = false; if (modem != null && modem.getPrice() != null && (modem.getPrice() >= 10 && modem.getPrice() <= 15)) { isInRange = true; } return isInRange; }

Pay attention to how much code we have to write to achieve this, especially in the if condition. The only part of the if condition that is critical to the application is the last price-range check; the rest of the checks are defensive:

@Test public void whenFiltersWithoutOptional_thenCorrect() { assertTrue(priceIsInRange1(new Modem(10.0))); assertFalse(priceIsInRange1(new Modem(9.9))); assertFalse(priceIsInRange1(new Modem(null))); assertFalse(priceIsInRange1(new Modem(15.5))); assertFalse(priceIsInRange1(null)); }

Apart from that, it's possible to forget about the null checks over a long day without getting any compile-time errors.

Now let's look at a variant with Optional#filter:

public boolean priceIsInRange2(Modem modem2) { return Optional.ofNullable(modem2) .map(Modem::getPrice) .filter(p -> p >= 10) .filter(p -> p <= 15) .isPresent(); }

The map call is simply used to transform a value to some other value. Keep in mind that this operation does not modify the original value.

In our case, we are obtaining a price object from the Model class. We will look at the map() method in detail in the next section.

First of all, if a null object is passed to this method, we don't expect any problem.

Secondly, the only logic we write inside its body is exactly what the method name describes — price-range check. Optional takes care of the rest:

@Test public void whenFiltersWithOptional_thenCorrect() { assertTrue(priceIsInRange2(new Modem(10.0))); assertFalse(priceIsInRange2(new Modem(9.9))); assertFalse(priceIsInRange2(new Modem(null))); assertFalse(priceIsInRange2(new Modem(15.5))); assertFalse(priceIsInRange2(null)); }

The previous approach promises to check price range but has to do more than that to defend against its inherent fragility. Therefore, we can use the filter method to replace unnecessary if statements and reject unwanted values.

11. Transforming Value With map()

In the previous section, we looked at how to reject or accept a value based on a filter.

We can use a similar syntax to transform the Optional value with the map() method:

@Test public void givenOptional_whenMapWorks_thenCorrect() { List companyNames = Arrays.asList( "paypal", "oracle", "", "microsoft", "", "apple"); Optional
    
      listOptional = Optional.of(companyNames); int size = listOptional .map(List::size) .orElse(0); assertEquals(6, size); }
    

In this example, we wrap a list of strings inside an Optional object and use its map method to perform an action on the contained list. The action we perform is to retrieve the size of the list.

The map method returns the result of the computation wrapped inside Optional. We then have to call an appropriate method on the returned Optional to retrieve its value.

Notice that the filter method simply performs a check on the value and returns a boolean. The map method however takes the existing value, performs a computation using this value, and returns the result of the computation wrapped in an Optional object:

@Test public void givenOptional_whenMapWorks_thenCorrect2() { String name = "baeldung"; Optional nameOptional = Optional.of(name); int len = nameOptional .map(String::length) .orElse(0); assertEquals(8, len); }

We can chain map and filter together to do something more powerful.

Let's assume we want to check the correctness of a password input by a user. We can clean the password using a map transformation and check its correctness using a filter:

@Test public void givenOptional_whenMapWorksWithFilter_thenCorrect() { String password = " password "; Optional passOpt = Optional.of(password); boolean correctPassword = passOpt.filter( pass -> pass.equals("password")).isPresent(); assertFalse(correctPassword); correctPassword = passOpt .map(String::trim) .filter(pass -> pass.equals("password")) .isPresent(); assertTrue(correctPassword); }

As we can see, without first cleaning the input, it will be filtered out — yet users may take for granted that leading and trailing spaces all constitute input. So, we transform a dirty password into a clean one with a map before filtering out incorrect ones.

12. Transforming Value With flatMap()

Just like the map() method, we also have the flatMap() method as an alternative for transforming values. The difference is that map transforms values only when they are unwrapped whereas flatMap takes a wrapped value and unwraps it before transforming it.

Previously, we created simple String and Integer objects for wrapping in an Optional instance. However, frequently, we will receive these objects from an accessor of a complex object.

To get a clearer picture of the difference, let's have a look at a Person object that takes a person's details such as name, age and password:

public class Person { private String name; private int age; private String password; public Optional getName() { return Optional.ofNullable(name); } public Optional getAge() { return Optional.ofNullable(age); } public Optional getPassword() { return Optional.ofNullable(password); } // normal constructors and setters }

We would normally create such an object and wrap it in an Optional object just like we did with String.

Alternatively, it can be returned to us by another method call:

Person person = new Person("john", 26); Optional personOptional = Optional.of(person);

Notice now that when we wrap a Person object, it will contain nested Optional instances:

@Test public void givenOptional_whenFlatMapWorks_thenCorrect2() { Person person = new Person("john", 26); Optional personOptional = Optional.of(person); Optional
    
      nameOptionalWrapper = personOptional.map(Person::getName); Optional nameOptional = nameOptionalWrapper.orElseThrow(IllegalArgumentException::new); String name1 = nameOptional.orElse(""); assertEquals("john", name1); String name = personOptional .flatMap(Person::getName) .orElse(""); assertEquals("john", name); }
    

Here, we're trying to retrieve the name attribute of the Person object to perform an assertion.

Note how we achieve this with map() method in the third statement, and then notice how we do the same with flatMap() method afterwards.

The Person::getName method reference is similar to the String::trim call we had in the previous section for cleaning up a password.

The only difference is that getName() returns an Optional rather than a String as did the trim() operation. This, coupled with the fact that a map transformation wraps the result in an Optional object, leads to a nested Optional.

While using map() method, therefore, we need to add an extra call to retrieve the value before using the transformed value. This way, the Optional wrapper will be removed. This operation is performed implicitly when using flatMap.

13. Chaining Optionals in Java 8

Sometimes, we may need to get the first non-empty Optional object from a number of Optionals. In such cases, it would be very convenient to use a method like orElseOptional(). Unfortunately, such operation is not directly supported in Java 8.

Let's first introduce a few methods that we'll be using throughout this section:

private Optional getEmpty() { return Optional.empty(); } private Optional getHello() { return Optional.of("hello"); } private Optional getBye() { return Optional.of("bye"); } private Optional createOptional(String input) { if (input == null || "".equals(input) || "empty".equals(input)) { return Optional.empty(); } return Optional.of(input); }

In order to chain several Optional objects and get the first non-empty one in Java 8, we can use the Stream API:

@Test public void givenThreeOptionals_whenChaining_thenFirstNonEmptyIsReturned() { Optional found = Stream.of(getEmpty(), getHello(), getBye()) .filter(Optional::isPresent) .map(Optional::get) .findFirst(); assertEquals(getHello(), found); }

The downside of this approach is that all of our get methods are always executed, regardless of where a non-empty Optional appears in the Stream.

If we want to lazily evaluate the methods passed to Stream.of(), we need to use the method reference and the Supplier interface:

@Test public void givenThreeOptionals_whenChaining_thenFirstNonEmptyIsReturnedAndRestNotEvaluated() { Optional found = Stream.
    
     >of(this::getEmpty, this::getHello, this::getBye) .map(Supplier::get) .filter(Optional::isPresent) .map(Optional::get) .findFirst(); assertEquals(getHello(), found); }
    

In case we need to use methods that take arguments, we have to resort to lambda expressions:

@Test public void givenTwoOptionalsReturnedByOneArgMethod_whenChaining_thenFirstNonEmptyIsReturned() { Optional found = Stream.
    
     >of( () -> createOptional("empty"), () -> createOptional("hello") ) .map(Supplier::get) .filter(Optional::isPresent) .map(Optional::get) .findFirst(); assertEquals(createOptional("hello"), found); }
    

Often, we'll want to return a default value in case all of the chained Optionals are empty. We can do so just by adding a call to orElse() or orElseGet():

@Test public void givenTwoEmptyOptionals_whenChaining_thenDefaultIsReturned() { String found = Stream.
    
     >of( () -> createOptional("empty"), () -> createOptional("empty") ) .map(Supplier::get) .filter(Optional::isPresent) .map(Optional::get) .findFirst() .orElseGet(() -> "default"); assertEquals("default", found); }
    

14. JDK 9 Optional API

The release of Java 9 added even more new methods to the Optional API:

  • or() method for providing a supplier that creates an alternative Optional
  • ifPresentOrElse() method that allows executing an action if the Optional is present or another action if not
  • stream() method for converting an Optional to a Stream

Here is the complete article for further reading.

15. Misuse of Optionals

Finally, let's see a tempting, however dangerous, way to use Optionals: passing an Optional parameter to a method.

Imagine we have a list of Person and we want a method to search through that list for people with a given name. Also, we would like that method to match entries with at least a certain age, if it's specified.

With this parameter being optional, we come with this method:

public static List search(List people, String name, Optional age) { // Null checks for people and name return people.stream() .filter(p -> p.getName().equals(name)) .filter(p -> p.getAge().get() >= age.orElse(0)) .collect(Collectors.toList()); }

Then we release our method, and another developer tries to use it:

someObject.search(people, "Peter", null);

Now the developer executes its code and gets a NullPointerException.There we are, having to null check our optional parameter, which defeats our initial purpose in wanting to avoid this kind of situation.

Here are some possibilities we could have done to handle it better:

public static List search(List people, String name, Integer age) { // Null checks for people and name final Integer ageFilter = age != null ? age : 0; return people.stream() .filter(p -> p.getName().equals(name)) .filter(p -> p.getAge().get() >= ageFilter) .collect(Collectors.toList()); }

There, the parameter's still optional, but we handle it in only one check.

Another possibility would have been to create two overloaded methods:

public static List search(List people, String name) { return doSearch(people, name, 0); } public static List search(List people, String name, int age) { return doSearch(people, name, age); } private static List doSearch(List people, String name, int age) { // Null checks for people and name return people.stream() .filter(p -> p.getName().equals(name)) .filter(p -> p.getAge().get().intValue() >= age) .collect(Collectors.toList()); }

That way we offer a clear API with two methods doing different things (though they share the implementation).

So, there are solutions to avoid using Optionals as method parameters. The intent of Java when releasing Optional was to use it as a return type, thus indicating that a method could return an empty value. As a matter of fact, the practice of using Optional as a method parameter is even discouraged by some code inspectors.

16. Optional and Serialization

As discussed above, Optional is meant to be used as a return type. Trying to use it as a field type is not recommended.

Additionally, using Optional in a serializable class will result in a NotSerializableException. Our article Java Optional as Return Type further addresses the issues with serialization.

And, in Using Optional With Jackson, we explain what happens when Optional fields are serialized, along with a few workarounds to achieve the desired results.

17. Conclusion

In this article, we covered most of the important features of Java 8 Optional class.

We briefly explored some reasons why we would choose to use Optional instead of explicit null checking and input validation.

Wir haben auch gelernt, wie man mit den Methoden get () , orElse () und orElseGet () den Wert eines optionalen oder eines Standardwerts ermittelt, wenn dieser leer ist (und den wichtigen Unterschied zwischen den letzten beiden Methoden erkannt ).

Dann haben wir gesehen, wie wir unsere optionalen s mit map (), flatMap () und filter () transformieren oder filtern können . Wir haben besprochen, was eine fließende API optional bietet, da wir so die verschiedenen Methoden einfach verketten können.

Schließlich haben wir gesehen, warum die Verwendung von Optional s als Methodenparameter eine schlechte Idee ist und wie dies vermieden werden kann.

Der Quellcode für alle Beispiele im Artikel ist auf GitHub verfügbar.