Чому Optional.get () без виклику isPresent () поганий, але не iterator.next ()?


23

Використовуючи нові потоки Java8 api для пошуку одного конкретного елемента в колекції, я пишу такий код:

    String theFirstString = myCollection.stream()
            .findFirst()
            .get();

Тут IntelliJ попереджає, що get () викликається без перевірки спочатку isPresent ().

Однак цей код:

    String theFirstString = myCollection.iterator().next();

... не дає попередження.

Чи є якась глибока різниця між двома методами, яка робить потоковий підхід певним чином "небезпечнішим", що важливо ніколи не викликати get (), не викликаючи спочатку isPresent ()? Гуляючи навколо, я знаходжу статті, які розповідають про те, як програмісти недбало ставляться до необов’язкових <> і припускаю, що вони можуть викликати 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));

Це виглядає корисніше і, мабуть, стане природним у багатьох місцях. Я все ще відчуваю, що мені захочеться вибрати один елемент зі списку, не маючи справи з усім цим, але це могло просто так, що мені потрібно звикнути до подібних конструкцій.


7
Частина вилучення частини останнього прикладу може бути написана набагато коротше, якщо ви використовуєте orElseThrow - і всім читачам буде зрозуміло, що ви маєте намір викинути виняток.
Халк

Відповіді:


12

Перш за все, врахуйте, що перевірка є складною і, мабуть, відносно багато часу займає. Він вимагає певного статичного аналізу, і між двома дзвінками може бути небагато коду.

По-друге, ідіоматичне використання двох конструкцій дуже відрізняється. Я програмую повний робочий день у Scala, який використовує Optionsнабагато частіше, ніж робить Java. Я не можу пригадати жодного випадку , коли мені потрібно було використовувати .get()на умовах Option. Ви майже завжди повинні повертати Optionalфункцію або orElse()замість цього використовувати . Це набагато чудові способи поводження з пропущеними значеннями, ніж викидання виключень.

Оскільки в .get()рідкому режимі його потрібно викликати рідко, має сенс IDE починати складну перевірку, коли він бачить a, .get()щоб перевірити, чи правильно він використовується. На відміну від цього, iterator.next()це правильне та повсюдне використання ітератора.

Іншими словами, справа не в тому, що один поганий, а інший - не один, це один звичайний випадок, який є основоположним для його роботи і є великою ймовірністю викинути виняток під час тестування розробника, а інший використовується рідко випадок, який може бути недостатньо використаний, щоб кинути виняток під час тестування розробника. Це такі випадки, про які ви хочете, щоб IDE попередили вас.


Можливо, мені потрібно дізнатися більше про цей факультативний бізнес - я в основному бачив речі java8 як хороший спосіб зробити відображення списків об'єктів, які ми багато робимо в нашому веб-сайті. Отже, ви повинні надсилати необов'язково <> s скрізь? Це займе трохи звикання, але це ще одна посада SE! :)
Телеканал Френка

@ Frank's TV Менше, ніж вони повинні бути скрізь, і більше, ніж вони дозволяють писати функції, що перетворюють значення вміщеного типу, і ви пов’язуєте їх з mapабо flatMap. OptionalВ основному це чудовий спосіб уникнути необхідності думати все з nullчеками.
Морген

15

