При использовании нового Java8 streams api для поиска одного конкретного элемента в коллекции я пишу такой код:
String theFirstString = myCollection.stream()
.findFirst()
.get();
Здесь IntelliJ предупреждает, что get () вызывается без предварительной проверки isPresent ().
Тем не менее, этот код:
String theFirstString = myCollection.iterator().next();
... не дает предупреждения.
Есть ли какое-то глубокое различие между этими двумя методами, которое делает потоковый подход более «опасным» в некотором смысле, что крайне важно никогда не вызывать get () без вызова isPresent () в первую очередь? Поглядывая вокруг, я нахожу статьи, рассказывающие о том, как программисты небрежны с Optional <>, и предполагаю, что они могут вызывать get () всякий раз, когда.
Дело в том, что я хотел бы, чтобы на меня было сгенерировано исключение как можно ближе к месту, где мой код содержит ошибки, что в этом случае будет тем, где я вызываю get (), когда нет элемента, который нужно найти. Я не хочу писать бесполезные эссе, такие как:
Optional<String> firstStream = myCollection.stream()
.findFirst();
if (!firstStream.isPresent()) {
throw new IllegalStateException("There is no first string even though I totally expected there to be! <sarcasm>This exception is much more useful than NoSuchElementException</sarcasm>");
}
String theFirstString = firstStream.get();
... разве есть опасность провоцировать исключения из get (), о которых я не знаю?
Прочитав ответ Карла Билефельда и комментарий Халка, я понял, что мой код исключения был немного неуклюжим, вот что-то лучше:
String errorString = "There is no first string even though I totally expected there to be! <sarcasm>This exception is much more useful than NoSuchElementException</sarcasm>";
String firstString = myCollection.stream()
.findFirst()
.orElseThrow(() -> new IllegalStateException(errorString));
Это выглядит более полезным и, вероятно, станет естественным во многих местах. Я все еще чувствую, что хочу иметь возможность выбирать один элемент из списка, не имея дело со всем этим, но это может быть просто потому, что мне нужно привыкнуть к такого рода конструкциям.
Ответы:
Прежде всего, учтите, что проверка сложна и, вероятно, требует относительно много времени. Это требует некоторого статического анализа, и между этими двумя вызовами может быть немного кода.
Во-вторых, идиоматическое использование двух конструкций очень разное. Я программирую полный рабочий день в Scala, который использует
Options
гораздо чаще, чем Java. Я не могу вспомнить ни одного случая, когда мне нужно было использовать a.get()
наOption
. Вы должны почти всегда возвращатьOptional
из своей функции или использоватьorElse()
вместо этого. Это гораздо лучший способ обработки пропущенных значений, чем создание исключений.Поскольку
.get()
при нормальном использовании его редко следует вызывать, для среды IDE имеет смысл запустить сложную проверку, когда она видит,.get()
чтобы проверить, правильно ли она использовалась. Напротив,iterator.next()
это правильное и повсеместное использование итератора.Другими словами, дело не в том, что одно плохо, а другое нет, дело в том, что один является общим случаем, который имеет фундаментальное значение для его работы и с большой вероятностью вызовет исключение во время тестирования разработчика, а другой редко используется. случай, который может быть недостаточно реализован, чтобы вызвать исключение во время тестирования разработчика. Это те случаи, о которых вы хотите, чтобы IDE предупреждали вас.
источник
map
илиflatMap
.Optional
Это в основном отличный способ избежать необходимости ставить все в скобки сnull
чеками.Класс Optional предназначен для использования, когда неизвестно, присутствует ли элемент, который он содержит. Существует предупреждение, предупреждающее программистов о том, что существует дополнительный возможный путь к коду, который они могут корректно обработать. Идея состоит в том, чтобы вообще не создавать исключений. Представленный вами вариант использования не предназначен для него, и поэтому предупреждение не имеет к вам отношения - если вы действительно хотите, чтобы было сгенерировано исключение, продолжайте и отключите его (хотя только для этой строки, а не глобально - вам следует Принятие решения о том, обрабатывать или нет не существующее дело в каждом отдельном случае, и предупреждение напомнит вам сделать это.
Возможно, аналогичное предупреждение должно быть сгенерировано для итераторов. Тем не менее, это просто никогда не было сделано, и добавление одного сейчас, вероятно, вызовет слишком много предупреждений в существующих проектах, чтобы быть хорошей идеей.
Также обратите внимание, что создание собственного исключения может быть более полезным, чем просто исключение по умолчанию, так как включает в себя описание того, какой элемент должен был существовать, но не может быть очень полезным при отладке на более позднем этапе.
источник
Я согласен, что в этом нет существенной разницы, однако я хотел бы указать на больший недостаток.
В обоих случаях у вас есть тип, который предоставляет метод, который не гарантированно работает, и вы должны вызывать другой метод до этого. Предупреждение вашей IDE / компилятора / etc о том или ином случае зависит только от того, поддерживает ли он этот случай и / или настроен ли он для этого.
Тем не менее, обе части кода являются запахами кода. Эти выражения намекают на основные недостатки дизайна и дают хрупкий код.
Конечно, вы правы в том, что хотите быстро потерпеть неудачу, но решение, по крайней мере для меня, не может состоять в том, чтобы просто отмахнуться от него и выбросить руки в воздух (или исключение в стек).
Может быть известно, что ваша коллекция на данный момент непуста, но все, на что вы действительно можете положиться, это ее тип:
Collection<T>
- и это не гарантирует вам не пустоту. Даже если вы знаете, что теперь он не пустой, измененное требование может привести к изменению исходного кода, и внезапно ваш код попадет в пустую коллекцию.Теперь вы можете отодвинуться и сказать другим, что вы должны получить непустой список. Вы можете быть счастливы, что быстро обнаружите эту «ошибку». Тем не менее, у вас сломан кусок программного обеспечения и вам нужно изменить код.
Сравните это с более прагматичным подходом: поскольку вы получаете коллекцию, и она может быть пустой, вы заставляете себя иметь дело с этим случаем, используя Optional # map, #orElse или любой другой из этих методов, кроме #get.
Компилятор выполняет для вас как можно больше работы, так что вы можете полагаться как можно больше на получение статического контракта
Collection<T>
. Когда ваш код никогда не нарушает этот контракт (например, через одну из этих вонючих цепочек вызовов), ваш код становится гораздо менее хрупким по отношению к изменяющимся условиям среды.В приведенном выше сценарии изменение, приводящее к пустой коллекции ввода, не будет иметь никакого значения для вашего кода. Это просто еще один действительный ввод и, следовательно, все еще обрабатывается правильно.
Стоимость этого заключается в том, что вам приходится тратить время на более качественные разработки, а не а) рисковать хрупким кодом или б) неоправданно усложнять вещи, создавая исключения.
И последнее замечание: поскольку не все IDE / компиляторы предупреждают о вызовах iterator (). Next () или option.get () без предварительной проверки, такой код обычно помечается в обзорах. Что бы это ни стоило, я бы не позволил такому коду разрешить проходить обзор и требовать чистого решения.
источник