Статьи

Руки на Дополнительное значение

Опционально в связи с коронавирусом, все становится необязательным, как необязательный публичный сбор, необязательная работа на дому, дополнительные поездки и т. Д.

Я думаю, что сейчас самое время поговорить о реальных « опциях » в программной инженерии, которые имеют дело с NULL.

Тони Хоар признался, что допустил ошибку в миллиард долларов, изобрел Нуль. Если вы не видели его выступления, то я предлагаю посмотреть на ошибку Null-References-The-Billion-Dollar-Error .

Я поделюсь некоторыми из анти-паттерна с нулем и как это можно решить с помощью абстракций, таких как Optional или MayBe.

В этом примере мы будем использовать объект простого значения, который может иметь несколько нулевых значений.

1
2
3
4
5
6
7
public class Person {
    final String firstName;
    final String lastName;
     
     final String email; // This can be null
    final String phone; //This can be null
}

Этот объект значения может иметь нулевое значение для адреса электронной почты и номера телефона.

Сценарий: контактное лицо по электронной почте и телефону

Не использовать опционально

Первая попытка будет основана на проверке нуля, как показано ниже

1
2
3
4
5
6
7
8
//Not using optional
        if (p.email != null) {
            System.out.println("Sending email to " + p.email);
        }
 
        if (p.phone != null) {
            System.out.println("Calling " + p.phone);
        }

Так было сделано годами. Еще один распространенный шаблон с результатом сбора.

1
2
3
4
5
6
7
List<Person> p = searchPersonById("100");
 
        if (p.isEmpty()) {
            System.out.println("No result");
        } else {
            System.out.println("Person" + p.get(0));
        }

Необязательное использование необязательно

1
2
3
4
5
6
7
8
9
Optional<String> phone = contactNumber(p);
        Optional<String> email = email(p);
 
        if (phone.isPresent()) {
            System.out.println("Calling Phone " + phone.get());
        }
        if (email.isPresent()) {
            System.out.println("Sending Email " + email.get());
        }

Это немного лучше, но все достоинства Optional отбрасываются добавлением блока if / else в коде.

Всегда с удовольствием по желанию

1
2
3
4
5
6
//Always Happy
        Optional<String> phone = contactNumber(p);
        Optional<String> email = email(p);
 
        System.out.println("Calling Phone " + phone.get());
        System.out.println("Sending Email " + email.get());

Хорошо быть счастливым, но когда вы пытаетесь сделать это с помощью Optional, вы делаете большое предположение или вам не нужен дополнительный.

Вложенное свойство необязательно

Для этого сценария мы расширим объект Person и добавим свойство Home. Не каждый может владеть домом, так что это хороший кандидат на то, что он будет недоступен.

Давайте посмотрим, как сценарий контактного лица работает в этом случае

1
2
3
4
5
6
7
8
9
//Nested Property
        if (p.getHome() != null) {
            System.out.println("Sending Postal mail " + p.getHome().address);
        }
 
 
        if (p.getHome() != null && p.getHome().getInsurance() != null) {
            System.out.println("Sending Notification to insurance " + p.getHome().getInsurance().getAgency());
        }

Это то, где это начинает ухудшаться, у кода будут тонны вложенных нулевых проверок.

Приоритет по умолчанию

для этого сценария мы сначала пытаемся связаться с человеком по домашнему адресу, а если он недоступен, то связаться по офисному адресу.

01
02
03
04
05
06
07
08
09
10
11
//Address has priority , first home and then Office
 
        if (p.home != null) {
            System.out.println("Contacted at home address " + p.home.address);
            return; // Magical return for early exit
        }
 
        if (p.office != null) {
            System.out.println("Contacted at office address " + p.office.address);
            return; // Magical return for early exit
        }

Такой тип сценария требует использования расширенного потока управления для раннего возврата и делает код сложным для понимания и сопровождения.

Это некоторые из общих шаблонов, где необязательные не используются или используются неправильно.

Дополнительные модели использования

Давайте посмотрим на некоторые из хороших способов использования по желанию.

Сделать свойство необязательным, основываясь на знании предметной области

