Почему я не могу использовать фильтр как мой последний шаг в потоке - PullRequest
8 голосов
/ 21 сентября 2019

Мне все время говорят, что это плохая практика - не прерывать поток с помощью таких методов, как collect и findFirst, но не получаю никаких реальных отзывов о том, почему в блогах так мало говорится об этом.

Глядя на следующий пример, вместо использования массивной вложенной проверки if я выбрал Optional, чтобы вернуть значение List.Как вы можете видеть, мой последний шаг - это фильтр в этом потоке.Это работает, как и ожидалось для меня, что вернуть список.Почему это неправильно и как я должен был написать это вместо этого?

import lombok.Getter;
import lombok.Setter;
import java.util.*;

public class Main {
    public static void main(String[] args) {

        RequestBean requestBean = new RequestBean();
        // if I uncomment this I will get the list values printed as expected
//        FruitBean fruitBean = new FruitBean();
//        AnotherBean anotherBean = new AnotherBean();
//        InnerBean innerBean = new InnerBean();
//        requestBean.setFruitBeans(Collections.singletonList(fruitBean));
//        fruitBean.setAnotherBeans(Collections.singletonList(anotherBean));
//        anotherBean.setInnerBeans(Collections.singletonList(innerBean));
//        List<String> beans = Arrays.asList("apple", "orange");
//        innerBean.setBeans(beans);

        List<String> result = getBeanViaOptional(requestBean);

        if(result != null){
            for(String s : result){
                System.out.println(s);
            }
        }else {
            System.out.println("nothing in list");
        }

    }

    private static List<String> getBeanViaOptional(RequestBean bean){
        Optional<List<String>> output = Optional.ofNullable(bean)
                .map(RequestBean::getFruitBeans)
                .map(n -> n.get(0))
                .map(FruitBean::getAnotherBeans)
                .map(n -> n.get(0))
                .map(AnotherBean::getInnerBeans)
                .map(n -> n.get(0))
                .map(InnerBean::getBeans)
                // why is this bad practice to end with a filter. how should I write this then?
                .filter(n -> n.contains("apple"));

        if(!output.isPresent()){
            throw new CustomException();
        }

        return output.get();
    }

    // not using this. just to show that optional was preferable compared to this.
    private static List<String> getBeanViaIfChecks(RequestBean bean){
        if(bean != null){
            if(bean.getFruitBeans() != null){
                if(bean.getFruitBeans().get(0) != null){
                    if(bean.getFruitBeans().get(0).getAnotherBeans() != null){
                        if(bean.getFruitBeans().get(0).getAnotherBeans().get(0) != null){
                            if(bean.getFruitBeans().get(0).getAnotherBeans().get(0).getInnerBeans() != null){
                                if(bean.getFruitBeans().get(0).getAnotherBeans().get(0).getInnerBeans().get(0) != null){
                                    return bean.getFruitBeans().get(0).getAnotherBeans().get(0).getInnerBeans().get(0).getBeans();
                                }
                            }
                        }
                    }
                }
            }
        }
        return null;
    }
}

@Getter
@Setter
class RequestBean{
    List<FruitBean> fruitBeans;
}

@Getter
@Setter
class FruitBean{
    List<AnotherBean> anotherBeans;
}

@Getter
@Setter
class AnotherBean{
    List<InnerBean> innerBeans;
}

@Getter
@Setter
class InnerBean{
    List<String> beans;
}

class CustomException extends RuntimeException{
    // do some custom exception stuff
}

Ответы [ 2 ]

10 голосов
/ 21 сентября 2019

Мне постоянно говорят, что это плохая практика - не прерывать поток с помощью таких методов, как collect и findFirst, но никакой реальной обратной связи относительно того, почему об этом не так много говорится в блогах.

Это действительно зависит от контекста , если вы говорите "могу ли я завершить поток промежуточной операцией, например filter, и не вызывать терминальную операцию (операцию, которая потребляет поток) когда-либо "тогда да, это плохая практика и бессмысленная, потому что вы только что определили некоторые критерии, но никогда не спрашивали о" результате "

Потоки ленивы в том смысле, что они ничего не делают, если это не указано в терминальной операции, например, collect, findFirst и т. Д.

Если вы говорите, "это так"плохая практика - возвращать поток из метода "тогда, возможно, стоит прочитать этот ответ о том, должен ли один возвращать поток или коллекцию .

Далее, обратите внимание, что ваша логика getBeanViaOptionalработает на Optional<T>, а не Stream<T>.Да, они оба имеют map, flatMap и filter, но обратите внимание, что Optional<T> может содержать только одно значение или пусто , тогда как поток может иметь один илиБольше.

Ваш подход использования Optional вместо императивного if s явно лучше с точки зрения читабельности, обслуживания и т. Д., Поэтому я бы посоветовал вам продолжить этот подход, хотя вы можете немного его улучшитьбит с помощью orElseThrow т.е.:

return Optional.ofNullable(bean)
               .map(RequestBean::getFruitBeans)
               .map(n -> n.get(0))
               .map(FruitBean::getAnotherBeans)
               .map(n -> n.get(0))
               .map(AnotherBean::getInnerBeans)
               .map(n -> n.get(0))
               .map(InnerBean::getBeans)
               .filter(n -> n.contains("apple"))
               .orElseThrow(CustomException::new);
7 голосов
/ 21 сентября 2019

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

Другое дело, что (как уже указывал Aomine) .orElseThrow - это более короткий способ получить значение в Optional и вызвать исключение, если его нет.Еще более важно то, что безопаснее использовать .orElseThrow (или .orElse, если есть значение по умолчанию).Optional.get() следует по возможности избегать.Вы получите NoSuchElementException, если нет значения.Это почти то же самое, что получить NullPointerException, когда не используется Optional.Optional при правильном использовании может защитить вас от NullPointerException.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...