Functional Programming - Basics

Overview

Functional programming basics on how to use java lambda and functional interfaces

Github: https://github.com/gitorko/project83

Functional Programming

Methods demonstrating use of functional programming

  1package com.demo.project83;
  2
  3import static java.util.Comparator.comparing;
  4import static java.util.function.Predicate.not;
  5import static java.util.stream.Collectors.collectingAndThen;
  6import static java.util.stream.Collectors.filtering;
  7import static java.util.stream.Collectors.groupingBy;
  8import static java.util.stream.Collectors.mapping;
  9import static java.util.stream.Collectors.maxBy;
 10import static java.util.stream.Collectors.toList;
 11import static java.util.stream.Collectors.toSet;
 12import static org.junit.jupiter.api.Assertions.assertEquals;
 13import static org.junit.jupiter.api.Assertions.assertThrows;
 14
 15import java.math.BigInteger;
 16import java.util.ArrayList;
 17import java.util.Arrays;
 18import java.util.HashMap;
 19import java.util.LinkedHashMap;
 20import java.util.List;
 21import java.util.Map;
 22import java.util.Optional;
 23import java.util.OptionalInt;
 24import java.util.Set;
 25import java.util.function.BiFunction;
 26import java.util.function.BinaryOperator;
 27import java.util.function.Consumer;
 28import java.util.function.Function;
 29import java.util.function.IntFunction;
 30import java.util.function.Predicate;
 31import java.util.function.Supplier;
 32import java.util.function.UnaryOperator;
 33import java.util.stream.Collectors;
 34import java.util.stream.IntStream;
 35import java.util.stream.Stream;
 36
 37import lombok.AllArgsConstructor;
 38import lombok.Builder;
 39import lombok.Data;
 40import org.junit.jupiter.api.Test;
 41
 42public class FunctionalTest {
 43
 44    List<Customer> customerList = List.of(
 45            Customer.builder().name("Peter Parker").city("london").age(32).build(),
 46            Customer.builder().name("Joe").city("paris").age(28).build(),
 47            Customer.builder().name("Marie").city("rome").age(31).build(),
 48            Customer.builder().name("Peter").city("rome").age(30).build(),
 49            Customer.builder().name("Raj").city("delhi").age(33).build(),
 50            Customer.builder().name("Simon").city("london").age(26).build()
 51    );
 52
 53    /**
 54     * ********************************************************************
 55     *  Difference between imperative vs functional style
 56     * ********************************************************************
 57     */
 58    @Test
 59    public void imperativeVsFunctional() {
 60
 61        // Group all person by city in pre Java 8 world
 62        Map<String, List<Customer>> personByCity1 = new HashMap<>();
 63        for (Customer p : customerList) {
 64            if (!personByCity1.containsKey(p.getCity())) {
 65                personByCity1.put(p.getCity(), new ArrayList<>());
 66            }
 67            personByCity1.get(p.getCity()).add(p);
 68        }
 69        System.out.println("Person grouped by cities : " + personByCity1);
 70        assertEquals(2, personByCity1.get("rome").size());
 71        System.out.println("---------------------------------------------------");
 72
 73        // Group objects in Java 8
 74        Map<String, List<Customer>> personByCity2 = customerList.stream()
 75                .collect(groupingBy(Customer::getCity));
 76        System.out.println("Person grouped by cities in Java 8: " + personByCity2);
 77        assertEquals(2, personByCity2.get("rome").size());
 78        System.out.println("---------------------------------------------------");
 79
 80        // Now let's group person by age
 81        Map<Integer, List<Customer>> personByAge = customerList.stream().collect(groupingBy(Customer::getAge));
 82        System.out.println("Person grouped by age in Java 8: " + personByAge);
 83        assertEquals(1, personByAge.get(32).size());
 84        System.out.println("---------------------------------------------------");
 85    }
 86
 87    /**
 88     * ********************************************************************
 89     *  Predicate <T> - takes T returns boolean
 90     * ********************************************************************
 91     */
 92    @Test
 93    public void predicateTest() {
 94        Predicate<String> strlen = (s) -> s.length() < 10;
 95        assertEquals(strlen.test("Apples"), true);
 96        System.out.println("---------------------------------------------------");
 97    }
 98
 99    /**
100     * ********************************************************************
101     *  Runnable - takes nothing returns nothing
102     * ********************************************************************
103     */
104    @Test
105    public void runnableTest() {
106        Runnable emptyConsumer = () -> System.out.println("run 1");
107        emptyConsumer.run();
108        System.out.println("---------------------------------------------------");
109    }
110
111    /**
112     * ********************************************************************
113     *  Consumer <T> - takes T returns nothing
114     * ********************************************************************
115     */
116    @Test
117    public void consumerTest() {
118        Consumer<String> consumerStr = (s) -> System.out.println(s.toUpperCase());
119        consumerStr.accept("peter parker");
120        System.out.println("---------------------------------------------------");
121
122        Consumer<String> hello = name -> System.out.println("Hello, " + name);
123        customerList.forEach(c -> hello.accept(c.getName()));
124        System.out.println("---------------------------------------------------");
125
126        //example of a lambda made from an instance method
127        Consumer<String> print = System.out::println;
128        print.accept("Sent directly from a lambda...");
129        System.out.println("---------------------------------------------------");
130
131        //As anonymous class, dont use this, provided for explanation only.
132        customerList.forEach(new Consumer<Customer>() {
133            @Override
134            public void accept(Customer customer) {
135                System.out.println("Hello " + customer.getName());
136            }
137        });
138        System.out.println("---------------------------------------------------");
139
140    }
141
142    /**
143     * ********************************************************************
144     *  Function <T,R> - takes T returns R
145     * ********************************************************************
146     */
147    @Test
148    public void functionTest() {
149        //Function example
150        Function<Integer, String> convertNumToString = (num) -> Integer.toString(num);
151        System.out.println("String value is : " + convertNumToString.apply(26));
152        System.out.println("---------------------------------------------------");
153
154        //lambdas made using a constructor
155        Function<String, BigInteger> newBigInt = BigInteger::new;
156        System.out.println("Number " + newBigInt.apply("123456789"));
157        System.out.println("---------------------------------------------------");
158    }
159
160    /**
161     * ********************************************************************
162     *  Supplier <T> - takes nothing returns T
163     * ********************************************************************
164     */
165    @Test
166    public void supplierTest() {
167        Supplier<String> s = () -> "Message from supplier";
168        System.out.println(s.get());
169        System.out.println("---------------------------------------------------");
170    }
171
172    /**
173     * ********************************************************************
174     *  BinaryOperator <T> - takes T,T returns T
175     * ********************************************************************
176     */
177    @Test
178    public void binaryOperatorTest() {
179        BinaryOperator<Integer> add = (a, b) -> a + b;
180        System.out.println("add 10 + 25: " + add.apply(10, 25));
181        System.out.println("---------------------------------------------------");
182    }
183
184    /**
185     * ********************************************************************
186     *  UnaryOperator <T> - takes T returns T
187     * ********************************************************************
188     */
189    @Test
190    public void unaryOperatorTest() {
191        UnaryOperator<String> str = (msg) -> msg.toUpperCase();
192        System.out.println(str.apply("hello, Joe"));
193        System.out.println("---------------------------------------------------");
194
195        //same example but using the static method concat
196        UnaryOperator<String> greeting = x -> "Hello, ".concat(x);
197        System.out.println(greeting.apply("Raj"));
198        System.out.println("---------------------------------------------------");
199
200        UnaryOperator<String> makeGreeting = "Hello, "::concat;
201        System.out.println(makeGreeting.apply("Peggy"));
202        System.out.println("---------------------------------------------------");
203    }
204
205    /**
206     * ********************************************************************
207     *  BiFunction <T,R,S> - takes T,R returns S
208     * ********************************************************************
209     */
210    @Test
211    public void biFunctionTest() {
212        BiFunction<Integer, Boolean, String> concat = (a, b) -> a.toString() + b.toString();
213        System.out.println(concat.apply(23, true));
214        System.out.println("---------------------------------------------------");
215    }
216
217    /**
218     * ********************************************************************
219     *  Custom Functional Interface
220     * ********************************************************************
221     */
222    @Test
223    public void functionalInterfaceTest() {
224        GreetingFunction greeting = message ->
225                System.out.println("Java Programming " + message);
226        greeting.sayMessage("is awesome");
227        System.out.println("---------------------------------------------------");
228    }
229
230    /**
231     * ********************************************************************
232     *  IntFunction<T> - takes integer returns T
233     * ********************************************************************
234     */
235    @Test
236    public void intFunctionTest() {
237        IntFunction<String> intToString = num -> Integer.toString(num);
238        System.out.println("String value of number: " + intToString.apply(123));
239        System.out.println("---------------------------------------------------");
240
241        //static method reference
242        IntFunction<String> intToString2 = Integer::toString;
243        System.out.println("String value of number: " + intToString2.apply(4567));
244        System.out.println("---------------------------------------------------");
245    }
246
247    /**
248     * ********************************************************************
249     *  Higher order function - pass functions as arguments
250     * ********************************************************************
251     */
252    @Test
253    public void higherOrderTest() {
254        //Function takes Integer,Predicate and returns Predicate
255        //Function<T,R>
256        Function<Integer, Predicate<String>> checkLength = (minLen) -> {
257            //predicate returned
258            return (str) -> str.length() > minLen;
259        };
260        List<String> collect = customerList.stream()
261                .map(Customer::getName)
262                .filter(checkLength.apply(4))
263                .collect(toList());
264        collect.forEach(System.out::println);
265        assertEquals(4, collect.size());
266        System.out.println("---------------------------------------------------");
267    }
268
269    /**
270     * ********************************************************************
271     *  collect - toList, joining, toCollection
272     * ********************************************************************
273     */
274    @Test
275    public void collectTest() {
276        //Collect customers who are below 30.
277        List<Customer> result = customerList.stream()
278                .filter(e -> e.getAge() < 30)
279                .collect(toList());
280        assertEquals(2, result.size());
281        System.out.println("---------------------------------------------------");
282
283        //get all employee names in List<String>
284        //Using toCollection you can specify the type
285        ArrayList<String> result2 = customerList.stream()
286                .map(e -> e.getName())
287                .collect(Collectors.toCollection(ArrayList::new));
288        assertEquals(6, result2.size());
289        System.out.println("---------------------------------------------------");
290
291        //Collect and join to single string separated by coma.
292        String customerString = customerList.stream()
293                .filter(e -> e.getAge() > 30)
294                .map(e -> e.getName())
295                .collect(Collectors.joining(", "));
296        System.out.println(customerString);
297        assertEquals("Peter Parker, Marie, Raj", customerString);
298        System.out.println("---------------------------------------------------");
299
300    }
301
302    /**
303     * ********************************************************************
304     *  collect - toMap
305     * ********************************************************************
306     */
307    @Test
308    void collectToMapTest() {
309
310        //Collect a map with name as key and age as value.
311        customerList.stream()
312                .filter(e -> e.getAge() > 30)
313                .collect(Collectors.toMap(Customer::getName, Customer::getAge))
314                .forEach((k, v) -> System.out.println(k + ":" + v));
315        System.out.println("---------------------------------------------------");
316
317        //Collect a map by name + city as key customer as value
318        customerList.stream()
319                .collect(Collectors.toMap(c -> c.getName() + "-" + c.getCity(), c -> c))
320                .forEach((k, v) -> System.out.println(k + ":" + v));
321        System.out.println("---------------------------------------------------");
322    }
323
324    /**
325     * ********************************************************************
326     *  collect - sort a Map by key or value
327     * ********************************************************************
328     */
329    @Test
330    public void sortMapTest() {
331        Map<String, Integer> map = new HashMap<>();
332        map.put("Niraj", 6);
333        map.put("Rahul", 43);
334        map.put("Ram", 44);
335        map.put("Sham", 33);
336        map.put("Pratik", 5);
337        map.put("Ashok", 5);
338
339        //Sort map by Value Ascending order
340        Map<String, Integer> sortedMapByValueAscending = map.entrySet()
341                .stream()
342                .sorted(Map.Entry.comparingByValue())
343                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
344        System.out.println(sortedMapByValueAscending);
345        System.out.println("---------------------------------------------------");
346
347        //Sort map by Value Descending order
348        Map<String, Integer> sortedMapByValueDescending = map.entrySet()
349                .stream()
350                .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
351                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
352        System.out.println(sortedMapByValueDescending);
353        System.out.println("---------------------------------------------------");
354
355        //Sort map by Key Ascending order
356        Map<String, Integer> sortedMapByKeyAscending
357                = map.entrySet()
358                .stream().sorted(Map.Entry.comparingByKey())
359                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
360        System.out.println(sortedMapByKeyAscending);
361        System.out.println("---------------------------------------------------");
362
363        //Sort map by Key Descending order
364        Map<String, Integer> sortedMapByKeyDescending
365                = map.entrySet()
366                .stream().sorted(Map.Entry.<String, Integer>comparingByKey().reversed())
367                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
368        System.out.println(sortedMapByKeyDescending);
369        System.out.println("---------------------------------------------------");
370    }
371
372    /**
373     * ********************************************************************
374     *  collect - summingInt, sum
375     * ********************************************************************
376     */
377    @Test
378    public void collectSumTest() {
379        //Sum all ages.
380        int total = customerList.stream()
381                .collect(Collectors.summingInt(Customer::getAge));
382        assertEquals(total, 180);
383        System.out.println("---------------------------------------------------");
384
385        int total2 = customerList.stream()
386                .mapToInt(Customer::getAge)
387                .sum();
388        assertEquals(total2, 180);
389        System.out.println("---------------------------------------------------");
390    }
391
392    /**
393     * ********************************************************************
394     *  sorted
395     * ********************************************************************
396     */
397    @Test
398    public void sortedTest() {
399
400        List<String> sortResult = customerList.stream()
401                .map(c -> c.getName())
402                .sorted((a, b) -> b.compareTo(a))
403                .collect(toList());
404        sortResult.forEach(System.out::println);
405
406        //Avoid using the below as it modifies the orignial list.
407        //Collections.sort(customerList, (a, b) -> b.getName().compareTo(a.getName()));
408
409        List<String> expectedResult = List.of("Simon", "Raj", "Peter Parker", "Peter", "Marie", "Joe");
410        assertEquals(expectedResult, sortResult);
411        System.out.println("---------------------------------------------------");
412
413    }
414
415    /**
416     * ********************************************************************
417     *  filter
418     * ********************************************************************
419     */
420    @Test
421    public void filterTest() {
422        customerList.stream()
423                .filter(customer -> {
424                    return customer.getName().startsWith("P"); //predicate
425                })
426                .forEach(System.out::println);
427        System.out.println("---------------------------------------------------");
428    }
429
430    /**
431     * ********************************************************************
432     *  findFirst, ifPresent
433     * ********************************************************************
434     */
435    @Test
436    public void findFirstTest() {
437        customerList
438                .stream()
439                .filter(customer -> customer.getName().startsWith("P"))
440                .findFirst()
441                .ifPresent(System.out::println);
442        System.out.println("---------------------------------------------------");
443    }
444
445    /**
446     * ********************************************************************
447     *  mapToInt, max, average, IntStream
448     * ********************************************************************
449     */
450    @Test
451    public void mapToIntTest() {
452        int sum = customerList.stream()
453                .mapToInt(Customer::getAge)
454                .sum();
455        System.out.println(sum);
456        System.out.println("---------------------------------------------------");
457
458        //primitive streams
459        IntStream.range(1, 4)
460                .forEach(System.out::println);
461        System.out.println("---------------------------------------------------");
462
463        //find the average of the numbers squared
464        Arrays.stream(new int[]{1, 2, 3, 4})
465                .map(n -> n * n)
466                .average()
467                .ifPresent(System.out::println);
468        System.out.println("---------------------------------------------------");
469
470        //map doubles to ints
471        Stream.of(1.5, 2.3, 3.7)
472                .mapToInt(Double::intValue)
473                .forEach(System.out::println);
474        System.out.println("---------------------------------------------------");
475
476        //max of age
477        OptionalInt max = customerList.stream()
478                .mapToInt(Customer::getAge)
479                .max();
480        System.out.println(max.getAsInt());
481        System.out.println("---------------------------------------------------");
482
483    }
484
485    /**
486     * ********************************************************************
487     *  thenComparing - double sort, sort on name, then sort on age
488     * ********************************************************************
489     */
490    @Test
491    public void doubleSortTest() {
492        //Sort customer by name and then by age.
493        customerList.stream()
494                .sorted(
495                        comparing(Customer::getName)
496                                .thenComparing(Customer::getAge)
497                )
498                .forEach(System.out::println);
499        System.out.println("---------------------------------------------------");
500    }
501
502    /**
503     * ********************************************************************
504     *  flatMap
505     * ********************************************************************
506     */
507    @Test
508    public void flatMapTest() {
509        //Get chars of all customer names.
510        Set<String> collect = customerList.stream()
511                .map(Customer::getName)
512                .flatMap(name -> Stream.of(name.split("")))
513                .collect(toSet());
514        System.out.println(collect);
515        System.out.println("---------------------------------------------------");
516
517        //one to many
518        List<Integer> nums = List.of(1, 2, 3);
519        List<Integer> collect2 = nums.stream()
520                .flatMap(e -> List.of(e, e + 1).stream())
521                .collect(toList());
522        System.out.println(collect2);
523        System.out.println("---------------------------------------------------");
524    }
525
526    /**
527     * ********************************************************************
528     *  collect - groupBy, mapping, filtering, counting
529     * ********************************************************************
530     */
531    @Test
532    public void groupByTest() {
533
534        //group by name and get list of customers with same name.
535        Map<String, List<Customer>> result1 = customerList.stream()
536                .collect(groupingBy(Customer::getName));
537        System.out.println(result1);
538        System.out.println("---------------------------------------------------");
539
540        //group by name and get list of ages if customer with same name.
541        Map<String, List<Integer>> result2 = customerList.stream()
542                .collect(
543                        groupingBy(Customer::getName,
544                                mapping(Customer::getAge, toList())));
545        System.out.println(result2);
546        System.out.println("---------------------------------------------------");
547
548        //Group by age, employees who name is greater than 4 chars.
549        Map<Integer, List<String>> result3 = customerList.stream()
550                .collect(
551                        groupingBy(Customer::getAge,
552                                mapping(
553                                        Customer::getName,
554                                        filtering(name -> name.length() > 4, toList())
555                                ))
556                );
557        System.out.println(result3);
558        System.out.println("---------------------------------------------------");
559
560        //group by age all customers name
561        Map<Integer, List<String>> result4 = customerList.stream()
562                .collect(
563                        groupingBy(Customer::getAge,
564                                mapping(Customer::getName, toList()))
565                );
566        System.out.println(result4);
567        System.out.println("---------------------------------------------------");
568
569        //count emp with same name.
570        Map<String, Long> result5 = customerList.stream()
571                .collect(groupingBy(Customer::getName, Collectors.counting()));
572        System.out.println(result5);
573        System.out.println("---------------------------------------------------");
574
575    }
576
577    /**
578     * ********************************************************************
579     *  maxBy - comparing, collectingAndThen
580     * ********************************************************************
581     */
582    @Test
583    public void maxByTest() {
584        //emp with max age
585        Optional<Customer> maxEmp = customerList.stream()
586                .collect(maxBy(comparing(Customer::getAge)));
587        System.out.println(maxEmp.get());
588        System.out.println("---------------------------------------------------");
589
590        //emp with max age and print name instead of emp.
591        String result = customerList.stream()
592                .collect(collectingAndThen(
593                        maxBy(comparing(Customer::getAge)),
594                        e -> e.map(Customer::getName).orElse("")
595                        )
596                );
597        System.out.println(result);
598        System.out.println("---------------------------------------------------");
599
600    }
601
602    /**
603     * ********************************************************************
604     *  collectingAndThen
605     * ********************************************************************
606     */
607    @Test
608    public void collectingAndThenTest() {
609        //convert long to int.
610        Map<String, Integer> result = customerList.stream()
611                .collect(groupingBy(Customer::getName,
612                        collectingAndThen(Collectors.counting(),
613                                Long::intValue
614                        )));
615        System.out.println(result);
616        System.out.println("---------------------------------------------------");
617    }
618
619    /**
620     * ********************************************************************
621     *  partitioningBy - same as groupBy but always partitions into 2 parts
622     * ********************************************************************
623     */
624    @Test
625    public void partitioningByTest() {
626        //2 list of even odd employees
627        Map<Boolean, List<Customer>> result = customerList.stream()
628                .collect(Collectors.partitioningBy(p -> p.getAge() % 2 == 0));
629        System.out.println(result);
630        System.out.println("---------------------------------------------------");
631    }
632
633    /**
634     * ********************************************************************
635     *  reduce
636     * ********************************************************************
637     */
638    @Test
639    public void reduceTest() {
640        List<Integer> numLst = Arrays.asList(1, 2, 3, 4, 5, 6);
641
642        //Sum of integer array. (both are param)
643        Integer reduce = numLst.stream().reduce(0, (total, val) -> Integer.sum(total, val));
644        System.out.println("reduce = " + reduce);
645        System.out.println("---------------------------------------------------");
646
647        reduce = numLst.stream().reduce(0, Integer::sum);
648        System.out.println("reduce = " + reduce);
649        System.out.println("---------------------------------------------------");
650
651        //Concat of string. (one is target, one is param)
652        String concat = numLst.stream().map(String::valueOf).reduce("", (carry, str) -> carry.concat(str));
653        System.out.println("concat = " + concat);
654        System.out.println("---------------------------------------------------");
655
656        concat = numLst.stream().map(String::valueOf).reduce("", String::concat);
657        System.out.println("concat = " + concat);
658        System.out.println("---------------------------------------------------");
659
660        Integer sum = numLst.stream().filter(e -> e % 2 == 0).map(e -> e * 2).reduce(0, Integer::sum);
661        System.out.println("sum = " + sum);
662        System.out.println("---------------------------------------------------");
663
664        Integer sum2 = numLst.stream().filter(e -> e % 2 == 0).mapToInt(e -> e * 2).sum();
665        System.out.println("sum2 = " + sum2);
666        System.out.println("---------------------------------------------------");
667
668        //Use reduce to collect to a list. Given only to explain, use toList in real world.
669        customerList.stream()
670                .filter(e -> e.getAge() > 30)
671                .map(e -> e.getName())
672                .map(String::toUpperCase)
673                .reduce(new ArrayList<String>(), (names, name) -> {
674                            names.add(name);
675                            return names;
676                        },
677                        (names1, names2) -> {
678                            names1.addAll(names2);
679                            return names1;
680                        }
681                ).forEach(System.out::println);
682        System.out.println("---------------------------------------------------");
683    }
684
685    /**
686     * ********************************************************************
687     *  ifPresent - findAny
688     * ********************************************************************
689     */
690    @Test
691    public void ifPresentTest() {
692        String input = "key:a,key:b,key:c,key:d";
693        Optional.ofNullable(input)
694                .ifPresent(in -> Arrays.stream(in.split(","))
695                        .map(String::toLowerCase)
696                        .peek(System.out::println)
697                        .filter(not(match -> (match.startsWith("key"))))
698                        .findAny()
699                        .ifPresent(match -> new RuntimeException("Pattern not valid!")));
700        System.out.println("---------------------------------------------------");
701
702        String input2 = "key:a,key:b,:c,key:d";
703        assertThrows(RuntimeException.class, () -> {
704            Optional.ofNullable(input2)
705                    .ifPresent(in -> Arrays.stream(in.split(","))
706                            .map(String::toLowerCase)
707                            .peek(System.out::println)
708                            .filter(not(match -> (match.startsWith("key"))))
709                            .findAny()
710                            .ifPresent(match -> {
711                                System.out.println("Here!");
712                                throw new RuntimeException("Pattern not valid!");
713                            }));
714        });
715        System.out.println("---------------------------------------------------");
716    }
717
718}
719
720@Builder
721@AllArgsConstructor
722@Data
723class Customer {
724    public String name;
725    public String city;
726    public Integer age;
727}
728
729@FunctionalInterface
730interface GreetingFunction {
731    void sayMessage(String message);
732}

References

Java Lambda

comments powered by Disqus