Необов’язковий клас призначений для використання, коли невідомо, чи присутній предмет, який він містить. Існує попередження, щоб сповістити програмістів, що існує додатковий можливий шлях коду, який вони, можливо, зможуть граціозно обробити. Ідея полягає у тому, щоб взагалі не було викидів винятків. Наведений вами випадок використання - це не те, для чого він призначений, і тому попередження для вас не має значення - якщо ви насправді хочете, щоб викид було викинуто, продовжуйте та вимикайте його (хоча тільки для цього рядка, а не глобально - ви повинні бути приймаючи рішення про те, чи слід обробляти неприсутній випадок випадковим чином, і попередження буде нагадувати вам про це.

Можливо, подібне попередження має бути створене для ітераторів. Однак цього просто ніколи не робилося, і додавання цього зараз, мабуть, спричинить занадто багато попереджень у існуючих проектах, щоб бути гарною ідеєю.

Також зауважте, що викидання власного винятку може бути кориснішим, ніж просто виняток за замовчуванням, оскільки включає опис того, який елемент, як очікувалося, існує, але не може бути дуже корисним при налагодженні в наступний момент.


6

Я погоджуюсь, що в них немає властивої різниці, проте я хотів би зазначити більший недолік.

В обох випадках у вас є тип, який забезпечує метод, який не гарантовано працює, і перед цим ви повинні викликати інший метод. Від того, чи буде ваш IDE / компілятор / тощо попереджати вас про один чи інший випадок, просто залежить від того, підтримує він цей випадок та / або у вас це налаштовано.

Однак, обидва фрагменти коду - це запахи коду. Ці вирази натякають на основні недоліки дизайну та дають крихкий код.

Ви правдиво хочете, щоб швидко провалитися, але рішенням, принаймні для мене, не може бути просто знизати його руками і кинути руки в повітря (або виняток на стек).

Можливо, ваша колекція наразі не порожня, але все, на що можна покластися, - це її тип: Collection<T>- і це не гарантує вам порожнечі. Незважаючи на те, що зараз ви знаєте, що він не порожній, змінена вимога може призвести до зміни коду наперед, і раптом ваш код потрапить у порожню колекцію.

Тепер ви можете відштовхуватися і казати іншим, що ви повинні отримати не порожній список. Ви можете бути щасливими, що цю помилку ви швидко знайдете. Тим не менш, у вас є зламана частина програмного забезпечення і вам потрібно змінити код.

Порівнюйте це з більш прагматичним підходом: оскільки ви отримуєте колекцію, і вона може бути порожньою, ви змушуєте себе вирішувати цей випадок, використовуючи необов’язкові # map, #orElse або будь-який інший із цих методів, крім #get.

Компілятор робить для вас якомога більше роботи, щоб ви могли максимально покластися на договір статичного типу отримання Collection<T>. Коли ваш код ніколи не порушує цей договір (наприклад, через будь-яку з цих двох смердючих ланцюгів дзвінків), ваш код набагато менш крихкий до змін екологічних умов.

У вищеописаному сценарії зміна, що дасть порожню колекцію вводу, не матиме жодного наслідку для вашого коду. Це просто ще один дійсний вклад і, таким чином, все ще правильно обробляється.

Цінність цього полягає в тому, що вам потрібно вкласти час на вдосконалення дизайну, на відміну від: а) ризикувати крихким кодом або б) зайвим чином ускладнювати речі, кидаючи винятки навколо.

На завершення: оскільки не всі IDE / компілятори попереджають про виклики iterator (). Next () або optional.get () без попередніх перевірок, такий код, як правило, позначається в оглядах. Для чого це не варто, я б не дозволяв такому коду дозволяти пройти огляд і вимагати чистого рішення.


Напевно, я можу трохи погодитися щодо запаху коду - я зробив приклад вище, щоб зробити короткий пост. Іншим більш вагомим випадком може бути вилучення обов'язкового елемента зі списку, що не дивно, що з'являється в IMO-програмі.
Телевізор Френка

Пляма на. "Виберіть елемент із властивістю p зі списку" -> list.stream (). Filter (p) .findFirst () .. і знову ми змушені задати питання "а що, якщо його немає там?" .. щоденний хліб і масло. Якщо це мандаторай та "просто там" - чому ти сховав це в купі інших речей в першу чергу?
Френк

4
Бо, можливо, це список, завантажений із бази даних. Можливо, у списку є статистичний збірник, який був зібраний у якійсь іншій функції: ми знаємо, що у нас є об’єкти A, B і C, я хочу знайти кількість будь-якого для В. Багато справжніх (і в моєму застосуванні поширених) випадків.
Телевізор Френка

Я не знаю про вашу базу даних, але моя не гарантує хоча б одного результату за запитом. Те саме для статистичних збірників - хто сказати, що вони завжди будуть не порожніми? Я погоджуюсь, що просто зрозуміти, що має бути той чи інший елемент, але я вважаю за краще належне статичне введення тексту в документацію або ще гірше розуміння дискусії.
Френк
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.