Это очень легко сделать свойство необязательным.

1
2
3
4
5
6
7
public Optional<String> getEmail() {
        return Optional.ofNullable(email);
    }
 
    public Optional<String> getPhone() {
        return Optional.ofNullable(phone);
    }

Да, это разрешено делать опциональными, никто не будет вас за это висеть и не стесняйтесь делать это без страха. Как только это изменение будет сделано, мы можем написать что-то вроде ниже

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//Use Optional
        p.getEmail().ifPresent(email -> System.out.println("Sending email to " + email));
        p.getPhone().ifPresent(phone -> System.out.println("Calling " + phone));
 
//Optional for Collection or Search type of request
 Optional<person> person="persons.stream().findFirst();" person.ifpresent(system.out::println);="" <="" pre=""><p>It looks neat, first step to code without explicit if else on application layer.</p><p>Use some power of Optional</p><pre class="brush:java">//Use IfPresent & other cool things
        phone
                .filter(number -> hasOptIn(number))
                .ifPresent(number -> System.out.println("Calling Phone " + number));
 
        email
                .filter(m -> hasOptIn(m))
                .ifPresent(m -> System.out.println("Sending Email " + m));
</pre><p>Optional is just like stream, we get all functional map,filter etc support. In above example we are checking for OptIn before contacting.</p><p>Always happy optional<br><br>Always happy optional that calls<i>"get"</i> without check will cause runtime error on sunday midnight, so it advised to use ifPresent</p><pre class="brush:java">//Don't do this
        System.out.println("Calling Phone " + phone.get());
        System.out.println("Sending Email " + email.get());
 
        //Use ifPresent to avoid runtime error
        phone.ifPresent(contact -> System.out.println("Sending email to " + contact));
        email.ifPresent(contact -> System.out.println("Calling " + contact));
</pre><p>Nested Optional</p><pre class="brush:java">p.getHome().ifPresent(a -> System.out.println("Sending Postal mail " + a.address));
 
    p.getHome()
                .flatMap(Person.Home::getInsurance)
                .ifPresent(a -> System.out.println("Sending Notification to insurance " + a.agency));
</pre><p>Flatmap does the magic and handles null check for home and convert  insurance object also.</p><p>Priority based default</p><pre class="brush:java">//Address has priority , first home and then Office
 
Optional<String> address = Stream
                .of(person.getHome().map(Home::getAddress), person.getOffice().map(Office::getAddress))
                .filter(Optional::isPresent)
                .map(Optional::get)
                .findFirst();
 
        address
                .ifPresent(add -> System.out.println("Contacting at address " + add));
</pre><p>This example is taking both home & office address and pick the first one that has value for sending notification. This particular pattern avoids lots of nested loops.</p><p>Else branch<br><br>Optional has lots of ways to handle else part of the scenario like returning some default value(orElse) , lazy default value (orElseGet) or throw exception(orElseThrow).</p><p>What is not good about optional<br><br>Each design choice has some trade off and optional also has some. It is important to know what are those so that you can make careful decision.<br><br>Memory overhead<br><br>Optional is container that holds value, so extra object is created for every value. Special consideration is required when it holds primitive value. If some performance sensitive code will be impacted by extra object creation via optional then it is better to use null.</p><p>Memory indirection<br><br>As optional is container , so every access to value need extra jump to get real value. Optional is not good choice for element in array or collection.</p><p>No serialization<br><br>I think this is good decision by Jdk team that does not encourage people to make instance variable optional. You can wrap instance variable to Optional at runtime or when required for processing.</p><div class="attribution"><table><tbody><tr><td><p>Published on Java Code Geeks with permission by Ashkrit Sharma, partner at our <a href="//www.javacodegeeks.com/join-us/jcg/" target="_blank" rel="noopener noreferrer">JCG program</a>. See the original article here: <a href="https://ashkrit.blogspot.com/2020/03/hands-on-optional-value.html" target="_blank" rel="noopener noreferrer">Hands on Optional value</a></p><p>Opinions expressed by Java Code Geeks contributors are their own.</p></td></tr></tbody></table></div></person>>