Programowanie funkcyjne - rlip/java GitHub Wiki

Interfejsy funkcyjne

to takie, które posiadają tylko 1 metodę, można go oznaczyć @FunctionalInterface i można do nich użyć lambdy:

        Movable m1 = new Movable() {
            @Override
            public int move(String direction) {
                return 11;
            }
        };
        Movable m2 = (String direction) -> {
            return 11;
        };
        Movable m3 = (String direction) -> 11; // jak jest 1 linijka to nie piszemy return
        Movable m4 =  direction -> 11; // jak jest 1 argument to nie trzeba podawać jego typu

Predicate

taki interfejs, który zamienia jakiś obiekt na booleana

        Predicate<Student> isAnia = new Predicate<Student>() {
            @Override
            public boolean test(Student student) {
                return student.getName().equals("Ania");
            }
        };
        Predicate<Student> isUnder30 = student ->  student.getAge() < 30;
        Predicate<Student> isAniaAndUnder30 = isAnia.and(isUnder30);
        Predicate<Student> isAniaORUnder30 = isAnia.or(isUnder30);
        Predicate<Student> isNotAnia = isAnia.negate();

Consumer

bierze jakiś obiekt, wykonuje jakąś operację i nic nie zwraca

        Consumer<Student> printStudentName = new Consumer<Student>() {
            @Override
            public void accept(Student student) {
                System.out.println(student.getName());
            }
        };

        Consumer<Student> printStudentName = (student) ->  System.out.println(student.getName());
        //można je też łączyć
        printStudentName.andThen(printStudentNameToUppercase))

Supplier

odwrotnie, nie bierze argumentów, coś zwraca

        Supplier<List<Student>> supplierPredefinedStudents = new Supplier<List<Student>>() {
            @Override
            public List<Student> get() {
                return createData();
            }
        };
        Supplier<List<Student>> supplierPredefinedStudents = () -> createData();
        Supplier<List<Student>> supplierPredefinedStudents = App::createData;

Function

bierze 1 typ obiektu, modyfikuje go i zwraca inny typ obiektu

        Function<Student,String> getStudentName = new Function<Student, String>() {
            @Override
            public String apply(Student student) {
                return student.getName();
            }
        };

        Function<Student,String> getStudentName2 = student -> student.getName();
        Function<Student,String> getStudentName3 = Student::getName;

BiFunction

bierze 2 typy i zwraca 3

        BiFunction<String, Student, Integer> bifunction = new BiFunction<String, Student, Integer>() {
            @Override
            public Integer apply(String s, Student student) {
                return null;
            }
        };

BinaryOperator

bierze 2 obiekty i zwraca 1

        BinaryOperator<Student> binaryOperator = new BinaryOperator<Student>() {
            @Override
            public Student apply(Student student, Student student2) {
                return null;
            }
        };

Interfejsy prymitywów

żeby nie operować na klasach

        IntPredicate intPredicate = new IntPredicate() {  //są też inne, a odwrotnie działające mają przedrostek "To"
            @Override
            public boolean test(int value) {
                return false;
            }
        };

Optional

opakowuje inny obiekt, które być, albo może go nie być

        Tworzymy:
         
         Optional.ofNullable(zmienna); // tak można stworzyć
         Optional.of(zmienna) // to gdy jesteśmy pewni, że wartość nie jest nullem
         Optional.empty() // pusty
         Optional.get() - pobiera wartość, zwróci wyjątek, jeśli będzie to null
         
        Potem:
        index.ifPresent(new Consumer<Index>() {
            @Override
            public void accept(Index index) {
                System.out.println(index.getName());
            }
        });
        
        Index aa = index.orElse(new Index("aa")); // zwraca index, a jeśli nie ma to nowy index
        Index aa2 = index.orElseGet(new Supplier<Index>() { // zwraca index, a jeśli go nie ma to zwraca suppliera
            @Override
            public Index get() {
                return null;
            }
        });
        index.filter(new Predicate<Index>() {
            @Override
            public boolean test(Index idx) {
                return idx.getName().equals("1234");
            }
        });

        index.filter(idx -> idx.getName().equals("1234")).ifPresent(new Consumer<Index>() {
            @Override
            public void accept(Index index) {
                System.out.println("Znalazłem");
            }
        });
        index.map(i -> i.getName()).filter(iName -> iName.equals("111")).ifPresent(indexNr -> System.out.println(indexNr));

Operacje:

Stream

Klasa, albo można też zrobić na kolekcjach, jednokrotnego użytku

        students.stream().filter(isAnia).map(getStudentName).forEach(print);
        // z kolekcji wybieramy te które spełniają filter, następnie 
        // mapujemy wybierając tylko imię i wyświetlamy imię
        TWORZENIE:
        Stream.of("A","B","C"); // wymienione
        students.stream(); // z kolekcji
        Stream.generate(() -> Math.random()).limit(10).forEach((v) -> System.out.println(v));
        Stream.generate(Math::random).limit(10).forEach(System.out::println); //losowy
        Stream.iterate(0, i->i+2).limit(20).forEach(System.out::println); // 20 liczb parzystych
         też streamy pymitywów, np InsStreem.rangeClosed(5,100).filter(i->i%2==0).forEach(System.out::println);

