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
comments powered by Disqus