Null-Check и isPresent — другое имя, но та же проблема?

У меня есть следующий код на Java:

public class Browser {

  public URL back() {
    try {
      //simulate: fetch last URL from Stack
      return Math.random() < 0.5 ? new URL("http://google.de") : null;
    } catch(MalformedURLException e) {
      return null;
    }
  }

  public void retrieveSite(URL url) {
    System.out.println(url);
    //...
  }

  public static void main(String[] args) {
    System.out.println("Normal back");
    Browser browser = new Browser();
    URL back = browser.back();
    if (back != null) browser.retrieveSite(back);
  }
}

Я хочу узнать больше о Optional и переписать этот код, чтобы return null и if (back!=null) больше не требовались.

Итак, вот что я получил:

public class Browser {

  Optional<URL> url = Optional.empty();

  public Optional<URL> back() {

    try {
      //simulate: fetch last URL from Stack
      if (Math.random()<0.5) {
        url = Optional.of(new URL("http://google.de"));
      } 
      return url;
    } catch(MalformedURLException e) {
      return url;
    }
  }

  public void retrieveSite(Optional<URL> url) {
    System.out.println(url);
    //...
  }

  public static void main(String[] args) {
    System.out.println("Normal back");
    Browser browser = new Browser();
    Optional<URL> back = browser.back();
    if (back.isPresent()) {
      browser.retrieveSite(back);
    }   
  }
}

Теперь, чтобы избежать передачи пустого Optional в retrieveSite, я должен проверить текущее значение. Но что именно я получаю, проверяя isPresent, а не просто !=null? Должен ли я вместо этого вернуть значение default, чтобы избавиться от isPresent?

Также мне пришлось изменить параметр для retrieveSite(), чтобы взять Optional, что считается плохой практикой?

Заранее спасибо.

Дело в том, что Optional не обязательно устраняет необходимость выполнять проверки: вы не можете рассматривать Optional<URL> как URL, поэтому вы придется явно обрабатываете наличие (или отсутствие). То же самое не относится к нулевому URL: вы можете передать null там, где ожидается ненулевое URL, и вы не узнаете, пока не попытаетесь разыменовать его.

Andy Turner 21.05.2019 18:53

Спасибо за разъяснение Энди! :)

Clayy91 21.05.2019 21:10
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
2
62
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Немного другой вариант для вашего второго подхода может быть реализован гораздо чище:

static class Browser {

    // good practice to return Optional instead of a null 
    Optional<URL> back() {
        try {
            //simulate: fetch last URL from Stack
            return Math.random() < 0.5 ? Optional.of(new URL("http://google.de")) : Optional.empty();
        } catch (MalformedURLException e) {
            return Optional.empty();
        }
    }

    // avoid using Optional as a parameter to a method
    static void retrieveSite(URL url) {
        System.out.println(url);
    }

    public static void main(String[] args) {
        System.out.println("Normal back");
        Browser browser = new Browser();
        // perform a void operation if the URL is present (consuming it)
        browser.back().ifPresent(Browser::retrieveSite);
    }
}

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

Clayy91 21.05.2019 23:30

@ Clayy91 Clayy91 В эта почта уже есть несколько объяснений того же самого.

Naman 22.05.2019 03:25
Ответ принят как подходящий

С Optional вам нужно развернуть/проверить Optional, чтобы получить его, и вы также получаете исключение досрочного опустошения, если Optional используется неправильно (принцип быстрой неудачи).
Например :

  public static void main(String[] args) {
    System.out.println("Normal back");
    Browser browser = new Browser(); 
   // unwraping the optional or testing it is mandatory to get the object inside in
    browser.back().ifPresent(browser::retrieveSite); 
    // for example it will not compile
    browser.retrieveSite(browser.back()); 
    // of course you could cheat by invoking Optional.get() but that is a bad practice and the absence of object will be detected as soon as the invocation 
    browser.retrieveSite(browser.back().get()); 
  }

  public void retrieveSite(URL url) {
    //...
  }

Без Optional возможен NPE, если клиент забудет явно проверить отсутствие недействительности (url != null). Эта проверка действительно менее привлекательна для разработчика, чем вызов метода, который является обязательным для получения/отображения обернутого объекта. Кроме того, вы можете найти ссылку null позже в самом нижнем слое, если параметр url передается через слои, что потенциально усложняет понимание и решение проблем:

  public static void main(String[] args) {
    System.out.println("Normal back");
    Browser browser = new Browser(); 
    // No unwrapping is necessary to get the url. 
    // So the robustness of the code depends on the developer habits
    browser.retrieveSite(browser.back());     
  }

  public void retrieveSite(URL url) {        
    //...
  }

Возможно, стоит отметить (явно), что, глядя на тип возвращаемого значения метода URL, вы не видите, что это может быть null, тогда как возвращаемый тип Optional<URL> сразу кричит, что он может отсутствовать. Кроме того, isPresent() имеет то же значение, что и «не является null» в предыдущей версии, потому что в этом конкретном примере null имело значение «отсутствует». Но null не всегда имеет такое значение. Иногда null означает «Я забыл инициализировать это поле», и в этом случае я бы никогда не переключился на Optional.

Holger 22.05.2019 13:04

@Holger Согласен на первую часть. Для «Иногда null означает «Я забыл инициализировать это поле», и в этом случае я бы никогда не переключился на необязательный». Я не. Необязательный не должен использоваться для полей. Так что никаких серьезных дискуссий по этому поводу. Теперь проверка не null для поля может, конечно, иметь смысл, но это отличается от фактического варианта использования: вызов метода, который что-то возвращает.

davidxxx 22.05.2019 20:59

Конечно, вы вообще не должны использовать необязательные для полей. Но когда вы создаете метод, возвращающий значение поля, вы можете решить сделать тип возвращаемого значения метода Optional, если поле может быть null, или необязательным, если предполагается, что оно никогда не должно быть null.

Holger 23.05.2019 09:16

Другие вопросы по теме