Filter

odfiltrowuje dane na streamach, przyjmuje predykat (zwracający booleanna), przepuszcza dane go spełniające

        Stream<Student> stream = createDataStream();
        stream
                .filter(student -> student.getAge() > 25)
                .filter((student) -> student.getName().equals("Ania"))
                .map(Student::getName)
                .forEach(print);

Map

przyjmuje funkcję (coś co na podstawie jednego obiektu zwraca inny) i mapuje jeden typ obiektu w drugi

        stream
                .map(student -> student.getIndex())
                .filter(optionalIndex -> optionalIndex.isPresent())
                .map(optionalInddex -> optionalInddex.get())
                .map(index -> index.getName())
                .forEach(System.out::println);

Są też mapy do prymitywów:

                createDataStream().map(Student::getAge).mapToInt(new ToIntFunction<Integer>() {
                    @Override
                    public int applyAsInt(Integer value) {
                        return value + 1;
                    }
                }).forEach(System.out::println);

FlatMap

W optionalach:

// Optional<Optional<String>> - nie zwiększa to bezpieczeństwa od nula
assertEquals(Optional.of(Optional.of("STRING")), 
  Optional
  .of("string")
  .map(s -> Optional.of("STRING")));

// więc można zrobić jako Optional<String>
assertEquals(Optional.of("STRING"), Optional
  .of("string")
  .flatMap(s -> Optional.of("STRING")));

W stream:

List<List<String>> list = Arrays.asList(
  Arrays.asList("a"),
  Arrays.asList("b"));
System.out.println(list);

// Otrzymamy list of lists [[a], [b]]. Z faltMap:

System.out.println(list
  .stream()
  .flatMap(Collection::stream)
  .collect(Collectors.toList()));

// Bedzie to [a, b]. 

forEach,findFirst,anyMatch,allMatch,noneMatch

forEach - przyjmuje consumera (coś co coś przyjmuje i nic nie zwraca)

findFirst() - zwróci pierwszy obiekt który dotrze do tego momentu w strumieniu

anyMatch(Predicate) - przyjmuje predykat i zwraca boolana czy jakiś spełnia ten warunek

allMatch(Predicate) - j.w. tylko sprawdza czy wszystkie obiekty ze strumienia spełniają warunek

noneMatch(Predicate) - j.w. tylko sprawdza czy wszystkie obiekty ze strumienia nie spełniają warunku

        Boolean atLeastOneAnia = createDataStream()
                .anyMatch(student -> student.getName().equals("Ania"));
        Boolean allAreAnia = createDataStream()
                .allMatch(student -> student.getName().equals("Ania"));
        Boolean thereIsNotAnyAnia = createDataStream()
                .noneMatch(student -> student.getName().equals("Ania"));

Reduce

Przyjmuje wartość początkową i binary operator (przyjmujący 2 wartości i zwracający jedną z nich) zwraca jeden łączny wynik Binaryoeratora dla wszystkich elementów

        Double reduce = Stream.generate(Math::random).limit(10)
        //  .reduce(0.0, Double::sum) // to samo co
            .reduce(0.0, new BinaryOperator<Double>() {
            @Override
            public Double apply(Double aDouble, Double aDouble2) {
                return aDouble + aDouble2;
            }
        });
        
        createDataStream().map(student -> student.getAge())
    //          .reduce( (a1, a2) -> a1 > a2 ? a1 : a2) // to samo co
                .reduce(Integer::max)
                .ifPresent(System.out::println);

Collect

zbiera dane i zwraca listę, mapę albo coś innego

        String agesString = createDataStream().map(student -> student.getAge()).map(age -> age.toString()).collect(Collectors.joining(", "));
        System.out.println(agesString);

        Map<Integer, List<Student>> studentsByAgeMap = createDataStream().collect(groupingBy(Student::getAge));
        studentsByAgeMap.forEach(new BiConsumer<Integer, List<Student>>() {
            @Override
            public void accept(Integer integer, List<Student> students) {
                System.out.println(integer.toString() + ":");
                students.stream().map(Student::getName).forEach(System.out::println);
            }
        });
    }

Przeksztaucanie mapy:

Map<String, Boolean> updatableAndAllowedCountries = cappingConfig.updatableCountries()
                .entrySet()
                .stream()
                .collect(Collectors.toMap(Entry::getKey, entry -> entry.getValue() && allowedCountries.contains(entry.getKey())));

limit,skip,distinct,sorted,count

limit(3) - ile elementów przepuścić

skip(3) - ile początkowych elementów pominąć

distinct() - przepuszcza tylko unikalne, bazuje na metodzie equals

sorted() - bez argumentu - sortuje wg. naturalnego porządku korzysta z metody compareTo

sorted(Comparator) - można też z własnym comparatorem

count() - ilość elementów w strumieniu

⚠️ **GitHub.com Fallback** ⚠️