У меня есть следующий код на 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
, что считается плохой практикой?
Заранее спасибо.
Спасибо за разъяснение Энди! :)
Немного другой вариант для вашего второго подхода может быть реализован гораздо чище:
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 Clayy91 В эта почта уже есть несколько объяснений того же самого.
С 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 Согласен на первую часть. Для «Иногда null означает «Я забыл инициализировать это поле», и в этом случае я бы никогда не переключился на необязательный». Я не. Необязательный не должен использоваться для полей. Так что никаких серьезных дискуссий по этому поводу. Теперь проверка не null для поля может, конечно, иметь смысл, но это отличается от фактического варианта использования: вызов метода, который что-то возвращает.
Конечно, вы вообще не должны использовать необязательные для полей. Но когда вы создаете метод, возвращающий значение поля, вы можете решить сделать тип возвращаемого значения метода Optional
, если поле может быть null
, или необязательным, если предполагается, что оно никогда не должно быть null
.
Дело в том, что
Optional
не обязательно устраняет необходимость выполнять проверки: вы не можете рассматриватьOptional<URL>
какURL
, поэтому вы придется явно обрабатываете наличие (или отсутствие). То же самое не относится к нулевомуURL
: вы можете передатьnull
там, где ожидается ненулевоеURL
, и вы не узнаете, пока не попытаетесь разыменовать его.