Właściwe korzystanie ze streamów z filtrami i mapowaniem

Właściwe korzystanie ze streamów z filtrami i mapowaniem
  • Rejestracja: dni
  • Ostatnio: dni
0

Hej,
chciałem zapytać w jaki sposób najbardziej optymalnie korzystając ze streamów należy przekonwertować jeden z zagnieżdżonych typów na inny filtrując nie nullowe wartości.
Przykładowy kod wyjaśniający o co mi chodzi jest poniżej:

Kopiuj
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class DummyTest {

    private class A {
        private B b;

        public B getB() {
            return b;
        }

        public void setB(B b) {
            this.b = b;
        }
    }

    private class B {
        private C c;

        public C getC() {
            return c;
        }

        public void setC(C c) {
            this.c = c;
        }
    }

    private class C {
        private D d;

        public D getD() {
            return d;
        }

        public void setD(D d) {
            this.d = d;
        }
    }

    private class D {

    }

    @Test
    public void test() {
        List<A> data = generateData(100000);

        long time1 = System.currentTimeMillis();

        List<D> output = data.stream()
                .filter(Objects::nonNull)
                .map(A::getB)
                .filter(Objects::nonNull)
                .map(B::getC)
                .filter(Objects::nonNull)
                .map(C::getD)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

        long time2 = System.currentTimeMillis();

        System.out.println(output.size());
        System.out.println(time2 - time1);
    }

    @Test
    public void test1() {
        List<A> data = generateData(100000);

        long time1 = System.currentTimeMillis();

        List<D> output = data.stream()
                .filter(input -> {
                    return input != null && input.getB() != null && input.getB().getC() != null && input.getB().getC().getD() != null;
                })
                .map(new Function<A, D>() {
                    @Override
                    public D apply(A a) {
                        return a.getB().getC().getD();
                    }
                })
                .collect(Collectors.toList());

        long time2 = System.currentTimeMillis();

        System.out.println(output.size());
        System.out.println(time2 - time1);
    }

    @Test
    public void test2() {
        List<A> data = generateData(100000);

        long time1 = System.currentTimeMillis();

        List<D> output = data.stream()
                .filter(((Predicate<A>) input -> input.getB() != null).and(input -> input.getB().getC() != null).and(input -> input.getB().getC().getD() != null))
                .map(new Function<A, D>() {
                    @Override
                    public D apply(A a) {
                        return a.getB().getC().getD();
                    }
                })
                .collect(Collectors.toList());

        long time2 = System.currentTimeMillis();

        System.out.println(output.size());
        System.out.println(time2 - time1);
    }


    private List<A> generateData(int reapeat) {
        List<A> result = new ArrayList<>();

        for (int i = 0; i < reapeat; i++) {
            A a = new A();
            B b = new B();
            C c = new C();
            D d = new D();

            c.setD(d);
            b.setC(c);
            a.setB(b);

            result.add(a);
        }

        return result;
    }

}

Ogolnie wyniki zaprezentowane w linii

Kopiuj
System.out.println(time2 - time1);

są zbliżone.

Shalom
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Space: the final frontier
  • Postów: 26433
0

Wszystkie są nieczytelne. Ja bym użył jednak Optional.ofNullable() + map.

Kopiuj
    @Test
    public void test3() {
        List<A> data = generateData(howMany);
        long start = System.currentTimeMillis();
        List<D> output = data.stream()
                .map(this::convert)
                .filter(Optional::isPresent)
                .map(Optional::get)
                .collect(Collectors.toList());
        long stop = System.currentTimeMillis();
        System.out.println(output.size());
        System.out.println(stop - start);
    }

    private Optional<D> convert(A a){
        return Optional.ofNullable(a)
                .map(A::getB)
                .map(B::getC)
                .map(C::getD);
    }

Nie będzie to koniecznie najszybsze ale zwykle czas programisty jest wart więcej niż czas CPU.

edit: w sumie po ekstrakcji konwertera do osobnej metody wylgląda że działa przynajmniej tak szybko jak najlepsze z tych rozwiązań które sugerowałeś.

  • Rejestracja: dni
  • Ostatnio: dni
0

Ok, ale czy de facto kazdy map ja rowniez kazdy filter nie powoduje stworzenia i przeiterowania po streamie na nowo? Cos jakby wykonanie kilku pętli jedna po drugiej?

Shalom
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Space: the final frontier
  • Postów: 26433
0

Nie. Stream przelatujesz raz i aplikujesz wszystkie funkcje od razu na danym elemencie! Odpal sobie taki test:

Kopiuj
    @Test
    public void test4() {
        List<A> data = generateData(10);
        long start = System.currentTimeMillis();
        List<D> output = data.stream()
                .map(this::convert1)
                .map(this::convert2)
                .map(this::convert3)
                .filter(Optional::isPresent)
                .map(Optional::get)
                .collect(Collectors.toList());
        long stop = System.currentTimeMillis();
        System.out.println(output.size());
        System.out.println(stop - start);
    }

    private Optional<B> convert1(A a){
        System.out.println("A->B");
        return Optional.ofNullable(a)
                .map(A::getB);
    }

    private Optional<C> convert2(Optional<B> b){
        System.out.println("B->C");
        return b.map(B::getC);
    }

    private Optional<D> convert3(Optional<C> c){
        System.out.println("C->D");
        return c.map(C::getD);
    }

Zauważ ze dla każdego elementu masz ciąg

Kopiuj
A->B
B->C
C->D

i dopiero potem następny element streamu jest brany.

jarekr000000
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: U krasnoludów - pod górą
  • Postów: 4712
0
  1. Polecam problem rozwiązywać u źródła i nie mieć nulli. Same się te nulle wzięły w strukturach? (czasami, jeśli to z zewnętrznej biblioteki pochodzi to nic nie zrobimy, ale jeśli to nasze to odnullowujemy).

  2. Optional javowy to rak:

Kopiuj
private Optional<D> convert(A a){
        return Optional.ofNullable(a)
                .map(A::getB)
                .map(B::getC)
                .map(C::getD);
    }

Ten kod od @Shalom zadziała. Jednakowoż każdy, kto miał do czynienia z normalnymi Optionami, ze Scali, Maybe z Haskella, czy Option z VAVR (Java) - patrząc na to dostaje skrętu kiszek. (bo getC, getB zwracają potencjalne nulle).
Dlatego polecam przejście na Option z bilbioteki VAVR - szczególnie, że może pomóc w punkcie 1.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.