functional java 8 - introduction
TRANSCRIPT
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 }
Optionalityof 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 }
Optionalityof 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 }
Optionalityof 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 }
Optionalityof 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 }
Optionalityof 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 }
Optionalityof 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 }
Optionalityof 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 }
Optionalityof 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 }
Optionalityof 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> fromfirstCharacter 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> fromfirstCharacter 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 APIrevisited: 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 APIrevisited: 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 APIrevisited: 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 APIrevisited: 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 objectsin 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 objectsin plain Java
• lots of getter noise 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 objectsin 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 objectsin 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 objectsin 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 ofReactive Extensions
Use cases ofReactive Extensions
• Streams of events
Use cases ofReactive Extensions
• Streams of events
• Parallel processing
Use cases ofReactive Extensions
• Streams of events
• Parallel processing
• Asynchronous programming
Use cases ofReactive Extensions
• Streams of events
• Parallel processing
• Asynchronous programming
• Bonus: functional error handling
Use cases ofReactive 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!