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