KEMBAR78
Functional Java 8 - Introduction | PDF
Łukasz

Biały
Functional

Java 8



Introduction
Scala / Java Developer
lbialy@virtuslab.com
What is functional
programming?
What is functional
programming?
• usage of pure functions
What is functional
programming?
• usage of pure functions
• expressions instead of statements
What is functional
programming?
• usage of pure functions
• expressions instead of statements
• widespread immutability
What is functional
programming?
• usage of pure functions
• expressions instead of statements
• widespread immutability
• referential transparency
What is functional
programming?
• usage of pure functions
• expressions instead of statements
• widespread immutability
• referential transparency
• lazy evaluation
What is functional
programming?
• usage of pure functions
• expressions instead of statements
• widespread immutability
• referential transparency
• lazy evaluation
... and functional idioms!
What's the gain?
What's the gain?
• More predictable execution (same arguments
always yield the same results)
What's the gain?
• More predictable execution (same arguments
always yield the same results)
• Easier to reason about
What's the gain?
• More predictable execution (same arguments
always yield the same results)
• Easier to reason about
• Easier to enforce strict type correctness
What's the gain?
• More predictable execution (same arguments
always yield the same results)
• Easier to reason about
• Easier to enforce strict type correctness
• more precise checks in compile time
What's the gain?
• More predictable execution (same arguments
always yield the same results)
• Easier to reason about
• Easier to enforce strict type correctness
• more precise checks in compile time
• less bugs in runtime
How does this relate to Java at all?
How does this relate to Java at all?
java.lang.NullPointerException
at org.springsource.loaded.agent.SpringLoadedPreProcessor.tryToEnsureSystemClassesInitialized(SpringLoadedPreProcessor.java:362)
at org.springsource.loaded.agent.SpringLoadedPreProcessor.preProcess(SpringLoadedPreProcessor.java:128)
at org.springsource.loaded.agent.ClassPreProcessorAgentAdapter.transform(ClassPreProcessorAgentAdapter.java:102)
at sun.instrument.TransformerManager.transform(TransformerManager.java:188)
at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:424)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:800)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at org.apache.catalina.loader.WebappClassLoader.findClassInternal(WebappClassLoader.java:2818)
at org.apache.catalina.loader.WebappClassLoader.findClass(WebappClassLoader.java:1159)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1647)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1526)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:800)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at org.apache.catalina.loader.WebappClassLoader.findClassInternal(WebappClassLoader.java:2818)
at org.apache.catalina.loader.WebappClassLoader.findClass(WebappClassLoader.java:1159)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1647)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1526)
at org.springframework.util.ClassUtils.forName(ClassUtils.java:265)
at org.springframework.beans.factory.support.AbstractBeanDefinition.resolveBeanClass(AbstractBeanDefinition.java:419)
at org.springframework.beans.factory.support.AbstractBeanFactory.doResolveBeanClass(AbstractBeanFactory.java:1299)
at org.springframework.beans.factory.support.AbstractBeanFactory.access$000(AbstractBeanFactory.java:109)
at org.springframework.beans.factory.support.AbstractBeanFactory$4.run(AbstractBeanFactory.java:1265)
at org.springframework.beans.factory.support.AbstractBeanFactory$4.run(AbstractBeanFactory.java:1263)
at java.security.AccessController.doPrivileged(Native Method)
at org.springframework.beans.factory.support.AbstractBeanFactory.resolveBeanClass(AbstractBeanFactory.java:1263)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.predictBeanType(AbstractAutowireCapableBeanFactory.java:575)
at org.springframework.beans.factory.support.AbstractBeanFactory.isFactoryBean(AbstractBeanFactory.java:1347)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBeanNamesForType(DefaultListableBeanFactory.java:358)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:327)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:437)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:626)
at com.xxx.core.web.WebApplicationContext.invokeBeanFactoryPostProcessors(WebApplicationContext.java)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:461)
at com.xxx.core.web.WebApplicationContext.refresh(WebApplicationContext.java)
...
Most common errors in Java
Most common errors in Java
• Takipi monitoring service analysed error logs of over 1k
enterprise apps
Most common errors in Java
• Takipi monitoring service analysed error logs of over 1k
enterprise apps
• 29,965,285 exceptions over 30 days
Most common errors in Java
• Takipi monitoring service analysed error logs of over 1k
enterprise apps
• 29,965,285 exceptions over 30 days
• Just 10 exception types generated 97,3% of all errors
Most common errors in Java
• Takipi monitoring service analysed error logs of over 1k
enterprise apps
• 29,965,285 exceptions over 30 days
• Just 10 exception types generated 97,3% of all errors
• Two most common: NullPointerException &
NumberFormatException
Most common errors in Java
• Takipi monitoring service analysed error logs of over 1k
enterprise apps
• 29,965,285 exceptions over 30 days
• Just 10 exception types generated 97,3% of all errors
• Two most common: NullPointerException &
NumberFormatException
http://blog.takipi.com/we-crunched-1-billion-java-logged-errors-heres-what-causes-97-of-them/
http://blog.takipi.com/the-top-10-exceptions-types-in-production-java-applications-based-on-1b-events/
How can FP help?
How can FP help?
• Functional style makes implicit rules governing code
explicit, often as a part of type signatures
How can FP help?
• Functional style makes implicit rules governing code
explicit, often as a part of type signatures
• Functional style avoids constructs that are not statically
analysable like throwing exceptions, therefore allowing
compiler to do it's job better
How can FP help?
• Functional style makes implicit rules governing code
explicit, often as a part of type signatures
• Functional style avoids constructs that are not statically
analysable like throwing exceptions, therefore allowing
compiler to do it's job better
• In concurrent and parallel programming lack of mutable
state removes whole classes of programming errors for free
How can FP help?
• Functional style makes implicit rules governing code
explicit, often as a part of type signatures
• Functional style avoids constructs that are not statically
analysable like throwing exceptions, therefore allowing
compiler to do it's job better
• In concurrent and parallel programming lack of mutable
state removes whole classes of programming errors for free
• Yields an elegant and concise code :)
1 package com.example;
2
3 class CharacterOps {
4
5 static Character firstCharacter(String string) {
6 return (string.length() > 0) ? string.charAt(0) : null;
7 }
8
9 static Integer getAlphabetPosition(Character character) {
10 int code = (int) character;
11
12 if (isLowerCase(code))
13 return code - 96;
14 else if (isUpperCase(code))
15 return code - 64;
16 else
17 return null; // or throw exception?
18 }
19
20 private static boolean isLowerCase(int code) {
21 return code >= 97 && code <= 122;
22 }
23
24 private static boolean isUpperCase(int code) {
25 return code >= 65 && code <= 90;
26 }
27
28 }
Optionality

of values
1 package com.example;
2
3 class CharacterOps {
4
5 static Character firstCharacter(String string) {
6 return (string.length() > 0) ? string.charAt(0) : null;
7 }
8
9 static Integer getAlphabetPosition(Character character) {
10 int code = (int) character;
11
12 if (isLowerCase(code))
13 return code - 96;
14 else if (isUpperCase(code))
15 return code - 64;
16 else
17 return null; // or throw exception?
18 }
19
20 private static boolean isLowerCase(int code) {
21 return code >= 97 && code <= 122;
22 }
23
24 private static boolean isUpperCase(int code) {
25 return code >= 65 && code <= 90;
26 }
27
28 }
Optionality

of values
• optionality of value is not explicitly
known from method signature
1 package com.example;
2
3 class CharacterOps {
4
5 static Character firstCharacter(String string) {
6 return (string.length() > 0) ? string.charAt(0) : null;
7 }
8
9 static Integer getAlphabetPosition(Character character) {
10 int code = (int) character;
11
12 if (isLowerCase(code))
13 return code - 96;
14 else if (isUpperCase(code))
15 return code - 64;
16 else
17 return null; // or throw exception?
18 }
19
20 private static boolean isLowerCase(int code) {
21 return code >= 97 && code <= 122;
22 }
23
24 private static boolean isUpperCase(int code) {
25 return code >= 65 && code <= 90;
26 }
27
28 }
Optionality

of values
• optionality of value is not explicitly
known from method signature
1 package com.example;
2
3 class CharacterOps {
4
5 static Character firstCharacter(String string) {
6 return (string.length() > 0) ? string.charAt(0) : null;
7 }
8
9 static Integer getAlphabetPosition(Character character) {
10 int code = (int) character;
11
12 if (isLowerCase(code))
13 return code - 96;
14 else if (isUpperCase(code))
15 return code - 64;
16 else
17 return null; // or throw exception?
18 }
19
20 private static boolean isLowerCase(int code) {
21 return code >= 97 && code <= 122;
22 }
23
24 private static boolean isUpperCase(int code) {
25 return code >= 65 && code <= 90;
26 }
27
28 }
Optionality

of values
• optionality of value is not explicitly
known from method signature
1 package com.example;
2
3 class CharacterOps {
4
5 static Character firstCharacter(String string) {
6 return (string.length() > 0) ? string.charAt(0) : null;
7 }
8
9 static Integer getAlphabetPosition(Character character) {
10 int code = (int) character;
11
12 if (isLowerCase(code))
13 return code - 96;
14 else if (isUpperCase(code))
15 return code - 64;
16 else
17 return null; // or throw exception?
18 }
19
20 private static boolean isLowerCase(int code) {
21 return code >= 97 && code <= 122;
22 }
23
24 private static boolean isUpperCase(int code) {
25 return code >= 65 && code <= 90;
26 }
27
28 }
Optionality

of values
• optionality of value is not explicitly
known from method signature
• not feeling especially bright today?
have a NullPointerException in
runtime
1 package com.example;
2
3 class CharacterOps {
4
5 static Character firstCharacter(String string) {
6 return (string.length() > 0) ? string.charAt(0) : null;
7 }
8
9 static Integer getAlphabetPosition(Character character) {
10 int code = (int) character;
11
12 if (isLowerCase(code))
13 return code - 96;
14 else if (isUpperCase(code))
15 return code - 64;
16 else
17 return null; // or throw exception?
18 }
19
20 private static boolean isLowerCase(int code) {
21 return code >= 97 && code <= 122;
22 }
23
24 private static boolean isUpperCase(int code) {
25 return code >= 65 && code <= 90;
26 }
27
28 }
Optionality

of values
• optionality of value is not explicitly
known from method signature
• not feeling especially bright today?
have a NullPointerException in
runtime
1 package com.example;
2
3 class CharacterOps {
4
5 static Character firstCharacter(String string) {
6 return (string.length() > 0) ? string.charAt(0) : null;
7 }
8
9 static Integer getAlphabetPosition(Character character) {
10 int code = (int) character;
11
12 if (isLowerCase(code))
13 return code - 96;
14 else if (isUpperCase(code))
15 return code - 64;
16 else
17 return null; // or throw exception?
18 }
19
20 private static boolean isLowerCase(int code) {
21 return code >= 97 && code <= 122;
22 }
23
24 private static boolean isUpperCase(int code) {
25 return code >= 65 && code <= 90;
26 }
27
28 }
Optionality

of values
• optionality of value is not explicitly
known from method signature
• not feeling especially bright today?
have a NullPointerException in
runtime
1 package com.example;
2
3 class CharacterOps {
4
5 static Character firstCharacter(String string) {
6 return (string.length() > 0) ? string.charAt(0) : null;
7 }
8
9 static Integer getAlphabetPosition(Character character) {
10 int code = (int) character;
11
12 if (isLowerCase(code))
13 return code - 96;
14 else if (isUpperCase(code))
15 return code - 64;
16 else
17 return null; // or throw exception?
18 }
19
20 private static boolean isLowerCase(int code) {
21 return code >= 97 && code <= 122;
22 }
23
24 private static boolean isUpperCase(int code) {
25 return code >= 65 && code <= 90;
26 }
27
28 }
Optionality

of values
• optionality of value is not explicitly
known from method signature
• not feeling especially bright today?
have a NullPointerException in
runtime
• null is a subtype of every reference
type, so it will be propagated as 

a valid object value
1 package com.example;
2
3 class CharacterOps {
4
5 static Character firstCharacter(String string) {
6 return (string.length() > 0) ? string.charAt(0) : null;
7 }
8
9 static Integer getAlphabetPosition(Character character) {
10 int code = (int) character;
11
12 if (isLowerCase(code))
13 return code - 96;
14 else if (isUpperCase(code))
15 return code - 64;
16 else
17 return null; // or throw exception?
18 }
19
20 private static boolean isLowerCase(int code) {
21 return code >= 97 && code <= 122;
22 }
23
24 private static boolean isUpperCase(int code) {
25 return code >= 65 && code <= 90;
26 }
27
28 }
Optionality

of values
• optionality of value is not explicitly
known from method signature
• not feeling especially bright today?
have a NullPointerException in
runtime
• null is a subtype of every reference
type, so it will be propagated as 

a valid object value
• tracking null's origination point is so
much fun!
What we want:

composability
1 package com.example;
2
3 import org.junit.Test;
4
5 import static com.example.CharacterOps.*;
6
7 public class CharOpsTest {
8
9 @Test
10 public void testComposability() {
11
12 getAlphabetPosition(firstCharacter("cat"));
13
14 getAlphabetPosition(firstCharacter(""));
15
16 }
17
18 }
What we want:

composability
1 package com.example;
2
3 import org.junit.Test;
4
5 import static com.example.CharacterOps.*;
6
7 public class CharOpsTest {
8
9 @Test
10 public void testComposability() {
11
12 getAlphabetPosition(firstCharacter("cat"));
13
14 getAlphabetPosition(firstCharacter(""));
15
16 }
17
18 }
• feeding function a "cat" is
fine, we get 3 as result
What we want:

composability
1 package com.example;
2
3 import org.junit.Test;
4
5 import static com.example.CharacterOps.*;
6
7 public class CharOpsTest {
8
9 @Test
10 public void testComposability() {
11
12 getAlphabetPosition(firstCharacter("cat"));
13
14 getAlphabetPosition(firstCharacter(""));
15
16 }
17
18 }
• feeding function a "cat" is
fine, we get 3 as result
java.lang.NullPointerException
at com.example.CharacterOps.getAlphabetPosition(CharacterOps.java:10)
at com.example.CharOpsTest.testComposability(CharOpsTest.java:14)
What we want:

composability
1 package com.example;
2
3 import org.junit.Test;
4
5 import static com.example.CharacterOps.*;
6
7 public class CharOpsTest {
8
9 @Test
10 public void testComposability() {
11
12 getAlphabetPosition(firstCharacter("cat"));
13
14 getAlphabetPosition(firstCharacter(""));
15
16 }
17
18 }
• feeding function a "cat" is
fine, we get 3 as result
• doesn't work so well for 

an empty string though
java.lang.NullPointerException
at com.example.CharacterOps.getAlphabetPosition(CharacterOps.java:10)
at com.example.CharOpsTest.testComposability(CharOpsTest.java:14)
1 package com.example;
2
3 import org.junit.Test;
4
5 import static com.example.CharacterOps.*;
6
7 public class ActualCharOpsTest {
8
9 @Test
10 public void testNullChecks() {
11
12 Character firstChar = firstCharacter("cat");
13
14 if (null == firstChar) {
15 // oops. what now? exception? propagate null?
16 } else {
17 // will throw NPE on digit :(
18 Integer position = getAlphabetPosition(firstChar);
19 // ...
20 }
21
22 }
23
24 }
What we get:

explicit null checks
1 package com.example;
2
3 import org.junit.Test;
4
5 import static com.example.CharacterOps.*;
6
7 public class ActualCharOpsTest {
8
9 @Test
10 public void testNullChecks() {
11
12 Character firstChar = firstCharacter("cat");
13
14 if (null == firstChar) {
15 // oops. what now? exception? propagate null?
16 } else {
17 // will throw NPE on digit :(
18 Integer position = getAlphabetPosition(firstChar);
19 // ...
20 }
21
22 }
23
24 }
• you have to remember to
check for null explicitly or
face NPE hunting
What we get:

explicit null checks
1 package com.example;
2
3 import org.junit.Test;
4
5 import static com.example.CharacterOps.*;
6
7 public class ActualCharOpsTest {
8
9 @Test
10 public void testNullChecks() {
11
12 Character firstChar = firstCharacter("cat");
13
14 if (null == firstChar) {
15 // oops. what now? exception? propagate null?
16 } else {
17 // will throw NPE on digit :(
18 Integer position = getAlphabetPosition(firstChar);
19 // ...
20 }
21
22 }
23
24 }
• you have to remember to
check for null explicitly or
face NPE hunting
• methods don't compose
anymore
What we get:

