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

References

Java Lambda

comments powered by Disqus