Есть ли новый способ сравнить элемент из того же списка в java 8 с лямбда? - PullRequest
1 голос
/ 10 марта 2020

Есть ли способ сравнить элементы в списке в java 8?

Мне интересно переписать два цикла for.

private static void test(List<Foo> a) {
    for(int i = 0 ; i < a.size(); i++){
        for(int j = 1; j < a.size() ; j++){
            Foo o1= a.get(i);
            Foo o2= a.get(j);
            if (o1.getFooA().equals(o2.getFooA()) && !o1.getFooB().equals(o2.getFooB()) ) {
                    throw new Exception("some message goes here...");
                }
            }

        }
    }

Есть ли классный способ сделать это с помощью lambdas / streams или любой другой функции, которая встречалась с java 8?

Ответы [ 3 ]

3 голосов
/ 10 марта 2020

Не спрашивайте «лямбды / потоки», но фактическое улучшение.

Когда вы используете

private static void test(List<Foo> a) throws Exception {
    Map<TypeOfFooA, TypeOfFooB> seen = new HashMap<>();
    for(Foo f: a) {
        TypeOfFooB fooB = f.getFooB(), previous = seen.putIfAbsent(f.getFooA(), fooB);
        if(previous != null && !fooB.equals(previous))
            throw new Exception("some message goes here...");
    }
}

, вы выполняете операцию за один проход вместо вложенных циклов. Это делает разницу между линейной и квадратичной c сложностью времени. Который имеет большее влияние, чем «выглядеть круто».

Вы можете переписать это для использования Stream API, например

private static void test(List<Foo> a) throws Exception {
    Map<TypeOfFooA, TypeOfFooB> seen = a.stream()
        .collect(Collectors.toMap(Foo::getFooA, Foo::getFooB,
            (previous, fooB) -> {
                if(!fooB.equals(previous))
                    throw new RuntimeException("some message goes here...");
                return previous;
            }));
}

Но это не рекомендуется. Сбор на карту, которая впоследствии не нужна, в сочетании с использованием функции слияния / сокращения для проверки и создания исключения, может удивить читателей, и он работает только для непроверенных исключений. Кроме того, функция выброса не может получить доступ ни к экземпляру Foo, ни к клавише FooA для создания сообщения об исключении.

Я рекомендую остаться с l oop.

2 голосов
/ 10 марта 2020

Это оптимизированное, но не столь очевидное решение:

a.stream()
 .collect(toMap(Foo::getFooA, Foo::getFooB, (x, y) -> {
     if (x.equals(y)) {
         return x;
     } else {
         throw new RuntimeException("some message goes here...");
     }
 }));

Используется O(1) время доступа HashMap, что исключает сложность вложенного l oop со сложностью O(n) ,


Обычно при программировании вы должны позаботиться об обоих аспектах: удобочитаемости и производительности.

Ваш простой старый код java довольно читабелен, хотя и не оптимизирован. В худшем случае он имеет сложность O(n*n), а при использовании Map вы можете уменьшить его до линейного O(n).

Разница будет иметь огромное значение при обработке больших списков.

0 голосов
/ 10 марта 2020

Если вам абсолютно необходимо использовать лямбды для коэффициента "крутизны". Вот один из вариантов:

private static void test(List<Foo> a) {
    a.forEach(o1 -> {
        if (a.stream().anyMatch(o2 -> o1.getFooA().equals(o2.getFooA()) && !o1.getFooB().equals(o2.getFooB()))) {
            throw new Exception("some message goes here...");
        }
    });
}

Вместо двух вложенных циклов - это лямбда "forEach" с вложенным "anyMatch", которая делает то же самое. Я выбрал этот подход, потому что он делает более очевидным, что здесь происходит.

...