explicit null checks
1 package com.example;
2
3 import org.junit.Test;
4
5 import static com.example.CharacterOps.*;
6
7 public class ActualCharOpsTest {
8
9 @Test
10 public void testNullChecks() {
11
12 Character firstChar = firstCharacter("cat");
13
14 if (null == firstChar) {
15 // oops. what now? exception? propagate null?
16 } else {
17 // will throw NPE on digit :(
18 Integer position = getAlphabetPosition(firstChar);
19 // ...
20 }
21
22 }
23
24 }
• you have to remember to
check for null explicitly or
face NPE hunting
• methods don't compose
anymore
• ugly :(
What we get:

explicit null checks
1 package com.example;
2
3 import java.util.Optional;
4
5 class SafeCharOps {
6
7 static Optional<Character> firstCharacter(
8 String string
9 ) {
10 return (string.length() > 0) ?
11 Optional.of(string.charAt(0)) :
12 Optional.empty();
13 }
14
15 static Optional<Integer> getAlphabetPosition(
16 Character character
17 ) {
18 int code = (int) character;
19
20 return isLowerCase(code) ? Optional.of(code - 96) :
21 isUpperCase(code) ? Optional.of(code - 64) :
22 Optional.empty();
23 }
24
25 private static boolean isLowerCase(int code) {
26 return code >= 97 && code <= 122;
27 }
28
29 private static boolean isUpperCase(int code) {
30 return code >= 65 && code <= 90;
31 }
32
33 }
Introducing 

Optional<T>
1 package com.example;
2
3 import java.util.Optional;
4
5 class SafeCharOps {
6
7 static Optional<Character> firstCharacter(
8 String string
9 ) {
10 return (string.length() > 0) ?
11 Optional.of(string.charAt(0)) :
12 Optional.empty();
13 }
14
15 static Optional<Integer> getAlphabetPosition(
16 Character character
17 ) {
18 int code = (int) character;
19
20 return isLowerCase(code) ? Optional.of(code - 96) :
21 isUpperCase(code) ? Optional.of(code - 64) :
22 Optional.empty();
23 }
24
25 private static boolean isLowerCase(int code) {
26 return code >= 97 && code <= 122;
27 }
28
29 private static boolean isUpperCase(int code) {
30 return code >= 65 && code <= 90;
31 }
32
33 }
• optionality of return value
is explicit in type signature
Introducing 

Optional<T>
1 package com.example;
2
3 import java.util.Optional;
4
5 class SafeCharOps {
6
7 static Optional<Character> firstCharacter(
8 String string
9 ) {
10 return (string.length() > 0) ?
11 Optional.of(string.charAt(0)) :
12 Optional.empty();
13 }
14
15 static Optional<Integer> getAlphabetPosition(
16 Character character
17 ) {
18 int code = (int) character;
19
20 return isLowerCase(code) ? Optional.of(code - 96) :
21 isUpperCase(code) ? Optional.of(code - 64) :
22 Optional.empty();
23 }
24
25 private static boolean isLowerCase(int code) {
26 return code >= 97 && code <= 122;
27 }
28
29 private static boolean isUpperCase(int code) {
30 return code >= 65 && code <= 90;
31 }
32
33 }
• optionality of return value
is explicit in type signature
• expressions used where
possible
Introducing 

Optional<T>
Composability
revisited
1 package com.example;
2
3 import org.junit.Test;
4
5 import static com.example.CharacterOps.*;
6
7 public class CharOpsTest {
8
9 @Test
10 public void testComposability() {
11
12 getAlphabetPosition(firstCharacter("cat"));
13
14 getAlphabetPosition(firstCharacter(""));
15
16 }
17
18 }
Composability
revisited
1 package com.example;
2
3 import org.junit.Test;
4
5 import static com.example.CharacterOps.*;
6
7 public class CharOpsTest {
8
9 @Test
10 public void testComposability() {
11
12 getAlphabetPosition(firstCharacter("cat"));
13
14 getAlphabetPosition(firstCharacter(""));
15
16 }
17
18 }
• doesn't compile :(
Composability
revisited
1 package com.example;
2
3 import org.junit.Test;
4
5 import static com.example.CharacterOps.*;
6
7 public class CharOpsTest {
8
9 @Test
10 public void testComposability() {
11
12 getAlphabetPosition(firstCharacter("cat"));
13
14 getAlphabetPosition(firstCharacter(""));
15
16 }
17
18 }
• doesn't compile :(
• getAlphabetPosition
accepts Character instances
Composability
revisited
1 package com.example;
2
3 import org.junit.Test;
4
5 import static com.example.CharacterOps.*;
6
7 public class CharOpsTest {
8
9 @Test
10 public void testComposability() {
11
12 getAlphabetPosition(firstCharacter("cat"));
13
14 getAlphabetPosition(firstCharacter(""));
15
16 }
17
18 }
• doesn't compile :(
• getAlphabetPosition
accepts Character instances
• we have
Optional<Character> from

firstCharacter call
Composability
revisited
1 package com.example;
2
3 import org.junit.Test;
4
5 import static com.example.CharacterOps.*;
6
7 public class CharOpsTest {
8
9 @Test
10 public void testComposability() {
11
12 getAlphabetPosition(firstCharacter("cat"));
13
14 getAlphabetPosition(firstCharacter(""));
15
16 }
17
18 }
• doesn't compile :(
• getAlphabetPosition
accepts Character instances
• we have
Optional<Character> from

firstCharacter call
• how to extract value in safe
manner?
1 package com.example;
2
3 import org.junit.Test;
4
5 import java.util.Optional;
6
7 import static com.example.SafeCharOps.*;
8
9 public class OptionalCharOpsTest {
10
11 @Test
12 public void testOptionalMethods() {
13
14 Optional<Character> firstChar =
15 firstCharacter("cat");
16
17 if (firstChar.isPresent()) {
18
19 Optional<Integer> alphabetPosition =
20 getAlphabetPosition(firstChar.get());
21
22 if (alphabetPosition.isPresent()) {
23 Integer position = alphabetPosition.get();
24
25 // ...
26 }
27
28 }
29
30 }
31
32 }
Imperative 

approach
1 package com.example;
2
3 import org.junit.Test;
4
5 import java.util.Optional;
6
7 import static com.example.SafeCharOps.*;
8
9 public class OptionalCharOpsTest {
10
11 @Test
12 public void testOptionalMethods() {
13
14 Optional<Character> firstChar =
15 firstCharacter("cat");
16
17 if (firstChar.isPresent()) {
18
19 Optional<Integer> alphabetPosition =
20 getAlphabetPosition(firstChar.get());
21
22 if (alphabetPosition.isPresent()) {
23 Integer position = alphabetPosition.get();
24
25 // ...
26 }
27
28 }
29
30 }
31
32 }
• we've gained explicit
information about
optionality
Imperative 

approach
1 package com.example;
2
3 import org.junit.Test;
4
5 import java.util.Optional;
6
7 import static com.example.SafeCharOps.*;
8
9 public class OptionalCharOpsTest {
10
11 @Test
12 public void testOptionalMethods() {
13
14 Optional<Character> firstChar =
15 firstCharacter("cat");
16
17 if (firstChar.isPresent()) {
18
19 Optional<Integer> alphabetPosition =
20 getAlphabetPosition(firstChar.get());
21
22 if (alphabetPosition.isPresent()) {
23 Integer position = alphabetPosition.get();
24
25 // ...
26 }
27
28 }
29
30 }
31
32 }
• we've gained explicit
information about
optionality
• still looks like null checks :(
Imperative 

approach
1 package com.example;
2
3 import org.junit.Test;
4
5 import java.util.Optional;
6
7 import static com.example.SafeCharOps.*;
8
9 public class OptionalCharOpsTest {
10
11 @Test
12 public void testOptionalMethods() {
13
14 Optional<Character> firstChar =
15 firstCharacter("cat");
16
17 if (firstChar.isPresent()) {
18
19 Optional<Integer> alphabetPosition =
20 getAlphabetPosition(firstChar.get());
21
22 if (alphabetPosition.isPresent()) {
23 Integer position = alphabetPosition.get();
24
25 // ...
26 }
27
28 }
29
30 }
31
32 }
• we've gained explicit
information about
optionality
• still looks like null checks :(
• still breaks composability :(
Imperative 

approach
Functional 

approach
1 package com.example;
2
3 import org.junit.Test;
4
5 import static com.example.SafeCharOps.*;
6
7 public class OptionalCharOpsTest {
8
9 @Test
10 public void testOptionalMethods() {
11
12 firstCharacter("cat")
13 .flatMap(SafeCharOps::getAlphabetPosition)
14 .ifPresent(value -> {
15 assert value == 3;
16 });
17
18 assert !firstCharacter("")
19 .flatMap(SafeCharOps::getAlphabetPosition)
20 .isPresent();
21 }
22
23 }
• no null checks!
Functional 

approach
1 package com.example;
2
3 import org.junit.Test;
4
5 import static com.example.SafeCharOps.*;
6
7 public class OptionalCharOpsTest {
8
9 @Test
10 public void testOptionalMethods() {
11
12 firstCharacter("cat")
13 .flatMap(SafeCharOps::getAlphabetPosition)
14 .ifPresent(value -> {
15 assert value == 3;
16 });
17
18 assert !firstCharacter("")
19 .flatMap(SafeCharOps::getAlphabetPosition)
20 .isPresent();
21 }
22
23 }
• no null checks!
• clean functional
composition
Functional 

approach
1 package com.example;
2
3 import org.junit.Test;
4
5 import static com.example.SafeCharOps.*;
6
7 public class OptionalCharOpsTest {
8
9 @Test
10 public void testOptionalMethods() {
11
12 firstCharacter("cat")
13 .flatMap(SafeCharOps::getAlphabetPosition)
14 .ifPresent(value -> {
15 assert value == 3;
16 });
17
18 assert !firstCharacter("")
19 .flatMap(SafeCharOps::getAlphabetPosition)
20 .isPresent();
21 }
22
23 }
• no null checks!
• clean functional
composition
• type safety, IDE helps at
every step as type inference
is easy
Functional 

approach
1 package com.example;
2
3 import org.junit.Test;
4
5 import static com.example.SafeCharOps.*;
6
7 public class OptionalCharOpsTest {
8
9 @Test
10 public void testOptionalMethods() {
11
12 firstCharacter("cat")
13 .flatMap(SafeCharOps::getAlphabetPosition)
14 .ifPresent(value -> {
15 assert value == 3;
16 });
17
18 assert !firstCharacter("")
19 .flatMap(SafeCharOps::getAlphabetPosition)
20 .isPresent();
21 }
22
23 }
• no null checks!
• clean functional
composition
• type safety, IDE helps at
every step as type inference
is easy
• easier to refactor
Functional 

approach
1 package com.example;
2
3 import org.junit.Test;
4
5 import static com.example.SafeCharOps.*;
6
7 public class OptionalCharOpsTest {
8
9 @Test
10 public void testOptionalMethods() {
11
12 firstCharacter("cat")
13 .flatMap(SafeCharOps::getAlphabetPosition)
14 .ifPresent(value -> {
15 assert value == 3;
16 });
17
18 assert !firstCharacter("")
19 .flatMap(SafeCharOps::getAlphabetPosition)
20 .isPresent();
21 }
22
23 }
• no null checks!
• clean functional
composition
• type safety, IDE helps at
every step as type inference
is easy
• easier to refactor
• elegant & succinct
Functional 

approach
1 package com.example;
2
3 import org.junit.Test;
4
5 import static com.example.SafeCharOps.*;
6
7 public class OptionalCharOpsTest {
8
9 @Test
10 public void testOptionalMethods() {
11
12 firstCharacter("cat")
13 .flatMap(SafeCharOps::getAlphabetPosition)
14 .ifPresent(value -> {
15 assert value == 3;
16 });
17
18 assert !firstCharacter("")
19 .flatMap(SafeCharOps::getAlphabetPosition)
20 .isPresent();
21 }
22
23 }
1 package com.example;
2
3 import java.util.List;
4 import java.util.stream.Collectors;
5
6 class StreamsAndErrors {
7
8 static List<Integer> toNumbers(List<String> strings) {
9 return strings.stream()
10 .map(Integer::parseInt)
11 .collect(Collectors.toList());
12 }
13
14 }
Streams API

revisited: errors
1 package com.example;
2
3 import org.junit.Test;
4
5 import java.util.List;
6
7 import static java.lang.System.out;
8 import static java.util.Arrays.asList;
9
10 public class StreamsTest {
11
12 @Test
13 public void testNumberConversion() {
14 List<String> strings = asList("1", "2", "3");
15 out.println(StreamsAndErrors.toNumbers(strings));
16 // prints '[1, 2, 3]'
17
18 List<String> itsaTrap = asList("1", "a", "3");
19 out.println(StreamsAndErrors.toNumbers(itsaTrap));
20 // NumberFormatException: For input string: "a"
21 }
22
23 }
1 package com.example;
2
3 import java.util.List;
4 import java.util.stream.Collectors;
5
6 class StreamsAndErrors {
7
8 static List<Integer> toNumbers(List<String> strings) {
9 return strings.stream()
10 .map(Integer::parseInt)
11 .collect(Collectors.toList());
12 }
13
14 }
• generic string to integer
conversion on collection level
Streams API

revisited: errors
1 package com.example;
2
3 import org.junit.Test;
4
5 import java.util.List;
6
7 import static java.lang.System.out;
8 import static java.util.Arrays.asList;
9
10 public class StreamsTest {
11
12 @Test
13 public void testNumberConversion() {
14 List<String> strings = asList("1", "2", "3");
15 out.println(StreamsAndErrors.toNumbers(strings));
16 // prints '[1, 2, 3]'
17
18 List<String> itsaTrap = asList("1", "a", "3");
19 out.println(StreamsAndErrors.toNumbers(itsaTrap));
20 // NumberFormatException: For input string: "a"
21 }
22
23 }
1 package com.example;
2
3 import java.util.List;
4 import java.util.stream.Collectors;
5
6 class StreamsAndErrors {
7
8 static List<Integer> toNumbers(List<String> strings) {
9 return strings.stream()
10 .map(Integer::parseInt)
11 .collect(Collectors.toList());
12 }
13
14 }
• generic string to integer
conversion on collection level
• type signature tells nothing
about possible of failure
Streams API

revisited: errors
1 package com.example;
2
3 import org.junit.Test;
4
5 import java.util.List;
6
7 import static java.lang.System.out;
8 import static java.util.Arrays.asList;
9
10 public class StreamsTest {
11
12 @Test
13 public void testNumberConversion() {
14 List<String> strings = asList("1", "2", "3");
15 out.println(StreamsAndErrors.toNumbers(strings));
16 // prints '[1, 2, 3]'
17
18 List<String> itsaTrap = asList("1", "a", "3");
19 out.println(StreamsAndErrors.toNumbers(itsaTrap));
20 // NumberFormatException: For input string: "a"
21 }
22
23 }
1 package com.example;
2
3 import java.util.List;
4 import java.util.stream.Collectors;
5
6 class StreamsAndErrors {
7
8 static List<Integer> toNumbers(List<String> strings) {
9 return strings.stream()
10 .map(Integer::parseInt)
11 .collect(Collectors.toList());
12 }
13
14 }
• generic string to integer
conversion on collection level
• type signature tells nothing
about possible of failure
• programmer knows (let's
hope so) about possibility of
failure and has to deal with it
explicitly
Streams API

revisited: errors
1 package com.example;
2
3 import org.junit.Test;
4
5 import java.util.List;
6
7 import static java.lang.System.out;
8 import static java.util.Arrays.asList;
9
10 public class StreamsTest {
11
12 @Test
13 public void testNumberConversion() {
14 List<String> strings = asList("1", "2", "3");
15 out.println(StreamsAndErrors.toNumbers(strings));
16 // prints '[1, 2, 3]'
17
18 List<String> itsaTrap = asList("1", "a", "3");
19 out.println(StreamsAndErrors.toNumbers(itsaTrap));
20 // NumberFormatException: For input string: "a"
21 }
22
23 }
1 package com.example;
2
3 import java.util.List;
4 import java.util.Objects;
5 import java.util.Optional;
6 import java.util.stream.Collectors;
7
8 class StreamsAndErrors {
9
10 static List<Integer> filterOut(List<String> strings) {
11 return strings.stream()
12 .map(s -> {
13 try {
14 return Integer.parseInt(s);
15 } catch (NumberFormatException nfe) {
16 return null;
17 }
18 })
19 .filter(Objects::nonNull)
20 .collect(Collectors.toList());
21 }
22
23 static Optional<List<Integer>> failOnFirst(
24 List<String> strings
25 ) {
26 try {
27 return Optional.of(
28 strings.stream()
29 .map(Integer::parseInt)
30 .collect(Collectors.toList())
31 );
32 } catch (NumberFormatException nfe) {
33 return Optional.empty();
34 }
35 }
36
37 }
Handling errors 

with try
1 package com.example;
2
3 import java.util.List;
4 import java.util.Objects;
5 import java.util.Optional;
6 import java.util.stream.Collectors;
7
8 class StreamsAndErrors {
9
10 static List<Integer> filterOut(List<String> strings) {
11 return strings.stream()
12 .map(s -> {
13 try {
14 return Integer.parseInt(s);
15 } catch (NumberFormatException nfe) {
16 return null;
17 }
18 })
19 .filter(Objects::nonNull)
20 .collect(Collectors.toList());
21 }
22
23 static Optional<List<Integer>> failOnFirst(
24 List<String> strings
25 ) {
26 try {
27 return Optional.of(
28 strings.stream()
29 .map(Integer::parseInt)
30 .collect(Collectors.toList())
31 );
32 } catch (NumberFormatException nfe) {
33 return Optional.empty();
34 }
35 }
36
37 }
• ughh... null again :(
Handling errors 

with try
1 package com.example;
2
3 import java.util.List;
4 import java.util.Objects;
5 import java.util.Optional;
6 import java.util.stream.Collectors;
7
8 class StreamsAndErrors {
9
10 static List<Integer> filterOut(List<String> strings) {
11 return strings.stream()
12 .map(s -> {
13 try {
14 return Integer.parseInt(s);
15 } catch (NumberFormatException nfe) {
16 return null;
17 }
18 })
19 .filter(Objects::nonNull)
20 .collect(Collectors.toList());
21 }
22
23 static Optional<List<Integer>> failOnFirst(
24 List<String> strings
25 ) {
26 try {
27 return Optional.of(
28 strings.stream()
29 .map(Integer::parseInt)
30 .collect(Collectors.toList())
31 );
32 } catch (NumberFormatException nfe) {
33 return Optional.empty();
34 }
35 }
36
37 }
• ughh... null again :(
• we have to filter nulls out lest
we create a disaster waiting to
happen
Handling errors 

with try
1 package com.example;
2
3 import java.util.List;
4 import java.util.Objects;
5 import java.util.Optional;
6 import java.util.stream.Collectors;
7
8 class StreamsAndErrors {
9
10 static List<Integer> filterOut(List<String> strings) {
11 return strings.stream()
12 .map(s -> {
13 try {
14 return Integer.parseInt(s);
15 } catch (NumberFormatException nfe) {
16 return null;
17 }
18 })
19 .filter(Objects::nonNull)
20 .collect(Collectors.toList());
21 }
22
23 static Optional<List<Integer>> failOnFirst(
24 List<String> strings
25 ) {
26 try {
27 return Optional.of(
28 strings.stream()
29 .map(Integer::parseInt)
30 .collect(Collectors.toList())
31 );
32 } catch (NumberFormatException nfe) {
33 return Optional.empty();
34 }
35 }
36
37 }
• ughh... null again :(
• we have to filter nulls out lest
we create a disaster waiting to
happen
• using Optional tells caller that
this method might yield value,
but doesn't say anything about
errors that may happen inside
Handling errors 

with try
Handling errors 

with Try
1 package com.example;
2
3 import javaslang.control.Try;
4
5 import java.util.List;
6 import java.util.stream.Collectors;
7
8 class StreamsAndErrors {
9
10 static List<Try<Integer>> withErrs(List<String> strings) {
11 return strings.stream()
12 .map(s -> Try.of(() -> Integer.parseInt(s)))
13 .collect(Collectors.toList());
14 }
15
16 static Try<List<Integer>> failOnFirst(
17 List<String> strings
18 ) {
19 return Try.of(() -> strings.stream()
20 .map(Integer::parseInt)
21 .collect(Collectors.toList()));
22 }
23
24 }
• type signature explicitly
informs us about the
possibility of error
Handling errors 

with Try
1 package com.example;
2
3 import javaslang.control.Try;
4
5 import java.util.List;
6 import java.util.stream.Collectors;
7
8 class StreamsAndErrors {
9
10 static List<Try<Integer>> withErrs(List<String> strings) {
11 return strings.stream()
12 .map(s -> Try.of(() -> Integer.parseInt(s)))
13 .collect(Collectors.toList());
14 }
15
16 static Try<List<Integer>> failOnFirst(
17 List<String> strings
18 ) {
19 return Try.of(() -> strings.stream()
20 .map(Integer::parseInt)
21 .collect(Collectors.toList()));
22 }
23
24 }
• type signature explicitly
informs us about the
possibility of error
• exceptions are caught
inside stream or in lambda
wrapping whole stream
processing
Handling errors 

with Try
1 package com.example;
2
3 import javaslang.control.Try;
4
5 import java.util.List;
6 import java.util.stream.Collectors;
7
8 class StreamsAndErrors {
9
10 static List<Try<Integer>> withErrs(List<String> strings) {
11 return strings.stream()
12 .map(s -> Try.of(() -> Integer.parseInt(s)))
13 .collect(Collectors.toList());
14 }
15
16 static Try<List<Integer>> failOnFirst(
17 List<String> strings
18 ) {
19 return Try.of(() -> strings.stream()
20 .map(Integer::parseInt)
21 .collect(Collectors.toList()));
22 }
23
24 }
1 package com.example;
2
3 import java.text.SimpleDateFormat;
4 import java.util.Date;
5 import java.util.List;
6 import java.util.stream.Collectors;
7
8 class StreamsAndCheckedExceptions {
9
10 private static SimpleDateFormat sdf =
11 new SimpleDateFormat("ddMMyyyy");
12
13 static List<Date> parseAll(List<String> strings) {
14 return strings.stream()
15 .map(sdf::parse) // won't compile!
16 .collect(Collectors.toList());
17 }
18
19 }
Checked exceptions 

& Java 8 Streams:

Rough edges
1 package com.example;
2
3 import java.text.SimpleDateFormat;
4 import java.util.Date;
5 import java.util.List;
6 import java.util.stream.Collectors;
7
8 class StreamsAndCheckedExceptions {
9
10 private static SimpleDateFormat sdf =
11 new SimpleDateFormat("ddMMyyyy");
12
13 static List<Date> parseAll(List<String> strings) {
14 return strings.stream()
15 .map(sdf::parse) // won't compile!
16 .collect(Collectors.toList());
17 }
18
19 }
• ParseException is checked,
which breaks lambda
expression
Checked exceptions 

& Java 8 Streams:

Rough edges
1 package com.example;
2
3 import java.text.SimpleDateFormat;
4 import java.util.Date;
5 import java.util.List;
6 import java.util.stream.Collectors;
7
8 class StreamsAndCheckedExceptions {
9
10 private static SimpleDateFormat sdf =
11 new SimpleDateFormat("ddMMyyyy");
12
13 static List<Date> parseAll(List<String> strings) {
14 return strings.stream()
15 .map(sdf::parse) // won't compile!
16 .collect(Collectors.toList());
17 }
18
19 }
• ParseException is checked,
which breaks lambda
expression
Checked exceptions 

& Java 8 Streams:

Rough edges
1 package com.example;
2
3 import java.text.SimpleDateFormat;
4 import java.util.Date;
5 import java.util.List;
6 import java.util.stream.Collectors;
7
8 class StreamsAndCheckedExceptions {
9
10 private static SimpleDateFormat sdf =
11 new SimpleDateFormat("ddMMyyyy");
12
13 static List<Date> parseAll(List<String> strings) {
14 return strings.stream()
15 .map(sdf::parse) // won't compile!
16 .collect(Collectors.toList());
17 }
18
19 }
• ParseException is checked,
which breaks lambda
expression
• solution using try-catch &
rethrow-as-unchecked block
is ugly and defeats the
purpose of lambdas
Checked exceptions 

& Java 8 Streams:

Rough edges
Checked exceptions 

& Java 8 Streams:

Try to the rescue
1 package com.example;
2
3 import javaslang.control.Try;
4
5 import java.text.SimpleDateFormat;
6 import java.util.Date;
7 import java.util.List;
8 import java.util.stream.Collectors;
9
10 class StreamsAndCheckedExceptions {
11
12 private static SimpleDateFormat sdf =
13 new SimpleDateFormat("ddMMyyyy");
14
15 static List<Try<Date>> parseAll(List<String> strings) {
16 return strings.stream()
17 .map(s -> Try.of(() -> sdf.parse(s)))
18 .collect(Collectors.toList());
19 }
20
21 }
1 package com.example;
2
3 import org.junit.Test;
4
5 import java.util.List;
6
7 import static java.lang.System.out;
8 import static java.util.Arrays.asList;
9
10 public class StreamsTest {
11
12 @Test
13 public void testNumberConversion() {
14 List<String> strings = asList("10042017", "23");
15 out.println(
16 StreamsAndCheckedExceptions.parseAll(strings)
17 );
18
19 // [
20 // Success(Mon Apr 10 00:00:00 CEST 2017),
21 // Failure(ParseException: Unparseable date: "23")
22 // ]
23 }
24
25 }
• problem of checked
exceptions solved

Checked exceptions 

& Java 8 Streams:

Try to the rescue
1 package com.example;
2
3 import javaslang.control.Try;
4
5 import java.text.SimpleDateFormat;
6 import java.util.Date;
7 import java.util.List;
8 import java.util.stream.Collectors;
9
10 class StreamsAndCheckedExceptions {
11
12 private static SimpleDateFormat sdf =
13 new SimpleDateFormat("ddMMyyyy");
14
15 static List<Try<Date>> parseAll(List<String> strings) {
16 return strings.stream()
17 .map(s -> Try.of(() -> sdf.parse(s)))
18 .collect(Collectors.toList());
19 }
20
21 }
1 package com.example;
2
3 import org.junit.Test;
4
5 import java.util.List;
6
7 import static java.lang.System.out;
8 import static java.util.Arrays.asList;
9
10 public class StreamsTest {
11
12 @Test
13 public void testNumberConversion() {
14 List<String> strings = asList("10042017", "23");
15 out.println(
16 StreamsAndCheckedExceptions.parseAll(strings)
17 );
18
19 // [
20 // Success(Mon Apr 10 00:00:00 CEST 2017),
21 // Failure(ParseException: Unparseable date: "23")
22 // ]
23 }
24
25 }
• problem of checked
exceptions solved

• we also gained explicit
information of possibility of
failure
Checked exceptions 

& Java 8 Streams:

Try to the rescue
1 package com.example;
2
3 import javaslang.control.Try;
4
5 import java.text.SimpleDateFormat;
6 import java.util.Date;
7 import java.util.List;
8 import java.util.stream.Collectors;
9
10 class StreamsAndCheckedExceptions {
11
12 private static SimpleDateFormat sdf =
13 new SimpleDateFormat("ddMMyyyy");
14
15 static List<Try<Date>> parseAll(List<String> strings) {
16 return strings.stream()
17 .map(s -> Try.of(() -> sdf.parse(s)))
18 .collect(Collectors.toList());
19 }
20
21 }
1 package com.example;
2
3 import org.junit.Test;
4
5 import java.util.List;
6
7 import static java.lang.System.out;
8 import static java.util.Arrays.asList;
9
10 public class StreamsTest {
11
12 @Test
13 public void testNumberConversion() {
14 List<String> strings = asList("10042017", "23");
15 out.println(
16 StreamsAndCheckedExceptions.parseAll(strings)
17 );
18
19 // [
20 // Success(Mon Apr 10 00:00:00 CEST 2017),
21 // Failure(ParseException: Unparseable date: "23")
22 // ]
23 }
24
25 }
Case for immutable data
Case for immutable data
• Mutable state makes it significantly harder to reason about a system due
to multiple code branches that can lead to mutation of state
Case for immutable data
• Mutable state makes it significantly harder to reason about a system due
to multiple code branches that can lead to mutation of state
• Shared mutable state in concurrent environment requires
synchronisation which is difficult and leads to subtle and 

hard to replicate bugs
Case for immutable data
• Mutable state makes it significantly harder to reason about a system due
to multiple code branches that can lead to mutation of state
• Shared mutable state in concurrent environment requires
synchronisation which is difficult and leads to subtle and 

hard to replicate bugs
• Immutable objects and persistent data structures have none of those
problems
Case for immutable data
• Mutable state makes it significantly harder to reason about a system due
to multiple code branches that can lead to mutation of state
• Shared mutable state in concurrent environment requires
synchronisation which is difficult and leads to subtle and 

hard to replicate bugs
• Immutable objects and persistent data structures have none of those
problems
• it's trivial to protect invariants once instance is immutable
Case for immutable data
• Mutable state makes it significantly harder to reason about a system due
to multiple code branches that can lead to mutation of state
• Shared mutable state in concurrent environment requires
synchronisation which is difficult and leads to subtle and 

hard to replicate bugs
• Immutable objects and persistent data structures have none of those
problems
• it's trivial to protect invariants once instance is immutable
• no need for synchronisation ever
Case for immutable data
• Mutable state makes it significantly harder to reason about a system due
to multiple code branches that can lead to mutation of state
• Shared mutable state in concurrent environment requires
synchronisation which is difficult and leads to subtle and 

hard to replicate bugs
• Immutable objects and persistent data structures have none of those
problems
• it's trivial to protect invariants once instance is immutable
• no need for synchronisation ever
... so how to do this in Java?
Immutable objects

in plain Java
1 package com.example;
2
3 import java.util.Set;
4
5 public class Office {
6
7 private final Set<Room> rooms;
8 private final Set<Employee> employees;
9
10 public Office(Set<Room> rooms, Set<Employee> employees) {
11 this.rooms = rooms;
12 this.employees = employees;
13 }
14
15 public Set<Room> getRooms() {
16 return rooms;
17 }
18
19 public Set<Employee> getEmployees() {
20 return employees;
21 }
22
23 }
Immutable objects

in plain Java
• lots of getter noise1 package com.example;
2
3 import java.util.Set;
4
5 public class Office {
6
7 private final Set<Room> rooms;
8 private final Set<Employee> employees;
9
10 public Office(Set<Room> rooms, Set<Employee> employees) {
11 this.rooms = rooms;
12 this.employees = employees;
13 }
14
15 public Set<Room> getRooms() {
16 return rooms;
17 }
18
19 public Set<Employee> getEmployees() {
20 return employees;
21 }
22
23 }
Immutable objects

in plain Java
• lots of getter noise
• if we want a builder, we have
to create one and make
constructor private
1 package com.example;
2
3 import java.util.Set;
4
5 public class Office {
6
7 private final Set<Room> rooms;
8 private final Set<Employee> employees;
9
10 public Office(Set<Room> rooms, Set<Employee> employees) {
11 this.rooms = rooms;
12 this.employees = employees;
13 }
14
15 public Set<Room> getRooms() {
16 return rooms;
17 }
18
19 public Set<Employee> getEmployees() {
20 return employees;
21 }
22
23 }
Immutable objects

in plain Java
• lots of getter noise
• if we want a builder, we have
to create one and make
constructor private
• optional field is painful as IDE
will cry that fields should
never have type Optional<T>
1 package com.example;
2
3 import java.util.Set;
4
5 public class Office {
6
7 private final Set<Room> rooms;
8 private final Set<Employee> employees;
9
10 public Office(Set<Room> rooms, Set<Employee> employees) {
11 this.rooms = rooms;
12 this.employees = employees;
13 }
14
15 public Set<Room> getRooms() {
16 return rooms;
17 }
18
19 public Set<Employee> getEmployees() {
20 return employees;
21 }
22
23 }
Immutable objects

in plain Java
• lots of getter noise
• if we want a builder, we have
to create one and make
constructor private
• optional field is painful as IDE
will cry that fields should
never have type Optional<T>
• how can we get a copy of
instance with only one field
modified?
1 package com.example;
2
3 import java.util.Set;
4
5 public class Office {
6
7 private final Set<Room> rooms;
8 private final Set<Employee> employees;
9
10 public Office(Set<Room> rooms, Set<Employee> employees) {
11 this.rooms = rooms;
12 this.employees = employees;
13 }
14
15 public Set<Room> getRooms() {
16 return rooms;
17 }
18
19 public Set<Employee> getEmployees() {
20 return employees;
21 }
22
23 }
Enter Immutables
1 package com.example;
2
3 import javaslang.collection.Set;
4 import org.immutables.value.Value;
5
6 @Value.Immutable
7 @Value.Style(typeImmutable = "*")
8 abstract class AbstractOffice {
9 abstract Set<Room> rooms();
10
11 abstract Set<Employee> employees();
12
13 abstract boolean open();
14 }
• that's all!
Enter Immutables
1 package com.example;
2
3 import javaslang.collection.Set;
4 import org.immutables.value.Value;
5
6 @Value.Immutable
7 @Value.Style(typeImmutable = "*")
8 abstract class AbstractOffice {
9 abstract Set<Room> rooms();
10
11 abstract Set<Employee> employees();
12
13 abstract boolean open();
14 }
• that's all!
• highly customisable code
generation
Enter Immutables
1 package com.example;
2
3 import javaslang.collection.Set;
4 import org.immutables.value.Value;
5
6 @Value.Immutable
7 @Value.Style(typeImmutable = "*")
8 abstract class AbstractOffice {
9 abstract Set<Room> rooms();
10
11 abstract Set<Employee> employees();
12
13 abstract boolean open();
14 }
• that's all!
• highly customisable code
generation
• handles Optional<T> values
correctly
Enter Immutables
1 package com.example;
2
3 import javaslang.collection.Set;
4 import org.immutables.value.Value;
5
6 @Value.Immutable
7 @Value.Style(typeImmutable = "*")
8 abstract class AbstractOffice {
9 abstract Set<Room> rooms();
10
11 abstract Set<Employee> employees();
12
13 abstract boolean open();
14 }
• that's all!
• highly customisable code
generation
• handles Optional<T> values
correctly
• handles default values
Enter Immutables
1 package com.example;
2
3 import javaslang.collection.Set;
4 import org.immutables.value.Value;
5
6 @Value.Immutable
7 @Value.Style(typeImmutable = "*")
8 abstract class AbstractOffice {
9 abstract Set<Room> rooms();
10
11 abstract Set<Employee> employees();
12
13 abstract boolean open();
14 }
• that's all!
• highly customisable code
generation
• handles Optional<T> values
correctly
• handles default values
• Jackson / Gson integration out-
of-the-box
Enter Immutables
1 package com.example;
2
3 import javaslang.collection.Set;
4 import org.immutables.value.Value;
5
6 @Value.Immutable
7 @Value.Style(typeImmutable = "*")
8 abstract class AbstractOffice {
9 abstract Set<Room> rooms();
10
11 abstract Set<Employee> employees();
12
13 abstract boolean open();
14 }
• that's all!
• highly customisable code
generation
• handles Optional<T> values
correctly
• handles default values
• Jackson / Gson integration out-
of-the-box
• provides with* methods
allowing painless modification
of immutable instances
Enter Immutables
1 package com.example;
2
3 import javaslang.collection.Set;
4 import org.immutables.value.Value;
5
6 @Value.Immutable
7 @Value.Style(typeImmutable = "*")
8 abstract class AbstractOffice {
9 abstract Set<Room> rooms();
10
11 abstract Set<Employee> employees();
12
13 abstract boolean open();
14 }
Immutables
in action
1 package com.example;
2
3 import javaslang.collection.HashSet;
4 import org.junit.Test;
5
6 public class OfficeTest {
7
8 @Test
9 public void muchSafeSuchImmutableVeryWow() {
10
11 HashSet<Employee> employees = HashSet.of(
12 Employee.of("Boss")
13 );
14
15 HashSet<Room> rooms = HashSet.of(
16 Room.of("Boss' office")
17 );
18
19 Office office = Office.builder()
20 .employees(employees)
21 .rooms(rooms)
22 .open(true)
23 .build();
24
25 System.out.println(office);
26 // Office{
27 // rooms=HashSet(Room{name=Boss' office}),
28 // employees=HashSet(Employee{name=Boss}),
29 // open=true}
30
31 Office officeWithoutEmployees = office
32 .withEmployees(HashSet.empty())
33 .withOpen(false);
34
35 System.out.println(officeWithoutEmployees);
36 // Office{
37 // rooms=HashSet(Room{name=Boss' office}),
38 // employees=HashSet(),
39 // open=false}
40 }
41 }
• named factory method can be
generated (of by default)
Immutables
in action
1 package com.example;
2
3 import javaslang.collection.HashSet;
4 import org.junit.Test;
5
6 public class OfficeTest {
7
8 @Test
9 public void muchSafeSuchImmutableVeryWow() {
10
11 HashSet<Employee> employees = HashSet.of(
12 Employee.of("Boss")
13 );
14
15 HashSet<Room> rooms = HashSet.of(
16 Room.of("Boss' office")
17 );
18
19 Office office = Office.builder()
20 .employees(employees)
21 .rooms(rooms)
22 .open(true)
23 .build();
24
25 System.out.println(office);
26 // Office{
27 // rooms=HashSet(Room{name=Boss' office}),
28 // employees=HashSet(Employee{name=Boss}),
29 // open=true}
30
31 Office officeWithoutEmployees = office
32 .withEmployees(HashSet.empty())
33 .withOpen(false);
34
35 System.out.println(officeWithoutEmployees);
36 // Office{
37 // rooms=HashSet(Room{name=Boss' office}),
38 // employees=HashSet(),
39 // open=false}
40 }
41 }
• named factory method can be
generated (of by default)
• builder class is generated by
default
Immutables
in action
1 package com.example;
2
3 import javaslang.collection.HashSet;
4 import org.junit.Test;
5
6 public class OfficeTest {
7
8 @Test
9 public void muchSafeSuchImmutableVeryWow() {
10
11 HashSet<Employee> employees = HashSet.of(
12 Employee.of("Boss")
13 );
14
15 HashSet<Room> rooms = HashSet.of(
16 Room.of("Boss' office")
17 );
18
19 Office office = Office.builder()
20 .employees(employees)
21 .rooms(rooms)
22 .open(true)
23 .build();
24
25 System.out.println(office);
26 // Office{
27 // rooms=HashSet(Room{name=Boss' office}),
28 // employees=HashSet(Employee{name=Boss}),
29 // open=true}
30
31 Office officeWithoutEmployees = office
32 .withEmployees(HashSet.empty())
33 .withOpen(false);
34
35 System.out.println(officeWithoutEmployees);
36 // Office{
37 // rooms=HashSet(Room{name=Boss' office}),
38 // employees=HashSet(),
39 // open=false}
40 }
41 }
• named factory method can be
generated (of by default)
• builder class is generated by
default
• toString, hashCode and equals
are generated using best practice
methods
Immutables
in action
1 package com.example;
2
3 import javaslang.collection.HashSet;
4 import org.junit.Test;
5
6 public class OfficeTest {
7
8 @Test
9 public void muchSafeSuchImmutableVeryWow() {
10
11 HashSet<Employee> employees = HashSet.of(
12 Employee.of("Boss")
13 );
14
15 HashSet<Room> rooms = HashSet.of(
16 Room.of("Boss' office")
17 );
18
19 Office office = Office.builder()
20 .employees(employees)
21 .rooms(rooms)
22 .open(true)
23 .build();
24
25 System.out.println(office);
26 // Office{
27 // rooms=HashSet(Room{name=Boss' office}),
28 // employees=HashSet(Employee{name=Boss}),
29 // open=true}
30
31 Office officeWithoutEmployees = office
32 .withEmployees(HashSet.empty())
33 .withOpen(false);
34
35 System.out.println(officeWithoutEmployees);
36 // Office{
37 // rooms=HashSet(Room{name=Boss' office}),
38 // employees=HashSet(),
39 // open=false}
40 }
41 }
• named factory method can be
generated (of by default)
• builder class is generated by
default
• toString, hashCode and equals
are generated using best practice
methods
• with(field) methods make
obtaining modified instances a
breeze!
Immutables
in action
1 package com.example;
2
3 import javaslang.collection.HashSet;
4 import org.junit.Test;
5
6 public class OfficeTest {
7
8 @Test
9 public void muchSafeSuchImmutableVeryWow() {
10
11 HashSet<Employee> employees = HashSet.of(
12 Employee.of("Boss")
13 );
14
15 HashSet<Room> rooms = HashSet.of(
16 Room.of("Boss' office")
17 );
18
19 Office office = Office.builder()
20 .employees(employees)
21 .rooms(rooms)
22 .open(true)
23 .build();
24
25 System.out.println(office);
26 // Office{
27 // rooms=HashSet(Room{name=Boss' office}),
28 // employees=HashSet(Employee{name=Boss}),
29 // open=true}
30
31 Office officeWithoutEmployees = office
32 .withEmployees(HashSet.empty())
33 .withOpen(false);
34
35 System.out.println(officeWithoutEmployees);
36 // Office{
37 // rooms=HashSet(Room{name=Boss' office}),
38 // employees=HashSet(),
39 // open=false}
40 }
41 }
• named factory method can be
generated (of by default)
• builder class is generated by
default
• toString, hashCode and equals
are generated using best practice
methods
• with(field) methods make
obtaining modified instances a
breeze!
• nulls are not allowed by default,
so fail early rule is enforced
Immutables
in action
1 package com.example;
2
3 import javaslang.collection.HashSet;
4 import org.junit.Test;
5
6 public class OfficeTest {
7
8 @Test
9 public void muchSafeSuchImmutableVeryWow() {
10
11 HashSet<Employee> employees = HashSet.of(
12 Employee.of("Boss")
13 );
14
15 HashSet<Room> rooms = HashSet.of(
16 Room.of("Boss' office")
17 );
18
19 Office office = Office.builder()
20 .employees(employees)
21 .rooms(rooms)
22 .open(true)
23 .build();
24
25 System.out.println(office);
26 // Office{
27 // rooms=HashSet(Room{name=Boss' office}),
28 // employees=HashSet(Employee{name=Boss}),
29 // open=true}
30
31 Office officeWithoutEmployees = office
32 .withEmployees(HashSet.empty())
33 .withOpen(false);
34
35 System.out.println(officeWithoutEmployees);
36 // Office{
37 // rooms=HashSet(Room{name=Boss' office}),
38 // employees=HashSet(),
39 // open=false}
40 }
41 }
About that 

javaslang.collection.HashSet
About that 

javaslang.collection.HashSet
• Javaslang provides immutable, persistent, purely functional
collections that match Java Collections
About that 

javaslang.collection.HashSet
• Javaslang provides immutable, persistent, purely functional
collections that match Java Collections
• Immutable, so every operation returns a new instance
About that 

javaslang.collection.HashSet
• Javaslang provides immutable, persistent, purely functional
collections that match Java Collections
• Immutable, so every operation returns a new instance
• Persistent, so creation of new instance means that data is
shared between instances of collection and only the reference
to modified element is replaced
About that 

javaslang.collection.HashSet
• Javaslang provides immutable, persistent, purely functional
collections that match Java Collections
• Immutable, so every operation returns a new instance
• Persistent, so creation of new instance means that data is
shared between instances of collection and only the reference
to modified element is replaced
• Functional, so all operations are referentially transparent

(at least as long as values stored in collection are immutable!)
Javaslang 

Collections APIs
• classic, imperative
approach with
StringBuilder: for loop
and append calls
Javaslang 

Collections APIs
1 String join(String... words) {
2 StringBuilder builder = new StringBuilder();
3 for(String s : words) {
4 if (builder.length() > 0) {
5 builder.append(", ");
6 }
7 builder.append(s);
8 }
9 return builder.toString();
10 }
• classic, imperative
approach with
StringBuilder: for loop
and append calls
Javaslang 

Collections APIs
1 String join(String... words) {
2 StringBuilder builder = new StringBuilder();
3 for(String s : words) {
4 if (builder.length() > 0) {
5 builder.append(", ");
6 }
7 builder.append(s);
8 }
9 return builder.toString();
10 }
• classic, imperative
approach with
StringBuilder: for loop
and append calls
• declarative use of List
methods and fold
(reduce to accumulator
value)
Javaslang 

Collections APIs
1 String join(String... words) {
2 StringBuilder builder = new StringBuilder();
3 for(String s : words) {
4 if (builder.length() > 0) {
5 builder.append(", ");
6 }
7 builder.append(s);
8 }
9 return builder.toString();
10 }
1 String join(String... words) {
2 return List.of(words)
3 .intersperse(", ")
4 .fold("", String::concat);
5 }
• classic, imperative
approach with
StringBuilder: for loop
and append calls
• declarative use of List
methods and fold
(reduce to accumulator
value)
Javaslang 

Collections APIs
1 String join(String... words) {
2 StringBuilder builder = new StringBuilder();
3 for(String s : words) {
4 if (builder.length() > 0) {
5 builder.append(", ");
6 }
7 builder.append(s);
8 }
9 return builder.toString();
10 }
1 String join(String... words) {
2 return List.of(words)
3 .intersperse(", ")
4 .fold("", String::concat);
5 }
• classic, imperative
approach with
StringBuilder: for loop
and append calls
• declarative use of List
methods and fold
(reduce to accumulator
value)
• a helper method!
Javaslang 

Collections APIs
1 String join(String... words) {
2 StringBuilder builder = new StringBuilder();
3 for(String s : words) {
4 if (builder.length() > 0) {
5 builder.append(", ");
6 }
7 builder.append(s);
8 }
9 return builder.toString();
10 }
1 String join(String... words) {
2 return List.of(words)
3 .intersperse(", ")
4 .fold("", String::concat);
5 }
1 List.of(words).mkString(", ");
• classic, imperative
approach with
StringBuilder: for loop
and append calls
• declarative use of List
methods and fold
(reduce to accumulator
value)
• a helper method!
Javaslang 

Collections APIs
1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.collection.HashMap;
5 import javaslang.collection.HashSet;
6 import javaslang.collection.Map;
7 import javaslang.collection.Set;
8
9 public class PhoneNumberData {
10
11 public static final Set<String> phoneNumbers =
12 HashSet.of(
13 "+48413422345",
14 "413572456",
15 "+48413456990",
16 "48225697246",
17 "+48224914634",
18 "48126434972",
19 "+48128275242"
20 );
21
22 public static final Map<String, String> areas =
23 HashMap.ofEntries(
24 Tuple.of("41", "Kielce"),
25 Tuple.of("22", "Warszawa"),
26 Tuple.of("12", "Kraków")
27 );
28
29 }
A more complex 

example...
1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.collection.HashMap;
5 import javaslang.collection.HashSet;
6 import javaslang.collection.Map;
7 import javaslang.collection.Set;
8
9 public class PhoneNumberData {
10
11 public static final Set<String> phoneNumbers =
12 HashSet.of(
13 "+48413422345",
14 "413572456",
15 "+48413456990",
16 "48225697246",
17 "+48224914634",
18 "48126434972",
19 "+48128275242"
20 );
21
22 public static final Map<String, String> areas =
23 HashMap.ofEntries(
24 Tuple.of("41", "Kielce"),
25 Tuple.of("22", "Warszawa"),
26 Tuple.of("12", "Kraków")
27 );
28
29 }
• phoneNumbers: phone
numbers in different
formats
A more complex 

example...
1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.collection.HashMap;
5 import javaslang.collection.HashSet;
6 import javaslang.collection.Map;
7 import javaslang.collection.Set;
8
9 public class PhoneNumberData {
10
11 public static final Set<String> phoneNumbers =
12 HashSet.of(
13 "+48413422345",
14 "413572456",
15 "+48413456990",
16 "48225697246",
17 "+48224914634",
18 "48126434972",
19 "+48128275242"
20 );
21
22 public static final Map<String, String> areas =
23 HashMap.ofEntries(
24 Tuple.of("41", "Kielce"),
25 Tuple.of("22", "Warszawa"),
26 Tuple.of("12", "Kraków")
27 );
28
29 }
• phoneNumbers: phone
numbers in different
formats
• areas: maps prefixes to area
names
A more complex 

example...
1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.collection.HashMap;
5 import javaslang.collection.HashSet;
6 import javaslang.collection.Map;
7 import javaslang.collection.Set;
8
9 public class PhoneNumberData {
10
11 public static final Set<String> phoneNumbers =
12 HashSet.of(
13 "+48413422345",
14 "413572456",
15 "+48413456990",
16 "48225697246",
17 "+48224914634",
18 "48126434972",
19 "+48128275242"
20 );
21
22 public static final Map<String, String> areas =
23 HashMap.ofEntries(
24 Tuple.of("41", "Kielce"),
25 Tuple.of("22", "Warszawa"),
26 Tuple.of("12", "Kraków")
27 );
28
29 }
• phoneNumbers: phone
numbers in different
formats
• areas: maps prefixes to area
names
• task: group numbers by
area name
A more complex 

example...
Processing!1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.Tuple2;
5 import javaslang.collection.HashMap;
6 import javaslang.collection.HashSet;
7 import javaslang.collection.Map;
8 import javaslang.collection.Set;
9 import org.junit.Test;
10
11 import static java.util.function.Function.identity;
12 import static com.example.PhoneNumberData.*;
13
14 public class GroupNumbersTest {
15
16 @Test
17 public void processNumbers() {
18 Map<String, Set<String>> map = phoneNumbers
19 .filter(num -> num.length() >= 9)
20 .map(number -> Tuple.of(number, number))
21 .map(tuple ->
22 tuple.map(
23 num -> num.replaceFirst("^+?48", ""),
24 identity()
25 )
26 )
27 .groupBy(tuple -> tuple._1().substring(0, 2))
28 .bimap(
29 key -> areas.get(key).getOrElse("Nieznany"),
30 value -> value.map(Tuple2::_2)
31 );
32
33 System.out.println(map);
34 }
35
36 }
Processing!1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.Tuple2;
5 import javaslang.collection.HashMap;
6 import javaslang.collection.HashSet;
7 import javaslang.collection.Map;
8 import javaslang.collection.Set;
9 import org.junit.Test;
10
11 import static java.util.function.Function.identity;
12 import static com.example.PhoneNumberData.*;
13
14 public class GroupNumbersTest {
15
16 @Test
17 public void processNumbers() {
18 Map<String, Set<String>> map = phoneNumbers
19 .filter(num -> num.length() >= 9)
20 .map(number -> Tuple.of(number, number))
21 .map(tuple ->
22 tuple.map(
23 num -> num.replaceFirst("^+?48", ""),
24 identity()
25 )
26 )
27 .groupBy(tuple -> tuple._1().substring(0, 2))
28 .bimap(
29 key -> areas.get(key).getOrElse("Nieznany"),
30 value -> value.map(Tuple2::_2)
31 );
32
33 System.out.println(map);
34 }
35
36 }
Processing!
• filter out numbers that are too
short
1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.Tuple2;
5 import javaslang.collection.HashMap;
6 import javaslang.collection.HashSet;
7 import javaslang.collection.Map;
8 import javaslang.collection.Set;
9 import org.junit.Test;
10
11 import static java.util.function.Function.identity;
12 import static com.example.PhoneNumberData.*;
13
14 public class GroupNumbersTest {
15
16 @Test
17 public void processNumbers() {
18 Map<String, Set<String>> map = phoneNumbers
19 .filter(num -> num.length() >= 9)
20 .map(number -> Tuple.of(number, number))
21 .map(tuple ->
22 tuple.map(
23 num -> num.replaceFirst("^+?48", ""),
24 identity()
25 )
26 )
27 .groupBy(tuple -> tuple._1().substring(0, 2))
28 .bimap(
29 key -> areas.get(key).getOrElse("Nieznany"),
30 value -> value.map(Tuple2::_2)
31 );
32
33 System.out.println(map);
34 }
35
36 }
Processing!
• filter out numbers that are too
short
1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.Tuple2;
5 import javaslang.collection.HashMap;
6 import javaslang.collection.HashSet;
7 import javaslang.collection.Map;
8 import javaslang.collection.Set;
9 import org.junit.Test;
10
11 import static java.util.function.Function.identity;
12 import static com.example.PhoneNumberData.*;
13
14 public class GroupNumbersTest {
15
16 @Test
17 public void processNumbers() {
18 Map<String, Set<String>> map = phoneNumbers
19 .filter(num -> num.length() >= 9)
20 .map(number -> Tuple.of(number, number))
21 .map(tuple ->
22 tuple.map(
23 num -> num.replaceFirst("^+?48", ""),
24 identity()
25 )
26 )
27 .groupBy(tuple -> tuple._1().substring(0, 2))
28 .bimap(
29 key -> areas.get(key).getOrElse("Nieznany"),
30 value -> value.map(Tuple2::_2)
31 );
32
33 System.out.println(map);
34 }
35
36 }
Processing!
• filter out numbers that are too
short
• we need to have original number
and still process it - Tuples!
1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.Tuple2;
5 import javaslang.collection.HashMap;
6 import javaslang.collection.HashSet;
7 import javaslang.collection.Map;
8 import javaslang.collection.Set;
9 import org.junit.Test;
10
11 import static java.util.function.Function.identity;
12 import static com.example.PhoneNumberData.*;
13
14 public class GroupNumbersTest {
15
16 @Test
17 public void processNumbers() {
18 Map<String, Set<String>> map = phoneNumbers
19 .filter(num -> num.length() >= 9)
20 .map(number -> Tuple.of(number, number))
21 .map(tuple ->
22 tuple.map(
23 num -> num.replaceFirst("^+?48", ""),
24 identity()
25 )
26 )
27 .groupBy(tuple -> tuple._1().substring(0, 2))
28 .bimap(
29 key -> areas.get(key).getOrElse("Nieznany"),
30 value -> value.map(Tuple2::_2)
31 );
32
33 System.out.println(map);
34 }
35
36 }
Processing!
• filter out numbers that are too
short
• we need to have original number
and still process it - Tuples!
1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.Tuple2;
5 import javaslang.collection.HashMap;
6 import javaslang.collection.HashSet;
7 import javaslang.collection.Map;
8 import javaslang.collection.Set;
9 import org.junit.Test;
10
11 import static java.util.function.Function.identity;
12 import static com.example.PhoneNumberData.*;
13
14 public class GroupNumbersTest {
15
16 @Test
17 public void processNumbers() {
18 Map<String, Set<String>> map = phoneNumbers
19 .filter(num -> num.length() >= 9)
20 .map(number -> Tuple.of(number, number))
21 .map(tuple ->
22 tuple.map(
23 num -> num.replaceFirst("^+?48", ""),
24 identity()
25 )
26 )
27 .groupBy(tuple -> tuple._1().substring(0, 2))
28 .bimap(
29 key -> areas.get(key).getOrElse("Nieznany"),
30 value -> value.map(Tuple2::_2)
31 );
32
33 System.out.println(map);
34 }
35
36 }
Processing!
• filter out numbers that are too
short
• we need to have original number
and still process it - Tuples!
• strip international code from
processed tuple entry, don't do
anything to original
1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.Tuple2;
5 import javaslang.collection.HashMap;
6 import javaslang.collection.HashSet;
7 import javaslang.collection.Map;
8 import javaslang.collection.Set;
9 import org.junit.Test;
10
11 import static java.util.function.Function.identity;
12 import static com.example.PhoneNumberData.*;
13
14 public class GroupNumbersTest {
15
16 @Test
17 public void processNumbers() {
18 Map<String, Set<String>> map = phoneNumbers
19 .filter(num -> num.length() >= 9)
20 .map(number -> Tuple.of(number, number))
21 .map(tuple ->
22 tuple.map(
23 num -> num.replaceFirst("^+?48", ""),
24 identity()
25 )
26 )
27 .groupBy(tuple -> tuple._1().substring(0, 2))
28 .bimap(
29 key -> areas.get(key).getOrElse("Nieznany"),
30 value -> value.map(Tuple2::_2)
31 );
32
33 System.out.println(map);
34 }
35
36 }
Processing!
• filter out numbers that are too
short
• we need to have original number
and still process it - Tuples!
• strip international code from
processed tuple entry, don't do
anything to original
1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.Tuple2;
5 import javaslang.collection.HashMap;
6 import javaslang.collection.HashSet;
7 import javaslang.collection.Map;
8 import javaslang.collection.Set;
9 import org.junit.Test;
10
11 import static java.util.function.Function.identity;
12 import static com.example.PhoneNumberData.*;
13
14 public class GroupNumbersTest {
15
16 @Test
17 public void processNumbers() {
18 Map<String, Set<String>> map = phoneNumbers
19 .filter(num -> num.length() >= 9)
20 .map(number -> Tuple.of(number, number))
21 .map(tuple ->
22 tuple.map(
23 num -> num.replaceFirst("^+?48", ""),
24 identity()
25 )
26 )
27 .groupBy(tuple -> tuple._1().substring(0, 2))
28 .bimap(
29 key -> areas.get(key).getOrElse("Nieznany"),
30 value -> value.map(Tuple2::_2)
31 );
32
33 System.out.println(map);
34 }
35
36 }
Processing!
• filter out numbers that are too
short
• we need to have original number
and still process it - Tuples!
• strip international code from
processed tuple entry, don't do
anything to original
• group tuples by area code
obtained from processed tuple
entry
1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.Tuple2;
5 import javaslang.collection.HashMap;
6 import javaslang.collection.HashSet;
7 import javaslang.collection.Map;
8 import javaslang.collection.Set;
9 import org.junit.Test;
10
11 import static java.util.function.Function.identity;
12 import static com.example.PhoneNumberData.*;
13
14 public class GroupNumbersTest {
15
16 @Test
17 public void processNumbers() {
18 Map<String, Set<String>> map = phoneNumbers
19 .filter(num -> num.length() >= 9)
20 .map(number -> Tuple.of(number, number))
21 .map(tuple ->
22 tuple.map(
23 num -> num.replaceFirst("^+?48", ""),
24 identity()
25 )
26 )
27 .groupBy(tuple -> tuple._1().substring(0, 2))
28 .bimap(
29 key -> areas.get(key).getOrElse("Nieznany"),
30 value -> value.map(Tuple2::_2)
31 );
32
33 System.out.println(map);
34 }
35
36 }
Processing!
• filter out numbers that are too
short
• we need to have original number
and still process it - Tuples!
• strip international code from
processed tuple entry, don't do
anything to original
• group tuples by area code
obtained from processed tuple
entry
1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.Tuple2;
5 import javaslang.collection.HashMap;
6 import javaslang.collection.HashSet;
7 import javaslang.collection.Map;
8 import javaslang.collection.Set;
9 import org.junit.Test;
10
11 import static java.util.function.Function.identity;
12 import static com.example.PhoneNumberData.*;
13
14 public class GroupNumbersTest {
15
16 @Test
17 public void processNumbers() {
18 Map<String, Set<String>> map = phoneNumbers
19 .filter(num -> num.length() >= 9)
20 .map(number -> Tuple.of(number, number))
21 .map(tuple ->
22 tuple.map(
23 num -> num.replaceFirst("^+?48", ""),
24 identity()
25 )
26 )
27 .groupBy(tuple -> tuple._1().substring(0, 2))
28 .bimap(
29 key -> areas.get(key).getOrElse("Nieznany"),
30 value -> value.map(Tuple2::_2)
31 );
32
33 System.out.println(map);
34 }
35
36 }
Processing!
• filter out numbers that are too
short
• we need to have original number
and still process it - Tuples!
• strip international code from
processed tuple entry, don't do
anything to original
• group tuples by area code
obtained from processed tuple
entry
• map keys and values to either
area name and set of original
phone numbers
1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.Tuple2;
5 import javaslang.collection.HashMap;
6 import javaslang.collection.HashSet;
7 import javaslang.collection.Map;
8 import javaslang.collection.Set;
9 import org.junit.Test;
10
11 import static java.util.function.Function.identity;
12 import static com.example.PhoneNumberData.*;
13
14 public class GroupNumbersTest {
15
16 @Test
17 public void processNumbers() {
18 Map<String, Set<String>> map = phoneNumbers
19 .filter(num -> num.length() >= 9)
20 .map(number -> Tuple.of(number, number))
21 .map(tuple ->
22 tuple.map(
23 num -> num.replaceFirst("^+?48", ""),
24 identity()
25 )
26 )
27 .groupBy(tuple -> tuple._1().substring(0, 2))
28 .bimap(
29 key -> areas.get(key).getOrElse("Nieznany"),
30 value -> value.map(Tuple2::_2)
31 );
32
33 System.out.println(map);
34 }
35
36 }
Results:
Results:
HashMap(

(Kielce, HashSet(+48413456990, +48413422345, 413572456)), 

(Kraków, HashSet(+48128275242, 48126434972)), 

(Warszawa, HashSet(+48224914634, 48225697246))

)
Results:
HashMap(

(Kielce, HashSet(+48413456990, +48413422345, 413572456)), 

(Kraków, HashSet(+48128275242, 48126434972)), 

(Warszawa, HashSet(+48224914634, 48225697246))

)
... neat!

Results:
HashMap(

(Kielce, HashSet(+48413456990, +48413422345, 413572456)), 

(Kraków, HashSet(+48128275242, 48126434972)), 

(Warszawa, HashSet(+48224914634, 48225697246))

)
... neat!

But what if that was an infinite stream of phone numbers?
1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.collection.HashMap;
5 import javaslang.collection.Map;
6 import rx.Observable;
7
8 public class StreamingPhoneNumberData {
9
10 public static final Observable<String> phoneNumbers =
11 Observable.from(new String[]{
12 "+48413422345",
13 "413572456",
14 "+48413456990",
15 "48225697246",
16 "+48224914634",
17 "48126434972",
18 "+48128275242"
19 });
20
21 public static final Map<String, String> areas =
22 HashMap.ofEntries(
23 Tuple.of("41", "Kielce"),
24 Tuple.of("22", "Warszawa"),
25 Tuple.of("12", "Kraków")
26 );
27
28 }
Event streams 

with RxJava
1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.collection.HashMap;
5 import javaslang.collection.Map;
6 import rx.Observable;
7
8 public class StreamingPhoneNumberData {
9
10 public static final Observable<String> phoneNumbers =
11 Observable.from(new String[]{
12 "+48413422345",
13 "413572456",
14 "+48413456990",
15 "48225697246",
16 "+48224914634",
17 "48126434972",
18 "+48128275242"
19 });
20
21 public static final Map<String, String> areas =
22 HashMap.ofEntries(
23 Tuple.of("41", "Kielce"),
24 Tuple.of("22", "Warszawa"),
25 Tuple.of("12", "Kraków")
26 );
27
28 }
Event streams 

with RxJava
1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.collection.HashMap;
5 import javaslang.collection.Map;
6 import rx.Observable;
7
8 public class StreamingPhoneNumberData {
9
10 public static final Observable<String> phoneNumbers =
11 Observable.from(new String[]{
12 "+48413422345",
13 "413572456",
14 "+48413456990",
15 "48225697246",
16 "+48224914634",
17 "48126434972",
18 "+48128275242"
19 });
20
21 public static final Map<String, String> areas =
22 HashMap.ofEntries(
23 Tuple.of("41", "Kielce"),
24 Tuple.of("22", "Warszawa"),
25 Tuple.of("12", "Kraków")
26 );
27
28 }
Event streams 

with RxJava
• Observable<T> is 

a 0 .. n collection of
events (potentially
infinite!)
1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.Tuple2;
5 import org.junit.Test;
6 import rx.observables.GroupedObservable;
7
8 import static com.example.PhoneNumberData.areas;
9 import static com.example.StreamingData.phoneNumbers;
10 import static java.lang.System.out;
11 import static java.util.function.Function.identity;
12
13 public class RxGroupPhoneNumbersTest {
14
15 @Test
16 public void processNumbers() {
17 phoneNumbers
18 .filter(num -> num.length() >= 9)
19 .map(number -> Tuple.of(number, number))
20 .map(tuple ->
21 tuple.map(
22 num -> num.replaceFirst("^+?48", ""),
23 identity()
24 )
25 )
26 .groupBy(tuple -> tuple._1().substring(0, 2))
27 .map(grouped ->
28 GroupedObservable.from(
29 areas.get(grouped.getKey())
30 .getOrElse("Unknown"),
31 grouped.map(Tuple2::_2)
32 )
33 )
34 .forEach(grouped -> grouped.forEach(number ->
35 out.println(
36 grouped.getKey() + ": " + number
37 )
38 )
39 );
40 }
41
42 }
Event streams 

with RxJava: 

reuse your lambdas!
1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.Tuple2;
5 import org.junit.Test;
6 import rx.observables.GroupedObservable;
7
8 import static com.example.PhoneNumberData.areas;
9 import static com.example.StreamingData.phoneNumbers;
10 import static java.lang.System.out;
11 import static java.util.function.Function.identity;
12
13 public class RxGroupPhoneNumbersTest {
14
15 @Test
16 public void processNumbers() {
17 phoneNumbers
18 .filter(num -> num.length() >= 9)
19 .map(number -> Tuple.of(number, number))
20 .map(tuple ->
21 tuple.map(
22 num -> num.replaceFirst("^+?48", ""),
23 identity()
24 )
25 )
26 .groupBy(tuple -> tuple._1().substring(0, 2))
27 .map(grouped ->
28 GroupedObservable.from(
29 areas.get(grouped.getKey())
30 .getOrElse("Unknown"),
31 grouped.map(Tuple2::_2)
32 )
33 )
34 .forEach(grouped -> grouped.forEach(number ->
35 out.println(
36 grouped.getKey() + ": " + number
37 )
38 )
39 );
40 }
41
42 }
Event streams 

with RxJava: 

reuse your lambdas!
1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.Tuple2;
5 import org.junit.Test;
6 import rx.observables.GroupedObservable;
7
8 import static com.example.PhoneNumberData.areas;
9 import static com.example.StreamingData.phoneNumbers;
10 import static java.lang.System.out;
11 import static java.util.function.Function.identity;
12
13 public class RxGroupPhoneNumbersTest {
14
15 @Test
16 public void processNumbers() {
17 phoneNumbers
18 .filter(num -> num.length() >= 9)
19 .map(number -> Tuple.of(number, number))
20 .map(tuple ->
21 tuple.map(
22 num -> num.replaceFirst("^+?48", ""),
23 identity()
24 )
25 )
26 .groupBy(tuple -> tuple._1().substring(0, 2))
27 .map(grouped ->
28 GroupedObservable.from(
29 areas.get(grouped.getKey())
30 .getOrElse("Unknown"),
31 grouped.map(Tuple2::_2)
32 )
33 )
34 .forEach(grouped -> grouped.forEach(number ->
35 out.println(
36 grouped.getKey() + ": " + number
37 )
38 )
39 );
40 }
41
42 }
Event streams 

with RxJava: 

reuse your lambdas!
• highlighted code is the same
as code used on Javaslang
collection!
1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.Tuple2;
5 import org.junit.Test;
6 import rx.observables.GroupedObservable;
7
8 import static com.example.PhoneNumberData.areas;
9 import static com.example.StreamingData.phoneNumbers;
10 import static java.lang.System.out;
11 import static java.util.function.Function.identity;
12
13 public class RxGroupPhoneNumbersTest {
14
15 @Test
16 public void processNumbers() {
17 phoneNumbers
18 .filter(num -> num.length() >= 9)
19 .map(number -> Tuple.of(number, number))
20 .map(tuple ->
21 tuple.map(
22 num -> num.replaceFirst("^+?48", ""),
23 identity()
24 )
25 )
26 .groupBy(tuple -> tuple._1().substring(0, 2))
27 .map(grouped ->
28 GroupedObservable.from(
29 areas.get(grouped.getKey())
30 .getOrElse("Unknown"),
31 grouped.map(Tuple2::_2)
32 )
33 )
34 .forEach(grouped -> grouped.forEach(number ->
35 out.println(
36 grouped.getKey() + ": " + number
37 )
38 )
39 );
40 }
41
42 }
Event streams 

with RxJava: 

reuse your lambdas!
• highlighted code is the same
as code used on Javaslang
collection!
• we are operating on
potentially infinite stream, so
groupBy returns Observable
of GroupedObservables - a
stream of streams of values
grouped by key!
1 package com.example;
2
3 import javaslang.Tuple;
4 import javaslang.Tuple2;
5 import org.junit.Test;
6 import rx.observables.GroupedObservable;
7
8 import static com.example.PhoneNumberData.areas;
9 import static com.example.StreamingData.phoneNumbers;
10 import static java.lang.System.out;
11 import static java.util.function.Function.identity;
12
13 public class RxGroupPhoneNumbersTest {
14
15 @Test
16 public void processNumbers() {
17 phoneNumbers
18 .filter(num -> num.length() >= 9)
19 .map(number -> Tuple.of(number, number))
20 .map(tuple ->
21 tuple.map(
22 num -> num.replaceFirst("^+?48", ""),
23 identity()
24 )
25 )
26 .groupBy(tuple -> tuple._1().substring(0, 2))
27 .map(grouped ->
28 GroupedObservable.from(
29 areas.get(grouped.getKey())
30 .getOrElse("Unknown"),
31 grouped.map(Tuple2::_2)
32 )
33 )
34 .forEach(grouped -> grouped.forEach(number ->
35 out.println(
36 grouped.getKey() + ": " + number
37 )
38 )
39 );
40 }
41
42 }
Event streams 

with RxJava: 

reuse your lambdas!
• highlighted code is the same
as code used on Javaslang
collection!
• we are operating on
potentially infinite stream, so
groupBy returns Observable
of GroupedObservables - a
stream of streams of values
grouped by key!
Use cases of

Reactive Extensions
Use cases of

Reactive Extensions
• Streams of events
Use cases of

Reactive Extensions
• Streams of events
• Parallel processing
Use cases of

Reactive Extensions
• Streams of events
• Parallel processing
• Asynchronous programming
Use cases of

Reactive Extensions
• Streams of events
• Parallel processing
• Asynchronous programming
• Bonus: functional error handling
Use cases of

Reactive Extensions
• Streams of events
• Parallel processing
• Asynchronous programming
• Bonus: functional error handling
• Bonus: reactive streams specification
Functional Programming in
Java: not needed, right?
Functional Programming in
Java: not needed, right?
• Microservices need linear scalability on instance level,
which is hard to achieve using thread-per-request
model, asynchronous frameworks offer this 

out-of-the-box
Functional Programming in
Java: not needed, right?
• Microservices need linear scalability on instance level,
which is hard to achieve using thread-per-request
model, asynchronous frameworks offer this 

out-of-the-box
• Reactive, event-driven programming model has already
been included into latest Java version - reactive streams
interfaces made it to JDK9!
Also - Spring 5:
1 @GetMapping("/accounts/{id}/alerts")
2 public Flux<Alert> getAccountAlerts(@PathVariable Long id) {
3
4 return this.repository.getAccount(id)
5 .flatMap(account ->
6 this.webClient
7 .perform(get("/alerts/{key}", account.getKey()))
8 .extract(bodyStream(Alert.class))
9 );
10 }
And Vert.x:
1 server.requestStream()
2 .toObservable()
3 .subscribe(request ->
4 request.toObservable()
5 .lift(RxHelper.unmarshaller(MyPojo.class))
6 .map(Marshaller::toMongoDocument)
7 .flatMap(jsonObject -> mongo.insertObservable(MY_POJOS, jsonObject))
8 .subscribe(
9 () -> request.response()
10 .setStatusCode(200)
11 .end(),
12 err -> request.response()
13 .setStatusCode(500)
14 .end()
15 )
16 );
Thanks for listening!

Functional Java 8 - Introduction

  • 1.
  • 2.
  • 3.
    What is functional programming? •usage of pure functions
  • 4.
    What is functional programming? •usage of pure functions • expressions instead of statements
  • 5.
    What is functional programming? •usage of pure functions • expressions instead of statements • widespread immutability
  • 6.
    What is functional programming? •usage of pure functions • expressions instead of statements • widespread immutability • referential transparency
  • 7.
    What is functional programming? •usage of pure functions • expressions instead of statements • widespread immutability • referential transparency • lazy evaluation
  • 8.
    What is functional programming? •usage of pure functions • expressions instead of statements • widespread immutability • referential transparency • lazy evaluation ... and functional idioms!
  • 9.
  • 10.
    What's the gain? •More predictable execution (same arguments always yield the same results)
  • 11.
    What's the gain? •More predictable execution (same arguments always yield the same results) • Easier to reason about
  • 12.
    What's the gain? •More predictable execution (same arguments always yield the same results) • Easier to reason about • Easier to enforce strict type correctness
  • 13.
    What's the gain? •More predictable execution (same arguments always yield the same results) • Easier to reason about • Easier to enforce strict type correctness • more precise checks in compile time
  • 14.
    What's the gain? •More predictable execution (same arguments always yield the same results) • Easier to reason about • Easier to enforce strict type correctness • more precise checks in compile time • less bugs in runtime
  • 15.
    How does thisrelate to Java at all?
  • 16.
    How does thisrelate to Java at all? java.lang.NullPointerException at org.springsource.loaded.agent.SpringLoadedPreProcessor.tryToEnsureSystemClassesInitialized(SpringLoadedPreProcessor.java:362) at org.springsource.loaded.agent.SpringLoadedPreProcessor.preProcess(SpringLoadedPreProcessor.java:128) at org.springsource.loaded.agent.ClassPreProcessorAgentAdapter.transform(ClassPreProcessorAgentAdapter.java:102) at sun.instrument.TransformerManager.transform(TransformerManager.java:188) at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:424) at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:800) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) at org.apache.catalina.loader.WebappClassLoader.findClassInternal(WebappClassLoader.java:2818) at org.apache.catalina.loader.WebappClassLoader.findClass(WebappClassLoader.java:1159) at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1647) at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1526) at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:800) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) at org.apache.catalina.loader.WebappClassLoader.findClassInternal(WebappClassLoader.java:2818) at org.apache.catalina.loader.WebappClassLoader.findClass(WebappClassLoader.java:1159) at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1647) at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1526) at org.springframework.util.ClassUtils.forName(ClassUtils.java:265) at org.springframework.beans.factory.support.AbstractBeanDefinition.resolveBeanClass(AbstractBeanDefinition.java:419) at org.springframework.beans.factory.support.AbstractBeanFactory.doResolveBeanClass(AbstractBeanFactory.java:1299) at org.springframework.beans.factory.support.AbstractBeanFactory.access$000(AbstractBeanFactory.java:109) at org.springframework.beans.factory.support.AbstractBeanFactory$4.run(AbstractBeanFactory.java:1265) at org.springframework.beans.factory.support.AbstractBeanFactory$4.run(AbstractBeanFactory.java:1263) at java.security.AccessController.doPrivileged(Native Method) at org.springframework.beans.factory.support.AbstractBeanFactory.resolveBeanClass(AbstractBeanFactory.java:1263) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.predictBeanType(AbstractAutowireCapableBeanFactory.java:575) at org.springframework.beans.factory.support.AbstractBeanFactory.isFactoryBean(AbstractBeanFactory.java:1347) at org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBeanNamesForType(DefaultListableBeanFactory.java:358) at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:327) at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:437) at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:626) at com.xxx.core.web.WebApplicationContext.invokeBeanFactoryPostProcessors(WebApplicationContext.java) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:461) at com.xxx.core.web.WebApplicationContext.refresh(WebApplicationContext.java) ...
  • 17.
  • 18.
    Most common errorsin Java • Takipi monitoring service analysed error logs of over 1k enterprise apps
  • 19.
    Most common errorsin Java • Takipi monitoring service analysed error logs of over 1k enterprise apps • 29,965,285 exceptions over 30 days
  • 20.
    Most common errorsin Java • Takipi monitoring service analysed error logs of over 1k enterprise apps • 29,965,285 exceptions over 30 days • Just 10 exception types generated 97,3% of all errors
  • 21.
    Most common errorsin Java • Takipi monitoring service analysed error logs of over 1k enterprise apps • 29,965,285 exceptions over 30 days • Just 10 exception types generated 97,3% of all errors • Two most common: NullPointerException & NumberFormatException
  • 22.
    Most common errorsin Java • Takipi monitoring service analysed error logs of over 1k enterprise apps • 29,965,285 exceptions over 30 days • Just 10 exception types generated 97,3% of all errors • Two most common: NullPointerException & NumberFormatException http://blog.takipi.com/we-crunched-1-billion-java-logged-errors-heres-what-causes-97-of-them/
  • 23.
  • 24.
  • 25.
    How can FPhelp? • Functional style makes implicit rules governing code explicit, often as a part of type signatures
  • 26.
    How can FPhelp? • Functional style makes implicit rules governing code explicit, often as a part of type signatures • Functional style avoids constructs that are not statically analysable like throwing exceptions, therefore allowing compiler to do it's job better
  • 27.
    How can FPhelp? • Functional style makes implicit rules governing code explicit, often as a part of type signatures • Functional style avoids constructs that are not statically analysable like throwing exceptions, therefore allowing compiler to do it's job better • In concurrent and parallel programming lack of mutable state removes whole classes of programming errors for free
  • 28.
    How can FPhelp? • Functional style makes implicit rules governing code explicit, often as a part of type signatures • Functional style avoids constructs that are not statically analysable like throwing exceptions, therefore allowing compiler to do it's job better • In concurrent and parallel programming lack of mutable state removes whole classes of programming errors for free • Yields an elegant and concise code :)
  • 29.
    1 package com.example; 2 3class CharacterOps { 4 5 static Character firstCharacter(String string) { 6 return (string.length() > 0) ? string.charAt(0) : null; 7 } 8 9 static Integer getAlphabetPosition(Character character) { 10 int code = (int) character; 11 12 if (isLowerCase(code)) 13 return code - 96; 14 else if (isUpperCase(code)) 15 return code - 64; 16 else 17 return null; // or throw exception? 18 } 19 20 private static boolean isLowerCase(int code) { 21 return code >= 97 && code <= 122; 22 } 23 24 private static boolean isUpperCase(int code) { 25 return code >= 65 && code <= 90; 26 } 27 28 } Optionality
 of values
  • 30.
    1 package com.example; 2 3class CharacterOps { 4 5 static Character firstCharacter(String string) { 6 return (string.length() > 0) ? string.charAt(0) : null; 7 } 8 9 static Integer getAlphabetPosition(Character character) { 10 int code = (int) character; 11 12 if (isLowerCase(code)) 13 return code - 96; 14 else if (isUpperCase(code)) 15 return code - 64; 16 else 17 return null; // or throw exception? 18 } 19 20 private static boolean isLowerCase(int code) { 21 return code >= 97 && code <= 122; 22 } 23 24 private static boolean isUpperCase(int code) { 25 return code >= 65 && code <= 90; 26 } 27 28 } Optionality
 of values • optionality of value is not explicitly known from method signature
  • 31.
    1 package com.example; 2 3class CharacterOps { 4 5 static Character firstCharacter(String string) { 6 return (string.length() > 0) ? string.charAt(0) : null; 7 } 8 9 static Integer getAlphabetPosition(Character character) { 10 int code = (int) character; 11 12 if (isLowerCase(code)) 13 return code - 96; 14 else if (isUpperCase(code)) 15 return code - 64; 16 else 17 return null; // or throw exception? 18 } 19 20 private static boolean isLowerCase(int code) { 21 return code >= 97 && code <= 122; 22 } 23 24 private static boolean isUpperCase(int code) { 25 return code >= 65 && code <= 90; 26 } 27 28 } Optionality
 of values • optionality of value is not explicitly known from method signature
  • 32.
    1 package com.example; 2 3class CharacterOps { 4 5 static Character firstCharacter(String string) { 6 return (string.length() > 0) ? string.charAt(0) : null; 7 } 8 9 static Integer getAlphabetPosition(Character character) { 10 int code = (int) character; 11 12 if (isLowerCase(code)) 13 return code - 96; 14 else if (isUpperCase(code)) 15 return code - 64; 16 else 17 return null; // or throw exception? 18 } 19 20 private static boolean isLowerCase(int code) { 21 return code >= 97 && code <= 122; 22 } 23 24 private static boolean isUpperCase(int code) { 25 return code >= 65 && code <= 90; 26 } 27 28 } Optionality
 of values • optionality of value is not explicitly known from method signature
  • 33.
    1 package com.example; 2 3class CharacterOps { 4 5 static Character firstCharacter(String string) { 6 return (string.length() > 0) ? string.charAt(0) : null; 7 } 8 9 static Integer getAlphabetPosition(Character character) { 10 int code = (int) character; 11 12 if (isLowerCase(code)) 13 return code - 96; 14 else if (isUpperCase(code)) 15 return code - 64; 16 else 17 return null; // or throw exception? 18 } 19 20 private static boolean isLowerCase(int code) { 21 return code >= 97 && code <= 122; 22 } 23 24 private static boolean isUpperCase(int code) { 25 return code >= 65 && code <= 90; 26 } 27 28 } Optionality
 of values • optionality of value is not explicitly known from method signature • not feeling especially bright today? have a NullPointerException in runtime
  • 34.
    1 package com.example; 2 3class CharacterOps { 4 5 static Character firstCharacter(String string) { 6 return (string.length() > 0) ? string.charAt(0) : null; 7 } 8 9 static Integer getAlphabetPosition(Character character) { 10 int code = (int) character; 11 12 if (isLowerCase(code)) 13 return code - 96; 14 else if (isUpperCase(code)) 15 return code - 64; 16 else 17 return null; // or throw exception? 18 } 19 20 private static boolean isLowerCase(int code) { 21 return code >= 97 && code <= 122; 22 } 23 24 private static boolean isUpperCase(int code) { 25 return code >= 65 && code <= 90; 26 } 27 28 } Optionality
 of values • optionality of value is not explicitly known from method signature • not feeling especially bright today? have a NullPointerException in runtime
  • 35.
    1 package com.example; 2 3class CharacterOps { 4 5 static Character firstCharacter(String string) { 6 return (string.length() > 0) ? string.charAt(0) : null; 7 } 8 9 static Integer getAlphabetPosition(Character character) { 10 int code = (int) character; 11 12 if (isLowerCase(code)) 13 return code - 96; 14 else if (isUpperCase(code)) 15 return code - 64; 16 else 17 return null; // or throw exception? 18 } 19 20 private static boolean isLowerCase(int code) { 21 return code >= 97 && code <= 122; 22 } 23 24 private static boolean isUpperCase(int code) { 25 return code >= 65 && code <= 90; 26 } 27 28 } Optionality
 of values • optionality of value is not explicitly known from method signature • not feeling especially bright today? have a NullPointerException in runtime
  • 36.
    1 package com.example; 2 3class CharacterOps { 4 5 static Character firstCharacter(String string) { 6 return (string.length() > 0) ? string.charAt(0) : null; 7 } 8 9 static Integer getAlphabetPosition(Character character) { 10 int code = (int) character; 11 12 if (isLowerCase(code)) 13 return code - 96; 14 else if (isUpperCase(code)) 15 return code - 64; 16 else 17 return null; // or throw exception? 18 } 19 20 private static boolean isLowerCase(int code) { 21 return code >= 97 && code <= 122; 22 } 23 24 private static boolean isUpperCase(int code) { 25 return code >= 65 && code <= 90; 26 } 27 28 } Optionality
 of values • optionality of value is not explicitly known from method signature • not feeling especially bright today? have a NullPointerException in runtime • null is a subtype of every reference type, so it will be propagated as 
 a valid object value
  • 37.
    1 package com.example; 2 3class CharacterOps { 4 5 static Character firstCharacter(String string) { 6 return (string.length() > 0) ? string.charAt(0) : null; 7 } 8 9 static Integer getAlphabetPosition(Character character) { 10 int code = (int) character; 11 12 if (isLowerCase(code)) 13 return code - 96; 14 else if (isUpperCase(code)) 15 return code - 64; 16 else 17 return null; // or throw exception? 18 } 19 20 private static boolean isLowerCase(int code) { 21 return code >= 97 && code <= 122; 22 } 23 24 private static boolean isUpperCase(int code) { 25 return code >= 65 && code <= 90; 26 } 27 28 } Optionality
 of values • optionality of value is not explicitly known from method signature • not feeling especially bright today? have a NullPointerException in runtime • null is a subtype of every reference type, so it will be propagated as 
 a valid object value • tracking null's origination point is so much fun!
  • 38.
    What we want:
 composability 1package com.example; 2 3 import org.junit.Test; 4 5 import static com.example.CharacterOps.*; 6 7 public class CharOpsTest { 8 9 @Test 10 public void testComposability() { 11 12 getAlphabetPosition(firstCharacter("cat")); 13 14 getAlphabetPosition(firstCharacter("")); 15 16 } 17 18 }
  • 39.
    What we want:
 composability 1package com.example; 2 3 import org.junit.Test; 4 5 import static com.example.CharacterOps.*; 6 7 public class CharOpsTest { 8 9 @Test 10 public void testComposability() { 11 12 getAlphabetPosition(firstCharacter("cat")); 13 14 getAlphabetPosition(firstCharacter("")); 15 16 } 17 18 } • feeding function a "cat" is fine, we get 3 as result
  • 40.
    What we want:
 composability 1package com.example; 2 3 import org.junit.Test; 4 5 import static com.example.CharacterOps.*; 6 7 public class CharOpsTest { 8 9 @Test 10 public void testComposability() { 11 12 getAlphabetPosition(firstCharacter("cat")); 13 14 getAlphabetPosition(firstCharacter("")); 15 16 } 17 18 } • feeding function a "cat" is fine, we get 3 as result java.lang.NullPointerException at com.example.CharacterOps.getAlphabetPosition(CharacterOps.java:10) at com.example.CharOpsTest.testComposability(CharOpsTest.java:14)
  • 41.
    What we want:
 composability 1package com.example; 2 3 import org.junit.Test; 4 5 import static com.example.CharacterOps.*; 6 7 public class CharOpsTest { 8 9 @Test 10 public void testComposability() { 11 12 getAlphabetPosition(firstCharacter("cat")); 13 14 getAlphabetPosition(firstCharacter("")); 15 16 } 17 18 } • feeding function a "cat" is fine, we get 3 as result • doesn't work so well for 
 an empty string though java.lang.NullPointerException at com.example.CharacterOps.getAlphabetPosition(CharacterOps.java:10) at com.example.CharOpsTest.testComposability(CharOpsTest.java:14)
  • 42.
    1 package com.example; 2 3import org.junit.Test; 4 5 import static com.example.CharacterOps.*; 6 7 public class ActualCharOpsTest { 8 9 @Test 10 public void testNullChecks() { 11 12 Character firstChar = firstCharacter("cat"); 13 14 if (null == firstChar) { 15 // oops. what now? exception? propagate null? 16 } else { 17 // will throw NPE on digit :( 18 Integer position = getAlphabetPosition(firstChar); 19 // ... 20 } 21 22 } 23 24 } What we get:
 explicit null checks
  • 43.
    1 package com.example; 2 3import org.junit.Test; 4 5 import static com.example.CharacterOps.*; 6 7 public class ActualCharOpsTest { 8 9 @Test 10 public void testNullChecks() { 11 12 Character firstChar = firstCharacter("cat"); 13 14 if (null == firstChar) { 15 // oops. what now? exception? propagate null? 16 } else { 17 // will throw NPE on digit :( 18 Integer position = getAlphabetPosition(firstChar); 19 // ... 20 } 21 22 } 23 24 } • you have to remember to check for null explicitly or face NPE hunting What we get:
 explicit null checks
  • 44.
    1 package com.example; 2 3import org.junit.Test; 4 5 import static com.example.CharacterOps.*; 6 7 public class ActualCharOpsTest { 8 9 @Test 10 public void testNullChecks() { 11 12 Character firstChar = firstCharacter("cat"); 13 14 if (null == firstChar) { 15 // oops. what now? exception? propagate null? 16 } else { 17 // will throw NPE on digit :( 18 Integer position = getAlphabetPosition(firstChar); 19 // ... 20 } 21 22 } 23 24 } • you have to remember to check for null explicitly or face NPE hunting • methods don't compose anymore What we get:
 explicit null checks
  • 45.
    1 package com.example; 2 3import org.junit.Test; 4 5 import static com.example.CharacterOps.*; 6 7 public class ActualCharOpsTest { 8 9 @Test 10 public void testNullChecks() { 11 12 Character firstChar = firstCharacter("cat"); 13 14 if (null == firstChar) { 15 // oops. what now? exception? propagate null? 16 } else { 17 // will throw NPE on digit :( 18 Integer position = getAlphabetPosition(firstChar); 19 // ... 20 } 21 22 } 23 24 } • you have to remember to check for null explicitly or face NPE hunting • methods don't compose anymore • ugly :( What we get:
 explicit null checks
  • 46.
    1 package com.example; 2 3import java.util.Optional; 4 5 class SafeCharOps { 6 7 static Optional<Character> firstCharacter( 8 String string 9 ) { 10 return (string.length() > 0) ? 11 Optional.of(string.charAt(0)) : 12 Optional.empty(); 13 } 14 15 static Optional<Integer> getAlphabetPosition( 16 Character character 17 ) { 18 int code = (int) character; 19 20 return isLowerCase(code) ? Optional.of(code - 96) : 21 isUpperCase(code) ? Optional.of(code - 64) : 22 Optional.empty(); 23 } 24 25 private static boolean isLowerCase(int code) { 26 return code >= 97 && code <= 122; 27 } 28 29 private static boolean isUpperCase(int code) { 30 return code >= 65 && code <= 90; 31 } 32 33 } Introducing 
 Optional<T>
  • 47.
    1 package com.example; 2 3import java.util.Optional; 4 5 class SafeCharOps { 6 7 static Optional<Character> firstCharacter( 8 String string 9 ) { 10 return (string.length() > 0) ? 11 Optional.of(string.charAt(0)) : 12 Optional.empty(); 13 } 14 15 static Optional<Integer> getAlphabetPosition( 16 Character character 17 ) { 18 int code = (int) character; 19 20 return isLowerCase(code) ? Optional.of(code - 96) : 21 isUpperCase(code) ? Optional.of(code - 64) : 22 Optional.empty(); 23 } 24 25 private static boolean isLowerCase(int code) { 26 return code >= 97 && code <= 122; 27 } 28 29 private static boolean isUpperCase(int code) { 30 return code >= 65 && code <= 90; 31 } 32 33 } • optionality of return value is explicit in type signature Introducing 
 Optional<T>
  • 48.
    1 package com.example; 2 3import java.util.Optional; 4 5 class SafeCharOps { 6 7 static Optional<Character> firstCharacter( 8 String string 9 ) { 10 return (string.length() > 0) ? 11 Optional.of(string.charAt(0)) : 12 Optional.empty(); 13 } 14 15 static Optional<Integer> getAlphabetPosition( 16 Character character 17 ) { 18 int code = (int) character; 19 20 return isLowerCase(code) ? Optional.of(code - 96) : 21 isUpperCase(code) ? Optional.of(code - 64) : 22 Optional.empty(); 23 } 24 25 private static boolean isLowerCase(int code) { 26 return code >= 97 && code <= 122; 27 } 28 29 private static boolean isUpperCase(int code) { 30 return code >= 65 && code <= 90; 31 } 32 33 } • optionality of return value is explicit in type signature • expressions used where possible Introducing 
 Optional<T>
  • 49.
    Composability revisited 1 package com.example; 2 3import org.junit.Test; 4 5 import static com.example.CharacterOps.*; 6 7 public class CharOpsTest { 8 9 @Test 10 public void testComposability() { 11 12 getAlphabetPosition(firstCharacter("cat")); 13 14 getAlphabetPosition(firstCharacter("")); 15 16 } 17 18 }
  • 50.
    Composability revisited 1 package com.example; 2 3import org.junit.Test; 4 5 import static com.example.CharacterOps.*; 6 7 public class CharOpsTest { 8 9 @Test 10 public void testComposability() { 11 12 getAlphabetPosition(firstCharacter("cat")); 13 14 getAlphabetPosition(firstCharacter("")); 15 16 } 17 18 } • doesn't compile :(
  • 51.
    Composability revisited 1 package com.example; 2 3import org.junit.Test; 4 5 import static com.example.CharacterOps.*; 6 7 public class CharOpsTest { 8 9 @Test 10 public void testComposability() { 11 12 getAlphabetPosition(firstCharacter("cat")); 13 14 getAlphabetPosition(firstCharacter("")); 15 16 } 17 18 } • doesn't compile :( • getAlphabetPosition accepts Character instances
  • 52.
    Composability revisited 1 package com.example; 2 3import org.junit.Test; 4 5 import static com.example.CharacterOps.*; 6 7 public class CharOpsTest { 8 9 @Test 10 public void testComposability() { 11 12 getAlphabetPosition(firstCharacter("cat")); 13 14 getAlphabetPosition(firstCharacter("")); 15 16 } 17 18 } • doesn't compile :( • getAlphabetPosition accepts Character instances • we have Optional<Character> from
 firstCharacter call
  • 53.
    Composability revisited 1 package com.example; 2 3import org.junit.Test; 4 5 import static com.example.CharacterOps.*; 6 7 public class CharOpsTest { 8 9 @Test 10 public void testComposability() { 11 12 getAlphabetPosition(firstCharacter("cat")); 13 14 getAlphabetPosition(firstCharacter("")); 15 16 } 17 18 } • doesn't compile :( • getAlphabetPosition accepts Character instances • we have Optional<Character> from
 firstCharacter call • how to extract value in safe manner?
  • 54.
    1 package com.example; 2 3import org.junit.Test; 4 5 import java.util.Optional; 6 7 import static com.example.SafeCharOps.*; 8 9 public class OptionalCharOpsTest { 10 11 @Test 12 public void testOptionalMethods() { 13 14 Optional<Character> firstChar = 15 firstCharacter("cat"); 16 17 if (firstChar.isPresent()) { 18 19 Optional<Integer> alphabetPosition = 20 getAlphabetPosition(firstChar.get()); 21 22 if (alphabetPosition.isPresent()) { 23 Integer position = alphabetPosition.get(); 24 25 // ... 26 } 27 28 } 29 30 } 31 32 } Imperative 
 approach
  • 55.
    1 package com.example; 2 3import org.junit.Test; 4 5 import java.util.Optional; 6 7 import static com.example.SafeCharOps.*; 8 9 public class OptionalCharOpsTest { 10 11 @Test 12 public void testOptionalMethods() { 13 14 Optional<Character> firstChar = 15 firstCharacter("cat"); 16 17 if (firstChar.isPresent()) { 18 19 Optional<Integer> alphabetPosition = 20 getAlphabetPosition(firstChar.get()); 21 22 if (alphabetPosition.isPresent()) { 23 Integer position = alphabetPosition.get(); 24 25 // ... 26 } 27 28 } 29 30 } 31 32 } • we've gained explicit information about optionality Imperative 
 approach
  • 56.
    1 package com.example; 2 3import org.junit.Test; 4 5 import java.util.Optional; 6 7 import static com.example.SafeCharOps.*; 8 9 public class OptionalCharOpsTest { 10 11 @Test 12 public void testOptionalMethods() { 13 14 Optional<Character> firstChar = 15 firstCharacter("cat"); 16 17 if (firstChar.isPresent()) { 18 19 Optional<Integer> alphabetPosition = 20 getAlphabetPosition(firstChar.get()); 21 22 if (alphabetPosition.isPresent()) { 23 Integer position = alphabetPosition.get(); 24 25 // ... 26 } 27 28 } 29 30 } 31 32 } • we've gained explicit information about optionality • still looks like null checks :( Imperative 
 approach
  • 57.
    1 package com.example; 2 3import org.junit.Test; 4 5 import java.util.Optional; 6 7 import static com.example.SafeCharOps.*; 8 9 public class OptionalCharOpsTest { 10 11 @Test 12 public void testOptionalMethods() { 13 14 Optional<Character> firstChar = 15 firstCharacter("cat"); 16 17 if (firstChar.isPresent()) { 18 19 Optional<Integer> alphabetPosition = 20 getAlphabetPosition(firstChar.get()); 21 22 if (alphabetPosition.isPresent()) { 23 Integer position = alphabetPosition.get(); 24 25 // ... 26 } 27 28 } 29 30 } 31 32 } • we've gained explicit information about optionality • still looks like null checks :( • still breaks composability :( Imperative 
 approach
  • 58.
    Functional 
 approach 1 packagecom.example; 2 3 import org.junit.Test; 4 5 import static com.example.SafeCharOps.*; 6 7 public class OptionalCharOpsTest { 8 9 @Test 10 public void testOptionalMethods() { 11 12 firstCharacter("cat") 13 .flatMap(SafeCharOps::getAlphabetPosition) 14 .ifPresent(value -> { 15 assert value == 3; 16 }); 17 18 assert !firstCharacter("") 19 .flatMap(SafeCharOps::getAlphabetPosition) 20 .isPresent(); 21 } 22 23 }
  • 59.
    • no nullchecks! Functional 
 approach 1 package com.example; 2 3 import org.junit.Test; 4 5 import static com.example.SafeCharOps.*; 6 7 public class OptionalCharOpsTest { 8 9 @Test 10 public void testOptionalMethods() { 11 12 firstCharacter("cat") 13 .flatMap(SafeCharOps::getAlphabetPosition) 14 .ifPresent(value -> { 15 assert value == 3; 16 }); 17 18 assert !firstCharacter("") 19 .flatMap(SafeCharOps::getAlphabetPosition) 20 .isPresent(); 21 } 22 23 }
  • 60.
    • no nullchecks! • clean functional composition Functional 
 approach 1 package com.example; 2 3 import org.junit.Test; 4 5 import static com.example.SafeCharOps.*; 6 7 public class OptionalCharOpsTest { 8 9 @Test 10 public void testOptionalMethods() { 11 12 firstCharacter("cat") 13 .flatMap(SafeCharOps::getAlphabetPosition) 14 .ifPresent(value -> { 15 assert value == 3; 16 }); 17 18 assert !firstCharacter("") 19 .flatMap(SafeCharOps::getAlphabetPosition) 20 .isPresent(); 21 } 22 23 }
  • 61.
    • no nullchecks! • clean functional composition • type safety, IDE helps at every step as type inference is easy Functional 
 approach 1 package com.example; 2 3 import org.junit.Test; 4 5 import static com.example.SafeCharOps.*; 6 7 public class OptionalCharOpsTest { 8 9 @Test 10 public void testOptionalMethods() { 11 12 firstCharacter("cat") 13 .flatMap(SafeCharOps::getAlphabetPosition) 14 .ifPresent(value -> { 15 assert value == 3; 16 }); 17 18 assert !firstCharacter("") 19 .flatMap(SafeCharOps::getAlphabetPosition) 20 .isPresent(); 21 } 22 23 }
  • 62.
    • no nullchecks! • clean functional composition • type safety, IDE helps at every step as type inference is easy • easier to refactor Functional 
 approach 1 package com.example; 2 3 import org.junit.Test; 4 5 import static com.example.SafeCharOps.*; 6 7 public class OptionalCharOpsTest { 8 9 @Test 10 public void testOptionalMethods() { 11 12 firstCharacter("cat") 13 .flatMap(SafeCharOps::getAlphabetPosition) 14 .ifPresent(value -> { 15 assert value == 3; 16 }); 17 18 assert !firstCharacter("") 19 .flatMap(SafeCharOps::getAlphabetPosition) 20 .isPresent(); 21 } 22 23 }
  • 63.
    • no nullchecks! • clean functional composition • type safety, IDE helps at every step as type inference is easy • easier to refactor • elegant & succinct Functional 
 approach 1 package com.example; 2 3 import org.junit.Test; 4 5 import static com.example.SafeCharOps.*; 6 7 public class OptionalCharOpsTest { 8 9 @Test 10 public void testOptionalMethods() { 11 12 firstCharacter("cat") 13 .flatMap(SafeCharOps::getAlphabetPosition) 14 .ifPresent(value -> { 15 assert value == 3; 16 }); 17 18 assert !firstCharacter("") 19 .flatMap(SafeCharOps::getAlphabetPosition) 20 .isPresent(); 21 } 22 23 }
  • 64.
    1 package com.example; 2 3import java.util.List; 4 import java.util.stream.Collectors; 5 6 class StreamsAndErrors { 7 8 static List<Integer> toNumbers(List<String> strings) { 9 return strings.stream() 10 .map(Integer::parseInt) 11 .collect(Collectors.toList()); 12 } 13 14 } Streams API
 revisited: errors 1 package com.example; 2 3 import org.junit.Test; 4 5 import java.util.List; 6 7 import static java.lang.System.out; 8 import static java.util.Arrays.asList; 9 10 public class StreamsTest { 11 12 @Test 13 public void testNumberConversion() { 14 List<String> strings = asList("1", "2", "3"); 15 out.println(StreamsAndErrors.toNumbers(strings)); 16 // prints '[1, 2, 3]' 17 18 List<String> itsaTrap = asList("1", "a", "3"); 19 out.println(StreamsAndErrors.toNumbers(itsaTrap)); 20 // NumberFormatException: For input string: "a" 21 } 22 23 }
  • 65.
    1 package com.example; 2 3import java.util.List; 4 import java.util.stream.Collectors; 5 6 class StreamsAndErrors { 7 8 static List<Integer> toNumbers(List<String> strings) { 9 return strings.stream() 10 .map(Integer::parseInt) 11 .collect(Collectors.toList()); 12 } 13 14 } • generic string to integer conversion on collection level Streams API
 revisited: errors 1 package com.example; 2 3 import org.junit.Test; 4 5 import java.util.List; 6 7 import static java.lang.System.out; 8 import static java.util.Arrays.asList; 9 10 public class StreamsTest { 11 12 @Test 13 public void testNumberConversion() { 14 List<String> strings = asList("1", "2", "3"); 15 out.println(StreamsAndErrors.toNumbers(strings)); 16 // prints '[1, 2, 3]' 17 18 List<String> itsaTrap = asList("1", "a", "3"); 19 out.println(StreamsAndErrors.toNumbers(itsaTrap)); 20 // NumberFormatException: For input string: "a" 21 } 22 23 }
  • 66.
    1 package com.example; 2 3import java.util.List; 4 import java.util.stream.Collectors; 5 6 class StreamsAndErrors { 7 8 static List<Integer> toNumbers(List<String> strings) { 9 return strings.stream() 10 .map(Integer::parseInt) 11 .collect(Collectors.toList()); 12 } 13 14 } • generic string to integer conversion on collection level • type signature tells nothing about possible of failure Streams API
 revisited: errors 1 package com.example; 2 3 import org.junit.Test; 4 5 import java.util.List; 6 7 import static java.lang.System.out; 8 import static java.util.Arrays.asList; 9 10 public class StreamsTest { 11 12 @Test 13 public void testNumberConversion() { 14 List<String> strings = asList("1", "2", "3"); 15 out.println(StreamsAndErrors.toNumbers(strings)); 16 // prints '[1, 2, 3]' 17 18 List<String> itsaTrap = asList("1", "a", "3"); 19 out.println(StreamsAndErrors.toNumbers(itsaTrap)); 20 // NumberFormatException: For input string: "a" 21 } 22 23 }
  • 67.
    1 package com.example; 2 3import java.util.List; 4 import java.util.stream.Collectors; 5 6 class StreamsAndErrors { 7 8 static List<Integer> toNumbers(List<String> strings) { 9 return strings.stream() 10 .map(Integer::parseInt) 11 .collect(Collectors.toList()); 12 } 13 14 } • generic string to integer conversion on collection level • type signature tells nothing about possible of failure • programmer knows (let's hope so) about possibility of failure and has to deal with it explicitly Streams API
 revisited: errors 1 package com.example; 2 3 import org.junit.Test; 4 5 import java.util.List; 6 7 import static java.lang.System.out; 8 import static java.util.Arrays.asList; 9 10 public class StreamsTest { 11 12 @Test 13 public void testNumberConversion() { 14 List<String> strings = asList("1", "2", "3"); 15 out.println(StreamsAndErrors.toNumbers(strings)); 16 // prints '[1, 2, 3]' 17 18 List<String> itsaTrap = asList("1", "a", "3"); 19 out.println(StreamsAndErrors.toNumbers(itsaTrap)); 20 // NumberFormatException: For input string: "a" 21 } 22 23 }
  • 68.
    1 package com.example; 2 3import java.util.List; 4 import java.util.Objects; 5 import java.util.Optional; 6 import java.util.stream.Collectors; 7 8 class StreamsAndErrors { 9 10 static List<Integer> filterOut(List<String> strings) { 11 return strings.stream() 12 .map(s -> { 13 try { 14 return Integer.parseInt(s); 15 } catch (NumberFormatException nfe) { 16 return null; 17 } 18 }) 19 .filter(Objects::nonNull) 20 .collect(Collectors.toList()); 21 } 22 23 static Optional<List<Integer>> failOnFirst( 24 List<String> strings 25 ) { 26 try { 27 return Optional.of( 28 strings.stream() 29 .map(Integer::parseInt) 30 .collect(Collectors.toList()) 31 ); 32 } catch (NumberFormatException nfe) { 33 return Optional.empty(); 34 } 35 } 36 37 } Handling errors 
 with try
  • 69.
    1 package com.example; 2 3import java.util.List; 4 import java.util.Objects; 5 import java.util.Optional; 6 import java.util.stream.Collectors; 7 8 class StreamsAndErrors { 9 10 static List<Integer> filterOut(List<String> strings) { 11 return strings.stream() 12 .map(s -> { 13 try { 14 return Integer.parseInt(s); 15 } catch (NumberFormatException nfe) { 16 return null; 17 } 18 }) 19 .filter(Objects::nonNull) 20 .collect(Collectors.toList()); 21 } 22 23 static Optional<List<Integer>> failOnFirst( 24 List<String> strings 25 ) { 26 try { 27 return Optional.of( 28 strings.stream() 29 .map(Integer::parseInt) 30 .collect(Collectors.toList()) 31 ); 32 } catch (NumberFormatException nfe) { 33 return Optional.empty(); 34 } 35 } 36 37 } • ughh... null again :( Handling errors 
 with try
  • 70.
    1 package com.example; 2 3import java.util.List; 4 import java.util.Objects; 5 import java.util.Optional; 6 import java.util.stream.Collectors; 7 8 class StreamsAndErrors { 9 10 static List<Integer> filterOut(List<String> strings) { 11 return strings.stream() 12 .map(s -> { 13 try { 14 return Integer.parseInt(s); 15 } catch (NumberFormatException nfe) { 16 return null; 17 } 18 }) 19 .filter(Objects::nonNull) 20 .collect(Collectors.toList()); 21 } 22 23 static Optional<List<Integer>> failOnFirst( 24 List<String> strings 25 ) { 26 try { 27 return Optional.of( 28 strings.stream() 29 .map(Integer::parseInt) 30 .collect(Collectors.toList()) 31 ); 32 } catch (NumberFormatException nfe) { 33 return Optional.empty(); 34 } 35 } 36 37 } • ughh... null again :( • we have to filter nulls out lest we create a disaster waiting to happen Handling errors 
 with try
  • 71.
    1 package com.example; 2 3import java.util.List; 4 import java.util.Objects; 5 import java.util.Optional; 6 import java.util.stream.Collectors; 7 8 class StreamsAndErrors { 9 10 static List<Integer> filterOut(List<String> strings) { 11 return strings.stream() 12 .map(s -> { 13 try { 14 return Integer.parseInt(s); 15 } catch (NumberFormatException nfe) { 16 return null; 17 } 18 }) 19 .filter(Objects::nonNull) 20 .collect(Collectors.toList()); 21 } 22 23 static Optional<List<Integer>> failOnFirst( 24 List<String> strings 25 ) { 26 try { 27 return Optional.of( 28 strings.stream() 29 .map(Integer::parseInt) 30 .collect(Collectors.toList()) 31 ); 32 } catch (NumberFormatException nfe) { 33 return Optional.empty(); 34 } 35 } 36 37 } • ughh... null again :( • we have to filter nulls out lest we create a disaster waiting to happen • using Optional tells caller that this method might yield value, but doesn't say anything about errors that may happen inside Handling errors 
 with try
  • 72.
    Handling errors 
 withTry 1 package com.example; 2 3 import javaslang.control.Try; 4 5 import java.util.List; 6 import java.util.stream.Collectors; 7 8 class StreamsAndErrors { 9 10 static List<Try<Integer>> withErrs(List<String> strings) { 11 return strings.stream() 12 .map(s -> Try.of(() -> Integer.parseInt(s))) 13 .collect(Collectors.toList()); 14 } 15 16 static Try<List<Integer>> failOnFirst( 17 List<String> strings 18 ) { 19 return Try.of(() -> strings.stream() 20 .map(Integer::parseInt) 21 .collect(Collectors.toList())); 22 } 23 24 }
  • 73.
    • type signatureexplicitly informs us about the possibility of error Handling errors 
 with Try 1 package com.example; 2 3 import javaslang.control.Try; 4 5 import java.util.List; 6 import java.util.stream.Collectors; 7 8 class StreamsAndErrors { 9 10 static List<Try<Integer>> withErrs(List<String> strings) { 11 return strings.stream() 12 .map(s -> Try.of(() -> Integer.parseInt(s))) 13 .collect(Collectors.toList()); 14 } 15 16 static Try<List<Integer>> failOnFirst( 17 List<String> strings 18 ) { 19 return Try.of(() -> strings.stream() 20 .map(Integer::parseInt) 21 .collect(Collectors.toList())); 22 } 23 24 }
  • 74.
    • type signatureexplicitly informs us about the possibility of error • exceptions are caught inside stream or in lambda wrapping whole stream processing Handling errors 
 with Try 1 package com.example; 2 3 import javaslang.control.Try; 4 5 import java.util.List; 6 import java.util.stream.Collectors; 7 8 class StreamsAndErrors { 9 10 static List<Try<Integer>> withErrs(List<String> strings) { 11 return strings.stream() 12 .map(s -> Try.of(() -> Integer.parseInt(s))) 13 .collect(Collectors.toList()); 14 } 15 16 static Try<List<Integer>> failOnFirst( 17 List<String> strings 18 ) { 19 return Try.of(() -> strings.stream() 20 .map(Integer::parseInt) 21 .collect(Collectors.toList())); 22 } 23 24 }
  • 75.
    1 package com.example; 2 3import java.text.SimpleDateFormat; 4 import java.util.Date; 5 import java.util.List; 6 import java.util.stream.Collectors; 7 8 class StreamsAndCheckedExceptions { 9 10 private static SimpleDateFormat sdf = 11 new SimpleDateFormat("ddMMyyyy"); 12 13 static List<Date> parseAll(List<String> strings) { 14 return strings.stream() 15 .map(sdf::parse) // won't compile! 16 .collect(Collectors.toList()); 17 } 18 19 } Checked exceptions 
 & Java 8 Streams:
 Rough edges
  • 76.
    1 package com.example; 2 3import java.text.SimpleDateFormat; 4 import java.util.Date; 5 import java.util.List; 6 import java.util.stream.Collectors; 7 8 class StreamsAndCheckedExceptions { 9 10 private static SimpleDateFormat sdf = 11 new SimpleDateFormat("ddMMyyyy"); 12 13 static List<Date> parseAll(List<String> strings) { 14 return strings.stream() 15 .map(sdf::parse) // won't compile! 16 .collect(Collectors.toList()); 17 } 18 19 } • ParseException is checked, which breaks lambda expression Checked exceptions 
 & Java 8 Streams:
 Rough edges
  • 77.
    1 package com.example; 2 3import java.text.SimpleDateFormat; 4 import java.util.Date; 5 import java.util.List; 6 import java.util.stream.Collectors; 7 8 class StreamsAndCheckedExceptions { 9 10 private static SimpleDateFormat sdf = 11 new SimpleDateFormat("ddMMyyyy"); 12 13 static List<Date> parseAll(List<String> strings) { 14 return strings.stream() 15 .map(sdf::parse) // won't compile! 16 .collect(Collectors.toList()); 17 } 18 19 } • ParseException is checked, which breaks lambda expression Checked exceptions 
 & Java 8 Streams:
 Rough edges
  • 78.
    1 package com.example; 2 3import java.text.SimpleDateFormat; 4 import java.util.Date; 5 import java.util.List; 6 import java.util.stream.Collectors; 7 8 class StreamsAndCheckedExceptions { 9 10 private static SimpleDateFormat sdf = 11 new SimpleDateFormat("ddMMyyyy"); 12 13 static List<Date> parseAll(List<String> strings) { 14 return strings.stream() 15 .map(sdf::parse) // won't compile! 16 .collect(Collectors.toList()); 17 } 18 19 } • ParseException is checked, which breaks lambda expression • solution using try-catch & rethrow-as-unchecked block is ugly and defeats the purpose of lambdas Checked exceptions 
 & Java 8 Streams:
 Rough edges
  • 79.
    Checked exceptions 
 &Java 8 Streams:
 Try to the rescue 1 package com.example; 2 3 import javaslang.control.Try; 4 5 import java.text.SimpleDateFormat; 6 import java.util.Date; 7 import java.util.List; 8 import java.util.stream.Collectors; 9 10 class StreamsAndCheckedExceptions { 11 12 private static SimpleDateFormat sdf = 13 new SimpleDateFormat("ddMMyyyy"); 14 15 static List<Try<Date>> parseAll(List<String> strings) { 16 return strings.stream() 17 .map(s -> Try.of(() -> sdf.parse(s))) 18 .collect(Collectors.toList()); 19 } 20 21 } 1 package com.example; 2 3 import org.junit.Test; 4 5 import java.util.List; 6 7 import static java.lang.System.out; 8 import static java.util.Arrays.asList; 9 10 public class StreamsTest { 11 12 @Test 13 public void testNumberConversion() { 14 List<String> strings = asList("10042017", "23"); 15 out.println( 16 StreamsAndCheckedExceptions.parseAll(strings) 17 ); 18 19 // [ 20 // Success(Mon Apr 10 00:00:00 CEST 2017), 21 // Failure(ParseException: Unparseable date: "23") 22 // ] 23 } 24 25 }
  • 80.
    • problem ofchecked exceptions solved
 Checked exceptions 
 & Java 8 Streams:
 Try to the rescue 1 package com.example; 2 3 import javaslang.control.Try; 4 5 import java.text.SimpleDateFormat; 6 import java.util.Date; 7 import java.util.List; 8 import java.util.stream.Collectors; 9 10 class StreamsAndCheckedExceptions { 11 12 private static SimpleDateFormat sdf = 13 new SimpleDateFormat("ddMMyyyy"); 14 15 static List<Try<Date>> parseAll(List<String> strings) { 16 return strings.stream() 17 .map(s -> Try.of(() -> sdf.parse(s))) 18 .collect(Collectors.toList()); 19 } 20 21 } 1 package com.example; 2 3 import org.junit.Test; 4 5 import java.util.List; 6 7 import static java.lang.System.out; 8 import static java.util.Arrays.asList; 9 10 public class StreamsTest { 11 12 @Test 13 public void testNumberConversion() { 14 List<String> strings = asList("10042017", "23"); 15 out.println( 16 StreamsAndCheckedExceptions.parseAll(strings) 17 ); 18 19 // [ 20 // Success(Mon Apr 10 00:00:00 CEST 2017), 21 // Failure(ParseException: Unparseable date: "23") 22 // ] 23 } 24 25 }
  • 81.
    • problem ofchecked exceptions solved
 • we also gained explicit information of possibility of failure Checked exceptions 
 & Java 8 Streams:
 Try to the rescue 1 package com.example; 2 3 import javaslang.control.Try; 4 5 import java.text.SimpleDateFormat; 6 import java.util.Date; 7 import java.util.List; 8 import java.util.stream.Collectors; 9 10 class StreamsAndCheckedExceptions { 11 12 private static SimpleDateFormat sdf = 13 new SimpleDateFormat("ddMMyyyy"); 14 15 static List<Try<Date>> parseAll(List<String> strings) { 16 return strings.stream() 17 .map(s -> Try.of(() -> sdf.parse(s))) 18 .collect(Collectors.toList()); 19 } 20 21 } 1 package com.example; 2 3 import org.junit.Test; 4 5 import java.util.List; 6 7 import static java.lang.System.out; 8 import static java.util.Arrays.asList; 9 10 public class StreamsTest { 11 12 @Test 13 public void testNumberConversion() { 14 List<String> strings = asList("10042017", "23"); 15 out.println( 16 StreamsAndCheckedExceptions.parseAll(strings) 17 ); 18 19 // [ 20 // Success(Mon Apr 10 00:00:00 CEST 2017), 21 // Failure(ParseException: Unparseable date: "23") 22 // ] 23 } 24 25 }
  • 82.
  • 83.
    Case for immutabledata • Mutable state makes it significantly harder to reason about a system due to multiple code branches that can lead to mutation of state
  • 84.
    Case for immutabledata • Mutable state makes it significantly harder to reason about a system due to multiple code branches that can lead to mutation of state • Shared mutable state in concurrent environment requires synchronisation which is difficult and leads to subtle and 
 hard to replicate bugs
  • 85.
    Case for immutabledata • Mutable state makes it significantly harder to reason about a system due to multiple code branches that can lead to mutation of state • Shared mutable state in concurrent environment requires synchronisation which is difficult and leads to subtle and 
 hard to replicate bugs • Immutable objects and persistent data structures have none of those problems
  • 86.
    Case for immutabledata • Mutable state makes it significantly harder to reason about a system due to multiple code branches that can lead to mutation of state • Shared mutable state in concurrent environment requires synchronisation which is difficult and leads to subtle and 
 hard to replicate bugs • Immutable objects and persistent data structures have none of those problems • it's trivial to protect invariants once instance is immutable
  • 87.
    Case for immutabledata • Mutable state makes it significantly harder to reason about a system due to multiple code branches that can lead to mutation of state • Shared mutable state in concurrent environment requires synchronisation which is difficult and leads to subtle and 
 hard to replicate bugs • Immutable objects and persistent data structures have none of those problems • it's trivial to protect invariants once instance is immutable • no need for synchronisation ever
  • 88.
    Case for immutabledata • Mutable state makes it significantly harder to reason about a system due to multiple code branches that can lead to mutation of state • Shared mutable state in concurrent environment requires synchronisation which is difficult and leads to subtle and 
 hard to replicate bugs • Immutable objects and persistent data structures have none of those problems • it's trivial to protect invariants once instance is immutable • no need for synchronisation ever ... so how to do this in Java?
  • 89.
    Immutable objects
 in plainJava 1 package com.example; 2 3 import java.util.Set; 4 5 public class Office { 6 7 private final Set<Room> rooms; 8 private final Set<Employee> employees; 9 10 public Office(Set<Room> rooms, Set<Employee> employees) { 11 this.rooms = rooms; 12 this.employees = employees; 13 } 14 15 public Set<Room> getRooms() { 16 return rooms; 17 } 18 19 public Set<Employee> getEmployees() { 20 return employees; 21 } 22 23 }
  • 90.
    Immutable objects
 in plainJava • lots of getter noise1 package com.example; 2 3 import java.util.Set; 4 5 public class Office { 6 7 private final Set<Room> rooms; 8 private final Set<Employee> employees; 9 10 public Office(Set<Room> rooms, Set<Employee> employees) { 11 this.rooms = rooms; 12 this.employees = employees; 13 } 14 15 public Set<Room> getRooms() { 16 return rooms; 17 } 18 19 public Set<Employee> getEmployees() { 20 return employees; 21 } 22 23 }
  • 91.
    Immutable objects
 in plainJava • lots of getter noise • if we want a builder, we have to create one and make constructor private 1 package com.example; 2 3 import java.util.Set; 4 5 public class Office { 6 7 private final Set<Room> rooms; 8 private final Set<Employee> employees; 9 10 public Office(Set<Room> rooms, Set<Employee> employees) { 11 this.rooms = rooms; 12 this.employees = employees; 13 } 14 15 public Set<Room> getRooms() { 16 return rooms; 17 } 18 19 public Set<Employee> getEmployees() { 20 return employees; 21 } 22 23 }
  • 92.
    Immutable objects
 in plainJava • lots of getter noise • if we want a builder, we have to create one and make constructor private • optional field is painful as IDE will cry that fields should never have type Optional<T> 1 package com.example; 2 3 import java.util.Set; 4 5 public class Office { 6 7 private final Set<Room> rooms; 8 private final Set<Employee> employees; 9 10 public Office(Set<Room> rooms, Set<Employee> employees) { 11 this.rooms = rooms; 12 this.employees = employees; 13 } 14 15 public Set<Room> getRooms() { 16 return rooms; 17 } 18 19 public Set<Employee> getEmployees() { 20 return employees; 21 } 22 23 }
  • 93.
    Immutable objects
 in plainJava • lots of getter noise • if we want a builder, we have to create one and make constructor private • optional field is painful as IDE will cry that fields should never have type Optional<T> • how can we get a copy of instance with only one field modified? 1 package com.example; 2 3 import java.util.Set; 4 5 public class Office { 6 7 private final Set<Room> rooms; 8 private final Set<Employee> employees; 9 10 public Office(Set<Room> rooms, Set<Employee> employees) { 11 this.rooms = rooms; 12 this.employees = employees; 13 } 14 15 public Set<Room> getRooms() { 16 return rooms; 17 } 18 19 public Set<Employee> getEmployees() { 20 return employees; 21 } 22 23 }
  • 94.
    Enter Immutables 1 packagecom.example; 2 3 import javaslang.collection.Set; 4 import org.immutables.value.Value; 5 6 @Value.Immutable 7 @Value.Style(typeImmutable = "*") 8 abstract class AbstractOffice { 9 abstract Set<Room> rooms(); 10 11 abstract Set<Employee> employees(); 12 13 abstract boolean open(); 14 }
  • 95.
    • that's all! EnterImmutables 1 package com.example; 2 3 import javaslang.collection.Set; 4 import org.immutables.value.Value; 5 6 @Value.Immutable 7 @Value.Style(typeImmutable = "*") 8 abstract class AbstractOffice { 9 abstract Set<Room> rooms(); 10 11 abstract Set<Employee> employees(); 12 13 abstract boolean open(); 14 }
  • 96.
    • that's all! •highly customisable code generation Enter Immutables 1 package com.example; 2 3 import javaslang.collection.Set; 4 import org.immutables.value.Value; 5 6 @Value.Immutable 7 @Value.Style(typeImmutable = "*") 8 abstract class AbstractOffice { 9 abstract Set<Room> rooms(); 10 11 abstract Set<Employee> employees(); 12 13 abstract boolean open(); 14 }
  • 97.
    • that's all! •highly customisable code generation • handles Optional<T> values correctly Enter Immutables 1 package com.example; 2 3 import javaslang.collection.Set; 4 import org.immutables.value.Value; 5 6 @Value.Immutable 7 @Value.Style(typeImmutable = "*") 8 abstract class AbstractOffice { 9 abstract Set<Room> rooms(); 10 11 abstract Set<Employee> employees(); 12 13 abstract boolean open(); 14 }
  • 98.
    • that's all! •highly customisable code generation • handles Optional<T> values correctly • handles default values Enter Immutables 1 package com.example; 2 3 import javaslang.collection.Set; 4 import org.immutables.value.Value; 5 6 @Value.Immutable 7 @Value.Style(typeImmutable = "*") 8 abstract class AbstractOffice { 9 abstract Set<Room> rooms(); 10 11 abstract Set<Employee> employees(); 12 13 abstract boolean open(); 14 }
  • 99.
    • that's all! •highly customisable code generation • handles Optional<T> values correctly • handles default values • Jackson / Gson integration out- of-the-box Enter Immutables 1 package com.example; 2 3 import javaslang.collection.Set; 4 import org.immutables.value.Value; 5 6 @Value.Immutable 7 @Value.Style(typeImmutable = "*") 8 abstract class AbstractOffice { 9 abstract Set<Room> rooms(); 10 11 abstract Set<Employee> employees(); 12 13 abstract boolean open(); 14 }
  • 100.
    • that's all! •highly customisable code generation • handles Optional<T> values correctly • handles default values • Jackson / Gson integration out- of-the-box • provides with* methods allowing painless modification of immutable instances Enter Immutables 1 package com.example; 2 3 import javaslang.collection.Set; 4 import org.immutables.value.Value; 5 6 @Value.Immutable 7 @Value.Style(typeImmutable = "*") 8 abstract class AbstractOffice { 9 abstract Set<Room> rooms(); 10 11 abstract Set<Employee> employees(); 12 13 abstract boolean open(); 14 }
  • 101.
    Immutables in action 1 packagecom.example; 2 3 import javaslang.collection.HashSet; 4 import org.junit.Test; 5 6 public class OfficeTest { 7 8 @Test 9 public void muchSafeSuchImmutableVeryWow() { 10 11 HashSet<Employee> employees = HashSet.of( 12 Employee.of("Boss") 13 ); 14 15 HashSet<Room> rooms = HashSet.of( 16 Room.of("Boss' office") 17 ); 18 19 Office office = Office.builder() 20 .employees(employees) 21 .rooms(rooms) 22 .open(true) 23 .build(); 24 25 System.out.println(office); 26 // Office{ 27 // rooms=HashSet(Room{name=Boss' office}), 28 // employees=HashSet(Employee{name=Boss}), 29 // open=true} 30 31 Office officeWithoutEmployees = office 32 .withEmployees(HashSet.empty()) 33 .withOpen(false); 34 35 System.out.println(officeWithoutEmployees); 36 // Office{ 37 // rooms=HashSet(Room{name=Boss' office}), 38 // employees=HashSet(), 39 // open=false} 40 } 41 }
  • 102.
    • named factorymethod can be generated (of by default) Immutables in action 1 package com.example; 2 3 import javaslang.collection.HashSet; 4 import org.junit.Test; 5 6 public class OfficeTest { 7 8 @Test 9 public void muchSafeSuchImmutableVeryWow() { 10 11 HashSet<Employee> employees = HashSet.of( 12 Employee.of("Boss") 13 ); 14 15 HashSet<Room> rooms = HashSet.of( 16 Room.of("Boss' office") 17 ); 18 19 Office office = Office.builder() 20 .employees(employees) 21 .rooms(rooms) 22 .open(true) 23 .build(); 24 25 System.out.println(office); 26 // Office{ 27 // rooms=HashSet(Room{name=Boss' office}), 28 // employees=HashSet(Employee{name=Boss}), 29 // open=true} 30 31 Office officeWithoutEmployees = office 32 .withEmployees(HashSet.empty()) 33 .withOpen(false); 34 35 System.out.println(officeWithoutEmployees); 36 // Office{ 37 // rooms=HashSet(Room{name=Boss' office}), 38 // employees=HashSet(), 39 // open=false} 40 } 41 }
  • 103.
    • named factorymethod can be generated (of by default) • builder class is generated by default Immutables in action 1 package com.example; 2 3 import javaslang.collection.HashSet; 4 import org.junit.Test; 5 6 public class OfficeTest { 7 8 @Test 9 public void muchSafeSuchImmutableVeryWow() { 10 11 HashSet<Employee> employees = HashSet.of( 12 Employee.of("Boss") 13 ); 14 15 HashSet<Room> rooms = HashSet.of( 16 Room.of("Boss' office") 17 ); 18 19 Office office = Office.builder() 20 .employees(employees) 21 .rooms(rooms) 22 .open(true) 23 .build(); 24 25 System.out.println(office); 26 // Office{ 27 // rooms=HashSet(Room{name=Boss' office}), 28 // employees=HashSet(Employee{name=Boss}), 29 // open=true} 30 31 Office officeWithoutEmployees = office 32 .withEmployees(HashSet.empty()) 33 .withOpen(false); 34 35 System.out.println(officeWithoutEmployees); 36 // Office{ 37 // rooms=HashSet(Room{name=Boss' office}), 38 // employees=HashSet(), 39 // open=false} 40 } 41 }
  • 104.
    • named factorymethod can be generated (of by default) • builder class is generated by default • toString, hashCode and equals are generated using best practice methods Immutables in action 1 package com.example; 2 3 import javaslang.collection.HashSet; 4 import org.junit.Test; 5 6 public class OfficeTest { 7 8 @Test 9 public void muchSafeSuchImmutableVeryWow() { 10 11 HashSet<Employee> employees = HashSet.of( 12 Employee.of("Boss") 13 ); 14 15 HashSet<Room> rooms = HashSet.of( 16 Room.of("Boss' office") 17 ); 18 19 Office office = Office.builder() 20 .employees(employees) 21 .rooms(rooms) 22 .open(true) 23 .build(); 24 25 System.out.println(office); 26 // Office{ 27 // rooms=HashSet(Room{name=Boss' office}), 28 // employees=HashSet(Employee{name=Boss}), 29 // open=true} 30 31 Office officeWithoutEmployees = office 32 .withEmployees(HashSet.empty()) 33 .withOpen(false); 34 35 System.out.println(officeWithoutEmployees); 36 // Office{ 37 // rooms=HashSet(Room{name=Boss' office}), 38 // employees=HashSet(), 39 // open=false} 40 } 41 }
  • 105.
    • named factorymethod can be generated (of by default) • builder class is generated by default • toString, hashCode and equals are generated using best practice methods • with(field) methods make obtaining modified instances a breeze! Immutables in action 1 package com.example; 2 3 import javaslang.collection.HashSet; 4 import org.junit.Test; 5 6 public class OfficeTest { 7 8 @Test 9 public void muchSafeSuchImmutableVeryWow() { 10 11 HashSet<Employee> employees = HashSet.of( 12 Employee.of("Boss") 13 ); 14 15 HashSet<Room> rooms = HashSet.of( 16 Room.of("Boss' office") 17 ); 18 19 Office office = Office.builder() 20 .employees(employees) 21 .rooms(rooms) 22 .open(true) 23 .build(); 24 25 System.out.println(office); 26 // Office{ 27 // rooms=HashSet(Room{name=Boss' office}), 28 // employees=HashSet(Employee{name=Boss}), 29 // open=true} 30 31 Office officeWithoutEmployees = office 32 .withEmployees(HashSet.empty()) 33 .withOpen(false); 34 35 System.out.println(officeWithoutEmployees); 36 // Office{ 37 // rooms=HashSet(Room{name=Boss' office}), 38 // employees=HashSet(), 39 // open=false} 40 } 41 }
  • 106.
    • named factorymethod can be generated (of by default) • builder class is generated by default • toString, hashCode and equals are generated using best practice methods • with(field) methods make obtaining modified instances a breeze! • nulls are not allowed by default, so fail early rule is enforced Immutables in action 1 package com.example; 2 3 import javaslang.collection.HashSet; 4 import org.junit.Test; 5 6 public class OfficeTest { 7 8 @Test 9 public void muchSafeSuchImmutableVeryWow() { 10 11 HashSet<Employee> employees = HashSet.of( 12 Employee.of("Boss") 13 ); 14 15 HashSet<Room> rooms = HashSet.of( 16 Room.of("Boss' office") 17 ); 18 19 Office office = Office.builder() 20 .employees(employees) 21 .rooms(rooms) 22 .open(true) 23 .build(); 24 25 System.out.println(office); 26 // Office{ 27 // rooms=HashSet(Room{name=Boss' office}), 28 // employees=HashSet(Employee{name=Boss}), 29 // open=true} 30 31 Office officeWithoutEmployees = office 32 .withEmployees(HashSet.empty()) 33 .withOpen(false); 34 35 System.out.println(officeWithoutEmployees); 36 // Office{ 37 // rooms=HashSet(Room{name=Boss' office}), 38 // employees=HashSet(), 39 // open=false} 40 } 41 }
  • 107.
  • 108.
    About that 
 javaslang.collection.HashSet •Javaslang provides immutable, persistent, purely functional collections that match Java Collections
  • 109.
    About that 
 javaslang.collection.HashSet •Javaslang provides immutable, persistent, purely functional collections that match Java Collections • Immutable, so every operation returns a new instance
  • 110.
    About that 
 javaslang.collection.HashSet •Javaslang provides immutable, persistent, purely functional collections that match Java Collections • Immutable, so every operation returns a new instance • Persistent, so creation of new instance means that data is shared between instances of collection and only the reference to modified element is replaced
  • 111.
    About that 
 javaslang.collection.HashSet •Javaslang provides immutable, persistent, purely functional collections that match Java Collections • Immutable, so every operation returns a new instance • Persistent, so creation of new instance means that data is shared between instances of collection and only the reference to modified element is replaced • Functional, so all operations are referentially transparent
 (at least as long as values stored in collection are immutable!)
  • 112.
  • 113.
    • classic, imperative approachwith StringBuilder: for loop and append calls Javaslang 
 Collections APIs
  • 114.
    1 String join(String...words) { 2 StringBuilder builder = new StringBuilder(); 3 for(String s : words) { 4 if (builder.length() > 0) { 5 builder.append(", "); 6 } 7 builder.append(s); 8 } 9 return builder.toString(); 10 } • classic, imperative approach with StringBuilder: for loop and append calls Javaslang 
 Collections APIs
  • 115.
    1 String join(String...words) { 2 StringBuilder builder = new StringBuilder(); 3 for(String s : words) { 4 if (builder.length() > 0) { 5 builder.append(", "); 6 } 7 builder.append(s); 8 } 9 return builder.toString(); 10 } • classic, imperative approach with StringBuilder: for loop and append calls • declarative use of List methods and fold (reduce to accumulator value) Javaslang 
 Collections APIs
  • 116.
    1 String join(String...words) { 2 StringBuilder builder = new StringBuilder(); 3 for(String s : words) { 4 if (builder.length() > 0) { 5 builder.append(", "); 6 } 7 builder.append(s); 8 } 9 return builder.toString(); 10 } 1 String join(String... words) { 2 return List.of(words) 3 .intersperse(", ") 4 .fold("", String::concat); 5 } • classic, imperative approach with StringBuilder: for loop and append calls • declarative use of List methods and fold (reduce to accumulator value) Javaslang 
 Collections APIs
  • 117.
    1 String join(String...words) { 2 StringBuilder builder = new StringBuilder(); 3 for(String s : words) { 4 if (builder.length() > 0) { 5 builder.append(", "); 6 } 7 builder.append(s); 8 } 9 return builder.toString(); 10 } 1 String join(String... words) { 2 return List.of(words) 3 .intersperse(", ") 4 .fold("", String::concat); 5 } • classic, imperative approach with StringBuilder: for loop and append calls • declarative use of List methods and fold (reduce to accumulator value) • a helper method! Javaslang 
 Collections APIs
  • 118.
    1 String join(String...words) { 2 StringBuilder builder = new StringBuilder(); 3 for(String s : words) { 4 if (builder.length() > 0) { 5 builder.append(", "); 6 } 7 builder.append(s); 8 } 9 return builder.toString(); 10 } 1 String join(String... words) { 2 return List.of(words) 3 .intersperse(", ") 4 .fold("", String::concat); 5 } 1 List.of(words).mkString(", "); • classic, imperative approach with StringBuilder: for loop and append calls • declarative use of List methods and fold (reduce to accumulator value) • a helper method! Javaslang 
 Collections APIs
  • 119.
    1 package com.example; 2 3import javaslang.Tuple; 4 import javaslang.collection.HashMap; 5 import javaslang.collection.HashSet; 6 import javaslang.collection.Map; 7 import javaslang.collection.Set; 8 9 public class PhoneNumberData { 10 11 public static final Set<String> phoneNumbers = 12 HashSet.of( 13 "+48413422345", 14 "413572456", 15 "+48413456990", 16 "48225697246", 17 "+48224914634", 18 "48126434972", 19 "+48128275242" 20 ); 21 22 public static final Map<String, String> areas = 23 HashMap.ofEntries( 24 Tuple.of("41", "Kielce"), 25 Tuple.of("22", "Warszawa"), 26 Tuple.of("12", "Kraków") 27 ); 28 29 } A more complex 
 example...
  • 120.
    1 package com.example; 2 3import javaslang.Tuple; 4 import javaslang.collection.HashMap; 5 import javaslang.collection.HashSet; 6 import javaslang.collection.Map; 7 import javaslang.collection.Set; 8 9 public class PhoneNumberData { 10 11 public static final Set<String> phoneNumbers = 12 HashSet.of( 13 "+48413422345", 14 "413572456", 15 "+48413456990", 16 "48225697246", 17 "+48224914634", 18 "48126434972", 19 "+48128275242" 20 ); 21 22 public static final Map<String, String> areas = 23 HashMap.ofEntries( 24 Tuple.of("41", "Kielce"), 25 Tuple.of("22", "Warszawa"), 26 Tuple.of("12", "Kraków") 27 ); 28 29 } • phoneNumbers: phone numbers in different formats A more complex 
 example...
  • 121.
    1 package com.example; 2 3import javaslang.Tuple; 4 import javaslang.collection.HashMap; 5 import javaslang.collection.HashSet; 6 import javaslang.collection.Map; 7 import javaslang.collection.Set; 8 9 public class PhoneNumberData { 10 11 public static final Set<String> phoneNumbers = 12 HashSet.of( 13 "+48413422345", 14 "413572456", 15 "+48413456990", 16 "48225697246", 17 "+48224914634", 18 "48126434972", 19 "+48128275242" 20 ); 21 22 public static final Map<String, String> areas = 23 HashMap.ofEntries( 24 Tuple.of("41", "Kielce"), 25 Tuple.of("22", "Warszawa"), 26 Tuple.of("12", "Kraków") 27 ); 28 29 } • phoneNumbers: phone numbers in different formats • areas: maps prefixes to area names A more complex 
 example...
  • 122.
    1 package com.example; 2 3import javaslang.Tuple; 4 import javaslang.collection.HashMap; 5 import javaslang.collection.HashSet; 6 import javaslang.collection.Map; 7 import javaslang.collection.Set; 8 9 public class PhoneNumberData { 10 11 public static final Set<String> phoneNumbers = 12 HashSet.of( 13 "+48413422345", 14 "413572456", 15 "+48413456990", 16 "48225697246", 17 "+48224914634", 18 "48126434972", 19 "+48128275242" 20 ); 21 22 public static final Map<String, String> areas = 23 HashMap.ofEntries( 24 Tuple.of("41", "Kielce"), 25 Tuple.of("22", "Warszawa"), 26 Tuple.of("12", "Kraków") 27 ); 28 29 } • phoneNumbers: phone numbers in different formats • areas: maps prefixes to area names • task: group numbers by area name A more complex 
 example...
  • 123.
    Processing!1 package com.example; 2 3import javaslang.Tuple; 4 import javaslang.Tuple2; 5 import javaslang.collection.HashMap; 6 import javaslang.collection.HashSet; 7 import javaslang.collection.Map; 8 import javaslang.collection.Set; 9 import org.junit.Test; 10 11 import static java.util.function.Function.identity; 12 import static com.example.PhoneNumberData.*; 13 14 public class GroupNumbersTest { 15 16 @Test 17 public void processNumbers() { 18 Map<String, Set<String>> map = phoneNumbers 19 .filter(num -> num.length() >= 9) 20 .map(number -> Tuple.of(number, number)) 21 .map(tuple -> 22 tuple.map( 23 num -> num.replaceFirst("^+?48", ""), 24 identity() 25 ) 26 ) 27 .groupBy(tuple -> tuple._1().substring(0, 2)) 28 .bimap( 29 key -> areas.get(key).getOrElse("Nieznany"), 30 value -> value.map(Tuple2::_2) 31 ); 32 33 System.out.println(map); 34 } 35 36 }
  • 124.
    Processing!1 package com.example; 2 3import javaslang.Tuple; 4 import javaslang.Tuple2; 5 import javaslang.collection.HashMap; 6 import javaslang.collection.HashSet; 7 import javaslang.collection.Map; 8 import javaslang.collection.Set; 9 import org.junit.Test; 10 11 import static java.util.function.Function.identity; 12 import static com.example.PhoneNumberData.*; 13 14 public class GroupNumbersTest { 15 16 @Test 17 public void processNumbers() { 18 Map<String, Set<String>> map = phoneNumbers 19 .filter(num -> num.length() >= 9) 20 .map(number -> Tuple.of(number, number)) 21 .map(tuple -> 22 tuple.map( 23 num -> num.replaceFirst("^+?48", ""), 24 identity() 25 ) 26 ) 27 .groupBy(tuple -> tuple._1().substring(0, 2)) 28 .bimap( 29 key -> areas.get(key).getOrElse("Nieznany"), 30 value -> value.map(Tuple2::_2) 31 ); 32 33 System.out.println(map); 34 } 35 36 }
  • 125.
    Processing! • filter outnumbers that are too short 1 package com.example; 2 3 import javaslang.Tuple; 4 import javaslang.Tuple2; 5 import javaslang.collection.HashMap; 6 import javaslang.collection.HashSet; 7 import javaslang.collection.Map; 8 import javaslang.collection.Set; 9 import org.junit.Test; 10 11 import static java.util.function.Function.identity; 12 import static com.example.PhoneNumberData.*; 13 14 public class GroupNumbersTest { 15 16 @Test 17 public void processNumbers() { 18 Map<String, Set<String>> map = phoneNumbers 19 .filter(num -> num.length() >= 9) 20 .map(number -> Tuple.of(number, number)) 21 .map(tuple -> 22 tuple.map( 23 num -> num.replaceFirst("^+?48", ""), 24 identity() 25 ) 26 ) 27 .groupBy(tuple -> tuple._1().substring(0, 2)) 28 .bimap( 29 key -> areas.get(key).getOrElse("Nieznany"), 30 value -> value.map(Tuple2::_2) 31 ); 32 33 System.out.println(map); 34 } 35 36 }
  • 126.
    Processing! • filter outnumbers that are too short 1 package com.example; 2 3 import javaslang.Tuple; 4 import javaslang.Tuple2; 5 import javaslang.collection.HashMap; 6 import javaslang.collection.HashSet; 7 import javaslang.collection.Map; 8 import javaslang.collection.Set; 9 import org.junit.Test; 10 11 import static java.util.function.Function.identity; 12 import static com.example.PhoneNumberData.*; 13 14 public class GroupNumbersTest { 15 16 @Test 17 public void processNumbers() { 18 Map<String, Set<String>> map = phoneNumbers 19 .filter(num -> num.length() >= 9) 20 .map(number -> Tuple.of(number, number)) 21 .map(tuple -> 22 tuple.map( 23 num -> num.replaceFirst("^+?48", ""), 24 identity() 25 ) 26 ) 27 .groupBy(tuple -> tuple._1().substring(0, 2)) 28 .bimap( 29 key -> areas.get(key).getOrElse("Nieznany"), 30 value -> value.map(Tuple2::_2) 31 ); 32 33 System.out.println(map); 34 } 35 36 }
  • 127.
    Processing! • filter outnumbers that are too short • we need to have original number and still process it - Tuples! 1 package com.example; 2 3 import javaslang.Tuple; 4 import javaslang.Tuple2; 5 import javaslang.collection.HashMap; 6 import javaslang.collection.HashSet; 7 import javaslang.collection.Map; 8 import javaslang.collection.Set; 9 import org.junit.Test; 10 11 import static java.util.function.Function.identity; 12 import static com.example.PhoneNumberData.*; 13 14 public class GroupNumbersTest { 15 16 @Test 17 public void processNumbers() { 18 Map<String, Set<String>> map = phoneNumbers 19 .filter(num -> num.length() >= 9) 20 .map(number -> Tuple.of(number, number)) 21 .map(tuple -> 22 tuple.map( 23 num -> num.replaceFirst("^+?48", ""), 24 identity() 25 ) 26 ) 27 .groupBy(tuple -> tuple._1().substring(0, 2)) 28 .bimap( 29 key -> areas.get(key).getOrElse("Nieznany"), 30 value -> value.map(Tuple2::_2) 31 ); 32 33 System.out.println(map); 34 } 35 36 }
  • 128.
    Processing! • filter outnumbers that are too short • we need to have original number and still process it - Tuples! 1 package com.example; 2 3 import javaslang.Tuple; 4 import javaslang.Tuple2; 5 import javaslang.collection.HashMap; 6 import javaslang.collection.HashSet; 7 import javaslang.collection.Map; 8 import javaslang.collection.Set; 9 import org.junit.Test; 10 11 import static java.util.function.Function.identity; 12 import static com.example.PhoneNumberData.*; 13 14 public class GroupNumbersTest { 15 16 @Test 17 public void processNumbers() { 18 Map<String, Set<String>> map = phoneNumbers 19 .filter(num -> num.length() >= 9) 20 .map(number -> Tuple.of(number, number)) 21 .map(tuple -> 22 tuple.map( 23 num -> num.replaceFirst("^+?48", ""), 24 identity() 25 ) 26 ) 27 .groupBy(tuple -> tuple._1().substring(0, 2)) 28 .bimap( 29 key -> areas.get(key).getOrElse("Nieznany"), 30 value -> value.map(Tuple2::_2) 31 ); 32 33 System.out.println(map); 34 } 35 36 }
  • 129.
    Processing! • filter outnumbers that are too short • we need to have original number and still process it - Tuples! • strip international code from processed tuple entry, don't do anything to original 1 package com.example; 2 3 import javaslang.Tuple; 4 import javaslang.Tuple2; 5 import javaslang.collection.HashMap; 6 import javaslang.collection.HashSet; 7 import javaslang.collection.Map; 8 import javaslang.collection.Set; 9 import org.junit.Test; 10 11 import static java.util.function.Function.identity; 12 import static com.example.PhoneNumberData.*; 13 14 public class GroupNumbersTest { 15 16 @Test 17 public void processNumbers() { 18 Map<String, Set<String>> map = phoneNumbers 19 .filter(num -> num.length() >= 9) 20 .map(number -> Tuple.of(number, number)) 21 .map(tuple -> 22 tuple.map( 23 num -> num.replaceFirst("^+?48", ""), 24 identity() 25 ) 26 ) 27 .groupBy(tuple -> tuple._1().substring(0, 2)) 28 .bimap( 29 key -> areas.get(key).getOrElse("Nieznany"), 30 value -> value.map(Tuple2::_2) 31 ); 32 33 System.out.println(map); 34 } 35 36 }
  • 130.
    Processing! • filter outnumbers that are too short • we need to have original number and still process it - Tuples! • strip international code from processed tuple entry, don't do anything to original 1 package com.example; 2 3 import javaslang.Tuple; 4 import javaslang.Tuple2; 5 import javaslang.collection.HashMap; 6 import javaslang.collection.HashSet; 7 import javaslang.collection.Map; 8 import javaslang.collection.Set; 9 import org.junit.Test; 10 11 import static java.util.function.Function.identity; 12 import static com.example.PhoneNumberData.*; 13 14 public class GroupNumbersTest { 15 16 @Test 17 public void processNumbers() { 18 Map<String, Set<String>> map = phoneNumbers 19 .filter(num -> num.length() >= 9) 20 .map(number -> Tuple.of(number, number)) 21 .map(tuple -> 22 tuple.map( 23 num -> num.replaceFirst("^+?48", ""), 24 identity() 25 ) 26 ) 27 .groupBy(tuple -> tuple._1().substring(0, 2)) 28 .bimap( 29 key -> areas.get(key).getOrElse("Nieznany"), 30 value -> value.map(Tuple2::_2) 31 ); 32 33 System.out.println(map); 34 } 35 36 }
  • 131.
    Processing! • filter outnumbers that are too short • we need to have original number and still process it - Tuples! • strip international code from processed tuple entry, don't do anything to original • group tuples by area code obtained from processed tuple entry 1 package com.example; 2 3 import javaslang.Tuple; 4 import javaslang.Tuple2; 5 import javaslang.collection.HashMap; 6 import javaslang.collection.HashSet; 7 import javaslang.collection.Map; 8 import javaslang.collection.Set; 9 import org.junit.Test; 10 11 import static java.util.function.Function.identity; 12 import static com.example.PhoneNumberData.*; 13 14 public class GroupNumbersTest { 15 16 @Test 17 public void processNumbers() { 18 Map<String, Set<String>> map = phoneNumbers 19 .filter(num -> num.length() >= 9) 20 .map(number -> Tuple.of(number, number)) 21 .map(tuple -> 22 tuple.map( 23 num -> num.replaceFirst("^+?48", ""), 24 identity() 25 ) 26 ) 27 .groupBy(tuple -> tuple._1().substring(0, 2)) 28 .bimap( 29 key -> areas.get(key).getOrElse("Nieznany"), 30 value -> value.map(Tuple2::_2) 31 ); 32 33 System.out.println(map); 34 } 35 36 }
  • 132.
    Processing! • filter outnumbers that are too short • we need to have original number and still process it - Tuples! • strip international code from processed tuple entry, don't do anything to original • group tuples by area code obtained from processed tuple entry 1 package com.example; 2 3 import javaslang.Tuple; 4 import javaslang.Tuple2; 5 import javaslang.collection.HashMap; 6 import javaslang.collection.HashSet; 7 import javaslang.collection.Map; 8 import javaslang.collection.Set; 9 import org.junit.Test; 10 11 import static java.util.function.Function.identity; 12 import static com.example.PhoneNumberData.*; 13 14 public class GroupNumbersTest { 15 16 @Test 17 public void processNumbers() { 18 Map<String, Set<String>> map = phoneNumbers 19 .filter(num -> num.length() >= 9) 20 .map(number -> Tuple.of(number, number)) 21 .map(tuple -> 22 tuple.map( 23 num -> num.replaceFirst("^+?48", ""), 24 identity() 25 ) 26 ) 27 .groupBy(tuple -> tuple._1().substring(0, 2)) 28 .bimap( 29 key -> areas.get(key).getOrElse("Nieznany"), 30 value -> value.map(Tuple2::_2) 31 ); 32 33 System.out.println(map); 34 } 35 36 }
  • 133.
    Processing! • filter outnumbers that are too short • we need to have original number and still process it - Tuples! • strip international code from processed tuple entry, don't do anything to original • group tuples by area code obtained from processed tuple entry • map keys and values to either area name and set of original phone numbers 1 package com.example; 2 3 import javaslang.Tuple; 4 import javaslang.Tuple2; 5 import javaslang.collection.HashMap; 6 import javaslang.collection.HashSet; 7 import javaslang.collection.Map; 8 import javaslang.collection.Set; 9 import org.junit.Test; 10 11 import static java.util.function.Function.identity; 12 import static com.example.PhoneNumberData.*; 13 14 public class GroupNumbersTest { 15 16 @Test 17 public void processNumbers() { 18 Map<String, Set<String>> map = phoneNumbers 19 .filter(num -> num.length() >= 9) 20 .map(number -> Tuple.of(number, number)) 21 .map(tuple -> 22 tuple.map( 23 num -> num.replaceFirst("^+?48", ""), 24 identity() 25 ) 26 ) 27 .groupBy(tuple -> tuple._1().substring(0, 2)) 28 .bimap( 29 key -> areas.get(key).getOrElse("Nieznany"), 30 value -> value.map(Tuple2::_2) 31 ); 32 33 System.out.println(map); 34 } 35 36 }
  • 134.
  • 135.
    Results: HashMap(
 (Kielce, HashSet(+48413456990, +48413422345,413572456)), 
 (Kraków, HashSet(+48128275242, 48126434972)), 
 (Warszawa, HashSet(+48224914634, 48225697246))
 )
  • 136.
    Results: HashMap(
 (Kielce, HashSet(+48413456990, +48413422345,413572456)), 
 (Kraków, HashSet(+48128275242, 48126434972)), 
 (Warszawa, HashSet(+48224914634, 48225697246))
 ) ... neat!

  • 137.
    Results: HashMap(
 (Kielce, HashSet(+48413456990, +48413422345,413572456)), 
 (Kraków, HashSet(+48128275242, 48126434972)), 
 (Warszawa, HashSet(+48224914634, 48225697246))
 ) ... neat!
 But what if that was an infinite stream of phone numbers?
  • 138.
    1 package com.example; 2 3import javaslang.Tuple; 4 import javaslang.collection.HashMap; 5 import javaslang.collection.Map; 6 import rx.Observable; 7 8 public class StreamingPhoneNumberData { 9 10 public static final Observable<String> phoneNumbers = 11 Observable.from(new String[]{ 12 "+48413422345", 13 "413572456", 14 "+48413456990", 15 "48225697246", 16 "+48224914634", 17 "48126434972", 18 "+48128275242" 19 }); 20 21 public static final Map<String, String> areas = 22 HashMap.ofEntries( 23 Tuple.of("41", "Kielce"), 24 Tuple.of("22", "Warszawa"), 25 Tuple.of("12", "Kraków") 26 ); 27 28 } Event streams 
 with RxJava
  • 139.
    1 package com.example; 2 3import javaslang.Tuple; 4 import javaslang.collection.HashMap; 5 import javaslang.collection.Map; 6 import rx.Observable; 7 8 public class StreamingPhoneNumberData { 9 10 public static final Observable<String> phoneNumbers = 11 Observable.from(new String[]{ 12 "+48413422345", 13 "413572456", 14 "+48413456990", 15 "48225697246", 16 "+48224914634", 17 "48126434972", 18 "+48128275242" 19 }); 20 21 public static final Map<String, String> areas = 22 HashMap.ofEntries( 23 Tuple.of("41", "Kielce"), 24 Tuple.of("22", "Warszawa"), 25 Tuple.of("12", "Kraków") 26 ); 27 28 } Event streams 
 with RxJava
  • 140.
    1 package com.example; 2 3import javaslang.Tuple; 4 import javaslang.collection.HashMap; 5 import javaslang.collection.Map; 6 import rx.Observable; 7 8 public class StreamingPhoneNumberData { 9 10 public static final Observable<String> phoneNumbers = 11 Observable.from(new String[]{ 12 "+48413422345", 13 "413572456", 14 "+48413456990", 15 "48225697246", 16 "+48224914634", 17 "48126434972", 18 "+48128275242" 19 }); 20 21 public static final Map<String, String> areas = 22 HashMap.ofEntries( 23 Tuple.of("41", "Kielce"), 24 Tuple.of("22", "Warszawa"), 25 Tuple.of("12", "Kraków") 26 ); 27 28 } Event streams 
 with RxJava • Observable<T> is 
 a 0 .. n collection of events (potentially infinite!)
  • 141.
    1 package com.example; 2 3import javaslang.Tuple; 4 import javaslang.Tuple2; 5 import org.junit.Test; 6 import rx.observables.GroupedObservable; 7 8 import static com.example.PhoneNumberData.areas; 9 import static com.example.StreamingData.phoneNumbers; 10 import static java.lang.System.out; 11 import static java.util.function.Function.identity; 12 13 public class RxGroupPhoneNumbersTest { 14 15 @Test 16 public void processNumbers() { 17 phoneNumbers 18 .filter(num -> num.length() >= 9) 19 .map(number -> Tuple.of(number, number)) 20 .map(tuple -> 21 tuple.map( 22 num -> num.replaceFirst("^+?48", ""), 23 identity() 24 ) 25 ) 26 .groupBy(tuple -> tuple._1().substring(0, 2)) 27 .map(grouped -> 28 GroupedObservable.from( 29 areas.get(grouped.getKey()) 30 .getOrElse("Unknown"), 31 grouped.map(Tuple2::_2) 32 ) 33 ) 34 .forEach(grouped -> grouped.forEach(number -> 35 out.println( 36 grouped.getKey() + ": " + number 37 ) 38 ) 39 ); 40 } 41 42 } Event streams 
 with RxJava: 
 reuse your lambdas!
  • 142.
    1 package com.example; 2 3import javaslang.Tuple; 4 import javaslang.Tuple2; 5 import org.junit.Test; 6 import rx.observables.GroupedObservable; 7 8 import static com.example.PhoneNumberData.areas; 9 import static com.example.StreamingData.phoneNumbers; 10 import static java.lang.System.out; 11 import static java.util.function.Function.identity; 12 13 public class RxGroupPhoneNumbersTest { 14 15 @Test 16 public void processNumbers() { 17 phoneNumbers 18 .filter(num -> num.length() >= 9) 19 .map(number -> Tuple.of(number, number)) 20 .map(tuple -> 21 tuple.map( 22 num -> num.replaceFirst("^+?48", ""), 23 identity() 24 ) 25 ) 26 .groupBy(tuple -> tuple._1().substring(0, 2)) 27 .map(grouped -> 28 GroupedObservable.from( 29 areas.get(grouped.getKey()) 30 .getOrElse("Unknown"), 31 grouped.map(Tuple2::_2) 32 ) 33 ) 34 .forEach(grouped -> grouped.forEach(number -> 35 out.println( 36 grouped.getKey() + ": " + number 37 ) 38 ) 39 ); 40 } 41 42 } Event streams 
 with RxJava: 
 reuse your lambdas!
  • 143.
    1 package com.example; 2 3import javaslang.Tuple; 4 import javaslang.Tuple2; 5 import org.junit.Test; 6 import rx.observables.GroupedObservable; 7 8 import static com.example.PhoneNumberData.areas; 9 import static com.example.StreamingData.phoneNumbers; 10 import static java.lang.System.out; 11 import static java.util.function.Function.identity; 12 13 public class RxGroupPhoneNumbersTest { 14 15 @Test 16 public void processNumbers() { 17 phoneNumbers 18 .filter(num -> num.length() >= 9) 19 .map(number -> Tuple.of(number, number)) 20 .map(tuple -> 21 tuple.map( 22 num -> num.replaceFirst("^+?48", ""), 23 identity() 24 ) 25 ) 26 .groupBy(tuple -> tuple._1().substring(0, 2)) 27 .map(grouped -> 28 GroupedObservable.from( 29 areas.get(grouped.getKey()) 30 .getOrElse("Unknown"), 31 grouped.map(Tuple2::_2) 32 ) 33 ) 34 .forEach(grouped -> grouped.forEach(number -> 35 out.println( 36 grouped.getKey() + ": " + number 37 ) 38 ) 39 ); 40 } 41 42 } Event streams 
 with RxJava: 
 reuse your lambdas! • highlighted code is the same as code used on Javaslang collection!
  • 144.
    1 package com.example; 2 3import javaslang.Tuple; 4 import javaslang.Tuple2; 5 import org.junit.Test; 6 import rx.observables.GroupedObservable; 7 8 import static com.example.PhoneNumberData.areas; 9 import static com.example.StreamingData.phoneNumbers; 10 import static java.lang.System.out; 11 import static java.util.function.Function.identity; 12 13 public class RxGroupPhoneNumbersTest { 14 15 @Test 16 public void processNumbers() { 17 phoneNumbers 18 .filter(num -> num.length() >= 9) 19 .map(number -> Tuple.of(number, number)) 20 .map(tuple -> 21 tuple.map( 22 num -> num.replaceFirst("^+?48", ""), 23 identity() 24 ) 25 ) 26 .groupBy(tuple -> tuple._1().substring(0, 2)) 27 .map(grouped -> 28 GroupedObservable.from( 29 areas.get(grouped.getKey()) 30 .getOrElse("Unknown"), 31 grouped.map(Tuple2::_2) 32 ) 33 ) 34 .forEach(grouped -> grouped.forEach(number -> 35 out.println( 36 grouped.getKey() + ": " + number 37 ) 38 ) 39 ); 40 } 41 42 } Event streams 
 with RxJava: 
 reuse your lambdas! • highlighted code is the same as code used on Javaslang collection! • we are operating on potentially infinite stream, so groupBy returns Observable of GroupedObservables - a stream of streams of values grouped by key!
  • 145.
    1 package com.example; 2 3import javaslang.Tuple; 4 import javaslang.Tuple2; 5 import org.junit.Test; 6 import rx.observables.GroupedObservable; 7 8 import static com.example.PhoneNumberData.areas; 9 import static com.example.StreamingData.phoneNumbers; 10 import static java.lang.System.out; 11 import static java.util.function.Function.identity; 12 13 public class RxGroupPhoneNumbersTest { 14 15 @Test 16 public void processNumbers() { 17 phoneNumbers 18 .filter(num -> num.length() >= 9) 19 .map(number -> Tuple.of(number, number)) 20 .map(tuple -> 21 tuple.map( 22 num -> num.replaceFirst("^+?48", ""), 23 identity() 24 ) 25 ) 26 .groupBy(tuple -> tuple._1().substring(0, 2)) 27 .map(grouped -> 28 GroupedObservable.from( 29 areas.get(grouped.getKey()) 30 .getOrElse("Unknown"), 31 grouped.map(Tuple2::_2) 32 ) 33 ) 34 .forEach(grouped -> grouped.forEach(number -> 35 out.println( 36 grouped.getKey() + ": " + number 37 ) 38 ) 39 ); 40 } 41 42 } Event streams 
 with RxJava: 
 reuse your lambdas! • highlighted code is the same as code used on Javaslang collection! • we are operating on potentially infinite stream, so groupBy returns Observable of GroupedObservables - a stream of streams of values grouped by key!
  • 146.
  • 147.
    Use cases of
 ReactiveExtensions • Streams of events
  • 148.
    Use cases of
 ReactiveExtensions • Streams of events • Parallel processing
  • 149.
    Use cases of
 ReactiveExtensions • Streams of events • Parallel processing • Asynchronous programming
  • 150.
    Use cases of
 ReactiveExtensions • Streams of events • Parallel processing • Asynchronous programming • Bonus: functional error handling
  • 151.
    Use cases of
 ReactiveExtensions • Streams of events • Parallel processing • Asynchronous programming • Bonus: functional error handling • Bonus: reactive streams specification
  • 152.
  • 153.
    Functional Programming in Java:not needed, right? • Microservices need linear scalability on instance level, which is hard to achieve using thread-per-request model, asynchronous frameworks offer this 
 out-of-the-box
  • 154.
    Functional Programming in Java:not needed, right? • Microservices need linear scalability on instance level, which is hard to achieve using thread-per-request model, asynchronous frameworks offer this 
 out-of-the-box • Reactive, event-driven programming model has already been included into latest Java version - reactive streams interfaces made it to JDK9!
  • 155.
    Also - Spring5: 1 @GetMapping("/accounts/{id}/alerts") 2 public Flux<Alert> getAccountAlerts(@PathVariable Long id) { 3 4 return this.repository.getAccount(id) 5 .flatMap(account -> 6 this.webClient 7 .perform(get("/alerts/{key}", account.getKey())) 8 .extract(bodyStream(Alert.class)) 9 ); 10 }
  • 156.
    And Vert.x: 1 server.requestStream() 2.toObservable() 3 .subscribe(request -> 4 request.toObservable() 5 .lift(RxHelper.unmarshaller(MyPojo.class)) 6 .map(Marshaller::toMongoDocument) 7 .flatMap(jsonObject -> mongo.insertObservable(MY_POJOS, jsonObject)) 8 .subscribe( 9 () -> request.response() 10 .setStatusCode(200) 11 .end(), 12 err -> request.response() 13 .setStatusCode(500) 14 .end() 15 ) 16 );
  • 157.