Functional Java

Copyright 2017 - Jacob D. Parr
https://www.jacobparr.com
(all rights reserved)
Functional Java
Agenda
Chapters
- Introduction
- The Lambda Form
- Functional, Default & Reference Methods
- The Fine Print
- Functions & Collections
- Streams
- Using Streams
- Thinking Functionally
- Conclusion
Functional Java
#1: Introduction
Overview
- The History of Functional Programming
- What is Functional Programming?
- Moore's Law, RIP
- Conciseness - The Dark Side of Functional Programming
- Why has it taken so long to functionalize Java?
- Dysfunctional Java?
- Learning Java 8
Functional Java
#1: Introduction
The History of Functional Programming
Functional Java
#1: Introduction
What is Functional Programming?
Functional Java
#1: Introduction
Moore's Law, RIP
Functional Java
#1: Introduction
Conciseness - The Dark Side of Functional Programming
Functional Java
#1: Introduction
Why has it taken so long to functionalize Java?
Functional Java
#1: Introduction
Dysfunctional Java?
Functional Java
#1: Introduction
Learning Java 8
Functional Java
#2: The Lambda Form
Overview
- What's a Lambda?
- What problems do Lambdas solve?
- Introducing the Java Lambda
- The Exemptable Lambda
Functional Java
#2: The Lambda Form
What's a Lambda?
- 11 Letter of Greek Alphabet
- Origins in Calculus & Computational Theory
- Alonzo Church: new form of calculus
- Now: Anonymous Function
- Closure used interchangeably with Lambda
- It's merely and inline function
- Closure include state
Functional Java
#2: The Lambda Form
What problems do Lambdas solve?
- Practical uses even outside of functional programming
- Behavior-As-Data
- Passing Behavior as Data
- Possible in Java but with terms & conditions
Functional Java
#2: The Lambda Form
What problems do Lambdas solve?
public class Chapter2 extends Chapter2Abstract {
public boolean isBalanceSufficient(Account account, double amount) {
logAccess(account);
boolean isBalanceSufficient = account.getBalance() - amount > 0;
if (isBalanceSufficient == false) {
// It would be nice to let the caller vary this condition
if (account.getCreditRating() > 700) {
isBalanceSufficient = true;
alertOverdraw(account);
}
}
return isBalanceSufficient;
}
}
- Enough Money?
- Enough Credit?
Functional Java
#2: The Lambda Form
What problems do Lambdas solve?
if (account.getCreditRating() > 700) {
- One line of code handles the exceptional case
Functional Java
#2: The Lambda Form
What problems do Lambdas solve?
// Part #1
public boolean isBalanceSufficient(Account account, double amount) {
logAccess(account);
return account.getBalance() - amount > 0;
}
// Part #2
public void doOverdraft(Account account, double amount) {
alertOverdraw(account);
}
// Part #3
public void doSomething(Account anAccount, double anAmount) {
if ( isBalanceSufficient(anAccount, anAmount) == false) {
// Caller can now vary the condition and control the flow
if (anAccount.getCreditRating() > 700) {
doOverdraft(anAccount, anAmount);
}
}
}
- What if we want to let the caller vary the condition?
- Here's a Poor solution
Functional Java
#2: The Lambda Form
What problems do Lambdas solve?
- A very ugly solution
- Results in duplication from multiple withdrawls
- The OO way would call for an interface
public interface Exemptable {
boolean isExempt(Account account);
}
Functional Java
#2: The Lambda Form
What problems do Lambdas solve?
- Then we can use the interface like this
public boolean isBalanceSufficient(Account account, double amount, Exemptable ex) {
logAccess(account);
boolean isBalanceSufficient = account.getBalance() - amount > 0;
if (isBalanceSufficient == false) {
if (ex.isExempt(account)) {
isBalanceSufficient = true;
alertOverdraw(account);
}
}
return isBalanceSufficient;
}
Functional Java
#2: The Lambda Form
What problems do Lambdas solve?
- But calling it will require an anonymous class
public void doSomething(Account anAccount, double anAmount) {
isBalanceSufficient(anAccount, anAmount, new Exemptable() {
@Override
public boolean isExempt(Account account) {
return account.getCreditRating() > 700;
}
});
}
Functional Java
#2: The Lambda Form
What problems do Lambdas solve?
- What's wrong with this: Verbosity
- The only statement we care about is
return account.getCreditRating() > 700; - We need to
- Create an Exemptable interface
- Create a new Exemptable object
- Override the isExempt() method
- Include the @Override annotation
- Test the condition
- Return the value
- A bunch of braces and parentheses
Functional Java
#2: The Lambda Form
What problems do Lambdas solve?
- What's wrong with this: Bilerplate code
- Each implementation of the interface may vary
- Results in code smell
- Hard to improve on in Java
What we want is to pass the behavior.
Functional Java
#2: The Lambda Form
Introducing the Java Lambda
1 2 3 (Parameter declaration) -> {Lambda body}
- Has Parameters in parentheses
- The body defines behavior
- Java 8 introduces the -> operator
- May have no parameters () -> {Lambda body}
- May have no body () -> {}
- Java Strongly typed - required to fit existing language
- All Lambdas are backed by an interface
- At runtime they become full objects
Functional Java
#2: The Lambda Form
The Exemptable Lambda
public void doSomething_1(Account anAccount, double anAmount) {
Exemptable ex = (Account acct) -> {return acct.getCreditRating() > 700; };
isBalanceSufficient(anAccount, anAmount, ex);
}
- With a Lambda
- Replaces some 8 lines of code
- We can get even more compact, later
Functional Java
#2: The Lambda Form
The Exemptable Lambda
- The Lambda conforms to the Exemptable interface because of it's signature (parameter declaration & return type)
- Lambda does not implement Exemptable
- Inferred by the compiler
Here's the logic:
- Is Exemptable a functional interface declaring exactly one method?
- Does the lambda have the same number of parameters as Exemptable.isExempt()?
- Are the lambda's parameters compatible with isExempt()?
- Is the lambda's return type compatible with isExempt()?
- Are thrown exceptions allowed by isExempt()
Functional Java
#2: The Lambda Form
The Exemptable Lambda
- We assigned the lambda to a local variable for clarity
- At runtime an object is created for the Lambda that implements the interface
- The method can be invoked
- Passed to other methods
- Assigned to a variable
- Will be garbage collected
- It even has an equals(), getClass(), hasCode(), notify(), notifyAll(), toString() and wait() functions.
Functional Java
#2: The Lambda Form
The Exemptable Lambda
public void doSomething_2(Account anAccount, double anAmount) {
Exemptable ex = (Account acct) -> acct.getCreditRating() > 700;
isBalanceSufficient(anAccount, anAmount, ex);
}
- Remove: brackets, return and semicolon
- This one-liner is a "Lambda Expression"
- Java 8 provides special syntax previledge
Functional Java
#2: The Lambda Form
The Exemptable Lambda
public void doSomething_3(Account anAccount, double anAmount) {
Exemptable ex = (acct) -> acct.getCreditRating() > 700;
isBalanceSufficient(anAccount, anAmount, ex);
}
- Remove: Parameter type
- The compiler can usually infer the type
- Even with multiple parameters
Functional Java
#2: The Lambda Form
The Exemptable Lambda
public void doSomething_4(Account anAccount, double anAmount) {
Exemptable ex = acct -> acct.getCreditRating() > 700;
isBalanceSufficient(anAccount, anAmount, ex);
}
- Remove: Parenthesis
- Parenthesis are not needed for single parameters
- They are required for multiple parameters.
- This is the preferred form
- Unless we need to resolve parameter type ambiguity
Functional Java
#2: The Lambda Form
The Exemptable Lambda
public void doSomething_5(Account anAccount, double anAmount) {
isBalanceSufficient(anAccount, anAmount, acct -> acct.getCreditRating() > 700);
}
- We don't need the Exemptable interface.
- The compiler will figure it out by matching to the parameter type of the method it's being passed to.
Functional Java
#3: Func, Def, & Ref. Methods
Overview
- Functional Interfaces
- Using Functional Interfaces
- Introducing Method References
- Method References as Bridges
- Method Reference Varieties
- Constructor Method References Variants
- Custom vs. Standard Functional Interfaces
- Anonymous Classes vs Lambdas
- Default Methods
- Functional Interface Extension
- Static Method References
Functional Java
#3: Func, Def, & Ref. Methods
Functional Interfaces
- Function interfaces are a special type of interface against which lambdas are defined.
- Exactly one method
- Single Abstract Method rule (SAM)
- Illegal to declare more than one method
- Enforced with the @FunctionalInterface annotation
- @FunctionalInterface remains optional
- Function vs Method
@FunctionalInterface
public interface Exemptable {
boolean isExempt(Account account);
// won't compile.
boolean someOtherFunction();
}
Functional Java
#3: Func, Def, & Ref. Methods
Using Functional Interfaces
- Let's say we want to allow users to add more behavior when we do actually overdraft
- Introducing a new interface
@FunctionalInterface
interface AccountExemptionHandler {
public void onAccountExempted(Account account);
}
Functional Java
#3: Func, Def, & Ref. Methods
Using Functional Interfaces
- As a 4th parameter, the caller can specify what happens when we overdraft.
public boolean isBalanceSufficient(Account account, double amount, Exemptable ex, AccountExemptionHandler handler) {
logAccess(account);
boolean isBalanceSufficient = account.getBalance() - amount > 0;
if (isBalanceSufficient == false) {
if (ex.isExempt(account)) {
isBalanceSufficient = true;
// Give the caller the opportunity to do something extra
handler.onAccountExempted(account);
}
}
return isBalanceSufficient;
}
Functional Java
#3: Func, Def, & Ref. Methods
Using Functional Interfaces
- Let's try our first usage with an interface.
public void doSomething_1(Account anAccount, double anAmount) {
AccountExemptionHandler anonymousAccountExemptionHandler = new AccountExemptionHandler() {
@Override
public void onAccountExempted(Account account) {
System.out.println(account);
}
};
isBalanceSufficient(
anAccount,
anAmount,
acct -> acct.getCreditRating() > 700,
anonymousAccountExemptionHandler);
}
Functional Java
#3: Func, Def, & Ref. Methods
Using Functional Interfaces
- And now with a Lambda...
- The difference here is that AEH is just a bridge to println()
public void doSomething_2(Account anAccount, double anAmount) {
AccountExemptionHandler anonymousAccountExemptionHandler = new AccountExemptionHandler() {
@Override
public void onAccountExempted(Account account) {
System.out.println(account);
}
};
isBalanceSufficient(
anAccount,
anAmount,
acct -> acct.getCreditRating() > 700,
acct -> System.out.println(acct) );
}
Functional Java
#3: Func, Def, & Ref. Methods
Introducing Method References
public void doSomething_3(Account anAccount, double anAmount) {
AccountExemptionHandler anonymousAccountExemptionHandler = new AccountExemptionHandler() {
@Override
public void onAccountExempted(Account account) {
System.out.println(account);
}
};
isBalanceSufficient(
anAccount,
anAmount,
acct -> acct.getCreditRating() > 700,
System.out::println);
}
- When the Lambda is just bridging to function calls
- We can use a method reference instead ::
Functional Java
#3: Func, Def, & Ref. Methods
Introducing Method References
This works because...
void println(Object)
matches
void onAccountExempted(Account
Functional Java
#3: Func, Def, & Ref. Methods
Introducing Method References

Functional Java
#3: Func, Def, & Ref. Methods
Introducing Method References
- Java compiler automatically links the Account parameter to the Object parameter.
- The compiler is led to believe that println(..) is a suitable implementer of AccountExemptionHandler
- The magic lies in signature matching
- One step closer to an imperative language
- Can be used anywhere
- Can have any number of parameters
- No need to specify parameters because they are matched
- Applied to a functional interface
Functional Java
#3: Func, Def, & Ref. Methods
Method References as Bridges
isBalanceSufficient(
anAccount,
anAmount,
acct -> acct.getCreditRating() > 700, // cannot use a method reference here
System.out::println);
- This won't work
- Parameter three is not just a bridge
- We can make this work by creating a static method
public static boolean defaultExemption(Account account) {
return account.getCreditRating() > 700;
}
- And then use it like this:
isBalanceSufficient(
anAccount,
anAmount,
Banker::defaultExemption, // THIS WORKS
System.out::println);
Functional Java
#3: Func, Def, & Ref. Methods
Method References as Bridges
Two key points
- Can sub any lambda block or expression if we are willing to write intermediary methods
- When used this way, code becomes "labeled" and reusable
Additionally, this gives us choices: Lambda expression, blocks or method references
- Use method references when the lambda is just a bridge
- Use expressions to keep short
- Use blocks when slightly more complex
Functional Java
#3: Func, Def, & Ref. Methods
Method Reference Varieties
6 Different Varieties
-
Static: Class:method
Used to refer to a static method of a class -
Instance: variable::method
Used to refer to an instance method of a class -
Super: super::method
Used to refer to an instance method found in a parent class -
Constructor: Class::new
Used to refer to a constructor of a class -
Generic Type Constructors: Class<Type>::new
Used to refer to a constructor of a generic class -
Array Constructors: Class[]::new
Used to refer to a constructor of an array
Functional Java
#3: Func, Def, & Ref. Methods
Method Reference Varieties
public void runTheBank_1(Account account, double amount) {
new Banker() {
public void runTheBank(Account account, double amount) {
isBalanceSufficient(account, amount,
Banker::defaultExemption, // Static method reference
this::instanceAccountExemptionHandler // Instance method reference
);
}
};
}
- We've seen Static instance
- We've seen this instance from within a Banker
- Another form of this uses a variable reference
public void runTheBank_2(Account account, double amount) {
Banker banker = new Banker();
banker.isBalanceSufficient(account, amount,
Banker::defaultExemption, // Static method reference
banker::instanceAccountExemptionHandler // Instance method reference
);
}
Functional Java
#3: Func, Def, & Ref. Methods
Method Reference Varieties
public void runTheBank_3(Account account, double amount) {
new Banker() {
public void runTheBank(Account account, double amount) {
isBalanceSufficient(account, amount,
Banker::defaultExemption, // Static method reference
super::superAccountExemptionHandler // Super method reference
);
}
};
}
- For super refences...
Functional Java
#3: Func, Def, & Ref. Methods
Constructor Method References Variants
- Three constructor variations
@FunctionalInterface
public interface AccountCreator {
public Account create(String aDefault);
}
@FunctionalInterface
public interface ListCreator<T> {
public T create();
}
@FunctionalInterface
public interface AccountArrayCreator {
public Account[] create(int count);
}
Functional Java
#3: Func, Def, & Ref. Methods
Constructor Method References Variants
Take a look at makeDefaultAccounts(..)
public List<Account> makeDefaultAccounts (int count,
AccountCreator accountCreator,
ListCreator<List<Account>> listCreator) {
List<Account> returnList = listCreator.create();
for (int index = 0; index < count; ++index) {
returnList.add(accountCreator.create("default"));
}
return returnList;
}
Functional Java
#3: Func, Def, & Ref. Methods
Constructor Method References Variants
- We can call it like this with Lambdas...
public void runTheBank_4() {
Banker banker = new Banker();
banker.makeDefaultAccounts(10,
id -> new Account(id),
() -> new LinkedList<>());
}
- Or with function references...
public void runTheBank_5() {
Banker banker = new Banker();
banker.makeDefaultAccounts(10,
Account::new, // Constructor Reference
LinkedList<Account>::new); // Generic Constructor Reference
}
Functional Java
#3: Func, Def, & Ref. Methods
Constructor Method References Variants
Take a look at makeArrayDefaultAccounts(..)
public Account[] makeArrayDefaultAccounts (int count,
AccountCreator accountCreator,
AccountArrayCreator accountArrayCreator) {
Account[] returnList = accountArrayCreator.create(count);
for (int index=0; index < count; ++index) {
returnList[index] = accountCreator.create("default");
}
return returnList;
}
Functional Java
#3: Func, Def, & Ref. Methods
Constructor Method References Variants
- We can call it like this with Lambdas...
public void runTheBank_6() {
Banker banker = new Banker();
banker.makeArrayDefaultAccounts(10,
Account::new,
size -> new Account[size]);
}
- Or with function references...
public void runTheBank_7() {
Banker banker = new Banker();
banker.makeArrayDefaultAccounts(10,
Account::new,
Account[]::new);
}
Functional Java
#3: Func, Def, & Ref. Methods
Custom vs. Standard Functional Interfaces
We've created our own functional interfaces:
- Exemptable
- AccountExemptionHandler
- AccountCreator
- ListCreator
- AccountArrayCreator
This was purely acedemic
- Rarely need to create such interfaces
- JDK 8 ships with 40-something different interfaces
- Used by Collections but available to us
- All five of our interfaces are replaceable by one of Java's
Functional Java
#3: Func, Def, & Ref. Methods
Anonymous Classes vs Lambdas
- Do Java's anonymous classes still have a place?
- Easier transition into functional programming
- Java 8 permits the use of anonymous classes as incarnations of functional interfaces
- We can use anonymous interfaces anywhere a function interface is expected
- A good Java IDE (ie IntelliJ by JetBrains) does exactly that.
- Still required for for object-oriented programming style
- Lambdas are meant for behavior-only constructs that fit functional programming philosophy
- Cannot blindly substitute all anonymous functions with Lambdas
Functional Java
#3: Func, Def, & Ref. Methods
Anonymous Classes vs Lambdas
public abstract class ObjectOrientedTimedRunnable implements Runnable {
private long executionDuration;
@Override
public abstract void run();
public final void runTimed() throws InterruptedException {
long startTime = System.currentTimeMillis();
Thread thread = new Thread(this);
thread.start();
thread.join();
executionDuration = System.currentTimeMillis() - startTime;
}
public long getExecutionDuration() {
return executionDuration;
}
}
- Take ObjectOrientedTimedRunnable as an example.
Functional Java
#3: Func, Def, & Ref. Methods
Anonymous Classes vs Lambdas
Designed in an object-oriented conversational style forcing us to use anonymous classes because...
- State is kept between creation, runTimed() and
getExecutionDuration(). Lambdas forbid state - Lambdas must be backed by a functional interface.
This class is abstract. - This class also defines other public methods. Lambdas can only be backed by functional interfaces, which only allow on method to implement.
We can fix this with a rewrite, but we have to address all three issues listed above.
Functional Java
#3: Func, Def, & Ref. Methods
Anonymous Classes vs Lambdas
- The first step is to remove all states from the abstract class:
public abstract class StatelessTimedRunnable implements Runnable {
public abstract void run();
public long runTimed() throws InterruptedException {
System.out.println("Running StatelessTimedRunnable");
long startTime = System.currentTimeMillis();
Thread thread = new Thread(this);
thread.start();
thread.join();
return System.currentTimeMillis() - startTime;
}
}
- More concise code
- Can't use a lambda because it's abstract w/two functions
- We cannot have a method body on an interface.
Functional Java
#3: Func, Def, & Ref. Methods
Default Methods
- Guess what!
- Java 8 now supports methods on interfaces
- More specifically defualt methods.
default void doSomething(Object takeSomething) {
// do something
}
- Can be used on functional or regular interfaces
- We can have any number of default methods
- Default methods are not considered abstract
- Thus they don't apply to the SAM rule
Functional Java
#3: Func, Def, & Ref. Methods
Default Methods
@FunctionalInterface
public interface FunctionalTimedRunnable extends Runnable {
default long runTimed() throws InterruptedException {
printInterfaceName("FunctionalTimedRunnable");
long startTime = System.currentTimeMillis();
Thread thread = new Thread(this);
thread.start();
thread.join();
return System.currentTimeMillis() - startTime;
}
default void launchTest() throws InterruptedException {
System.out.println("This thread executed for: " + runTimed() + " ms.");
}
default void printInterfaceName(String interfaceName) {
System.out.println("Running " + interfaceName);
}
}
- We can now convert into a functional interface
Functional Java
#3: Func, Def, & Ref. Methods
Default Methods
- Default methods can be invoked by other default methods
- Can be overridden by sub-interfaces
- Usual Java polymorphism takes effect.
- Default methods can only be public or package-private
(just like regular interface methods) - They cannot be final
public void timeThis_2() throws InterruptedException {
FunctionalTimedRunnable timedRunnable = () -> { /* do something */};
timedRunnable.launchTest();
}
Functional Java
#3: Func, Def, & Ref. Methods
Default Methods
- Born out of necessity
- Keeping the Collections library backward compatible
- Originally modeled as a deep hierarchy of interfaces
- Collection
- List
- Map
- Set
- Collections had to be enriched to make Lambdas truly useful as in functional programming.
- Changing these libraries would have broke thousands of applications and 3rd party libraries.
- Functional interface with default methods is a purely behavior-only construct.
Functional Java
#3: Func, Def, & Ref. Methods
Functional Interface Extension
- Can we add behavior via inheritance? Yes
- Can be extended like other interfaces
- Here AverageTimedRunnable builds on our example
@FunctionalInterface
public interface AverageTimedRunnable extends FunctionalTimedRunnable {
default long runTimed() throws InterruptedException {
printInterfaceName("AverageTimedRunnable");
long timeSum = 0;
for (int count = 0; count < 5; ++count) {
timeSum += FunctionalTimedRunnable.super.runTimed();
}
return Math.round(timeSum / 5);
}
}
- Notice that printInterfaceName() is called normally
- But runTimmed() is now fully qualified:
FunctionalTimedRunnable.super.runTimed()
Functional Java
#3: Func, Def, & Ref. Methods
Functional Interface Extension
- Here super is required to call the parent
- And to resolve ambiguity.
- This only works to call default methods from directly extended interfaces.
- What happens when a default method is inherited via two parents?
Functional Java
#3: Func, Def, & Ref. Methods
Functional Interface Extension
- Let's add a second interface
@FunctionalInterface
public interface MedianTimedRunnable extends FunctionalTimedRunnable {
default long runTimed() throws InterruptedException {
printInterfaceName("MedianTimedRunnable ");
final int sampleSize = 5;
ArrayList<Long> longs = new ArrayList<>(sampleSize);
for (int count = 0; count < sampleSize; ++count) {
longs.add(FunctionalTimedRunnable.super.runTimed());
}
Collections.sort(longs);
return longs.get(sampleSize / 2);
}
}
Functional Java
#3: Func, Def, & Ref. Methods
Functional Interface Extension
- And a third that extends the other two...
@FunctionalInterface
public interface SmartTimedRunnable extends AverageTimedRunnable, MedianTimedRunnable {
default long runTimed() throws InterruptedException {
printInterfaceName("SmartTimedRunnable");
long averageElapsedTime = AverageTimedRunnable.super.runTimed();
// Choose average time if less than 1 second
if (averageElapsedTime < 1000) {
return averageElapsedTime;
} else {
return MedianTimedRunnable.super.runTimed();
}
}
}
Functional Java
#3: Func, Def, & Ref. Methods
Functional Interface Extension

Functional Java
#3: Func, Def, & Ref. Methods
Functional Interface Extension
- We explicitly said which method we want to call:
@FunctionalInterface
public interface SmartTimedRunnable extends AverageTimedRunnable, MedianTimedRunnable {
default long runTimed() throws InterruptedException {
printInterfaceName("SmartTimedRunnable");
long averageElapsedTime = AverageTimedRunnable.super.runTimed();
// Choose average time if less than 1 second
if (averageElapsedTime < 1000) {
return averageElapsedTime;
} else {
return MedianTimedRunnable.super.runTimed();
}
}
}
Functional Java
#3: Func, Def, & Ref. Methods
Functional Interface Extension
When not specifically declared, Java has 3 rules for resolving conflicts caused by multiple inheritence
- Overrides from concrete classes take precedence over interface default methods
- Sub-interface methods take precedence over super-interface methods
- Explicitness takes precedence over inference
Said another way...
- Let Java figure it out
- When Java cannot, be explicit.
Functional Java
#3: Func, Def, & Ref. Methods
Functional Interface Extension
- Java 8 was touted as better than C++ in the early days because it avoided/eliminated this problem.
- Inheriting behavior is one problem
- But inheriting state is a completely separate problem
- default methods avoid some of this issue by ONLY inheriting behavior
- The larger problem of state is still avoided
Functional Java
#3: Func, Def, & Ref. Methods
Static Method References
- It is entirely possible to port a OO program to functional
- Got rid of conversational state
- We could use static methods to achieve a simillar goal
- Static methods in functional interfaces have the same syntax as regular interfaces.
- Must still adhere to the SAM rule
Functional Java
#3: Func, Def, & Ref. Methods
Static Method References
@FunctionalInterface
public interface StaticTimedRunnable extends Runnable {
static long runTimed(StaticTimedRunnable timedRunnable) throws InterruptedException {
printInterfaceName("StaticTimedRunnable");
long startTime = System.currentTimeMillis();
Thread thread = new Thread(timedRunnable);
thread.start();
thread.join();
return System.currentTimeMillis() - startTime;
}
static void launchTest(StaticTimedRunnable runnable) throws InterruptedException {
System.out.println("This thread executed for: " + runTimed(runnable) + " ms.");
}
static void printInterfaceName(String interfaceName) {
System.out.println("Running " + interfaceName);
}
}
Functional Java
#3: Func, Def, & Ref. Methods
Static Method References
- Very similar to FunctionalTimedRunnable except that the method launchTest() now needs to be given a
StaticTimedRunnable. - It does not have reference to the this instance
public void timeThis_3() throws InterruptedException {
StaticTimedRunnable.launchTest( () -> {/* do something */} );
}
Functional Java
#4: The Fine Print
Overview
- Lexical Scoping Rules
- Read/Write Access to Enclosing Class Attributes
- Read Access to Enclosing Local Variable
- Write Access to Enclosing Local Variable
- Shadowing Local Variables
- Mutating State of Local or Class Attributes
- Lambda Leftovers
- Lambda Byte Code
Functional Java
#4: The Fine Print
Lexical Scoping Rules

Functional Java
#4: The Fine Print
Lexical Scoping Rules
Let's start with a new functional interface...
@FunctionalInterface
public interface LambdaExecutor {
public void execute(Object object);
}
Functional Java
#4: The Fine Print
Read/Write Access to Enclosing Class Attributes
- Lambdas can read and write class attributes
- Here attribute is assigned the value 1
- This goes against the grain of functional programming
- But it's still legal, Java code.
- Reminder of the compromise between Java and adherence to functional principles.
public class LexicalScoping {
private Object attribute;
public void readWriteClassAttributes() throws Exception {
// attribute is a class attribute that can be read and
// written inside of lambdas
LambdaExecutor executor = (anInteger) -> attribute = anInteger;
executor.execute(1);
System.out.println(attribute); // prints 1
}
}
Functional Java
#4: The Fine Print
Read Access to Enclosing Local Variable
- Local variables are tricker
- Java requires them to be final when accessed by anonymous inner classes.
- Same is true for lambdas
- Java 1-7: the final keyword was required
- Java 8: we now have effectively final variables.
- Mutation is still forbidden
public class LexicalScoping {
public void readLocalVariable() {
// No need to declare localAttribute as final in Java 8
Object localAttribute = 1;
// Accessing a local attribute within a lambda is permitted
LambdaExecutor executor = anObject ->
System.out.println("Accessing localAttribute: " + localAttribute);
executor.execute(null);
}
}
Functional Java
#4: The Fine Print
Write Access to Enclosing Local Variable
- Anonymous classes cannot mute local variables
- Lambdas cannot mute local variables
public void writeEffectivelyFinal() {
Object localAttribute = 1;
// localAttribute will now lose its effectively final status and
// will no longer be allowed within the lambda.
localAttribute = 2;
LambdaExecutor executor = anInteger ->
System.out.println("Accessing localAttribute: " + localAttribute);
executor.execute(null); }
}
Functional Java
#4: The Fine Print
Write Access to Enclosing Local Variable
- Java had a good reason for this rule
- Lambda's give us even more good reason
- If someone calls this method and get's a lambda and later executes it, the object localAttribute is out of scope.
- Or what if someone modified it between creation and execution
- This will also cause problems for multi-thread use cases
public LambdaExecutor getLambdaExecutor() {
Object localAttribute = 1;
// Not legal because localAttribute annot be modified
return anObject -> localAttribute = 2;
}
Functional Java
#4: The Fine Print
Shadowing Local Variables
- Shadowing of variables in Lambdas is not allowed but it is allowed in anonymous inner classes.
public void shadowLocalVariables() {
Object localAttribute; // 1st declaration
LambdaExecutor executor = localAttribute -> // 2nd declaration
System.out.println("Accessing localAttribute: " + localAttribute);
executor.execute(1);
}
public void shadowLocalVariablesAnonymous() {
Object localAttribute;
LambdaExecutor executor = new LambdaExecutor() {
@Override public void execute(Object localAttribute) {
// localAttribute shadowing is legal for anonymous classes
// Do something
}
};
executor.execute(1);
}
Functional Java
#4: The Fine Print
Mutating State of Local or Class Attributes
- Java offers no automatic mechanism to manage inner state mutation of class attributes
- Java offers no way to stop this
public void mutatingState() {
// Assign a StringBuffer to the class attribute named attribute
attribute = new StringBuffer();
LambdaExecutor executor = aString ->
// Inner state of attribute is mutated inside the lambda
((StringBuffer) attribute).append(aString); executor.execute("another string");
}
Functional Java
#4: The Fine Print
Lambda Leftovers
- You can reject functional programming
- But Lambdas still bring a lot of value to the JVM
- Even if only as a replacement for anonymous inner classes
- They work with interfaces only
- @FunctionalInterface
- Lambdas are stateless?
- Transitional State vs
- Conversational State
- State only exists in the form of
- method parameters
- local variables
- public static variables
Functional Java
#4: The Fine Print
Lambda Leftovers
- Lambdas are also single-use constructs
- Every invocation is a new object/instance
- It's garbage collected just like all other objects
- Re-invocation repeats the creation/invocation/destruction
- There is no lambda object pooling
- There is no caching
- Lambdas cannot be extended, but extend Object
- They have getClass(), hasCode(), equals(), and toString()
- The Java compiler provides a default implementation
- One peculiarity is the getClass().getName()
HolderClass$$Lambda$n - Once instantiated they are indistinguishable from other objects.
Functional Java
#4: The Fine Print
Lambda Leftovers
- Lambdas cannot be declared and invoked all at once
- They must be assigned to a variable
- This is legal:
new StringBuilder("Hello").append("There");
- But this is not:
@FunctionalInterface
public interface StringExecutor {
public String execute(String text);
}
public void test_2() {
(StringExecutor executor = s -> s + "There").execute("Hello");
}
Functional Java
#4: The Fine Print
Lambda Leftovers
- Lambdas can throw exceptions
- If they throw checked exceptions the backing interface must support those exceptions.
- They can be generic - but it does make it harder to read
- Parentheses are optional for single parameter Lambdas
- They are required for multi-parameters
- The Lambda parameters are inferred
- Parameter types are only required to resolve ambiguity such as with generic parameters
- We can ditch return, semicolon & braces with expressions
- They are required with blocks and there is no limit to the size of the blocks short of readability and reusablity.
Functional Java
#4: The Fine Print
Lambda Byte Code
- Java 7 introduced the invoke dynamic instruction to the byte code as part of JSR-292.
- Originally intended for other JVM languages
- Java 8 uses this instruction natively for lambdas.
- invoke dynamic allows method calls to be bound to their destination at runtime - compared to compile time
- Anonymous classes generate their own class files but lambdas do not and the Lambda byte-code is more concise
Functional Java
#5: Functions & Collections
Overview
- Functional Interfaces
- The Consumer Functional Interface Family
- The Function Functional Interface Family
- The Predicate Functional Interface Family
- The Supplier Functional Interface Family
- Other Functional Interfaces
- Functional Composition
- Consumer Composition
- Predicate Composition
- Function Composition
- Functionalizing Collections
- Collection Interface
- Map Interface
- Spliterator
Functional Java
#5: Functions & Collections
Functional Interfaces
- Lambdas must be backed by a functional interface
- It would VERY inconvenient to have to create a new Functional interface for every Lambda
- Java addresses this with 40-something function interfaces
- The typical Lambda will usually fit one of these
- Unless you need very particular method signatures
Did you say 40?
Functional Java
#5: Functions & Collections
Functional Interfaces

Functional Java
#5: Functions & Collections
The Consumer Functional Interface Family
Archetype
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
Abstract
Consume and discard
- BiConsumer<T, U>
- DoubleConsumer
- IntConsumer
- LongConsumer
- ObjDoubleConsumer<T>
- ObjIntConsumer<T>
- ObjLongConsumer<T>
Variants
Functional Java
#5: Functions & Collections
The Consumer Functional Interface Family
- Consume & Discard Semantic
- Sender and Receiver are detached from each other
- Since accept() returns void it cannot communicate
a result back to the caller. - Unless the contents of <T> are modified - which is bad form but Java does not prevent
Functional Java
#5: Functions & Collections
The Consumer Functional Interface Family
public static double computeShapeArea(int angles, double measure1, double measure2, Consumer<String> consumer) {
double area;
switch (angles) {
case 0: {
area = Math.PI * Math.pow(measure1, 2);
consumer.accept("Area formula for shape with " + angles + " angles is pi times radius: " + Math.PI + " times " + measure1 + " squared = " + area);
break;
}
case 3: {
area = measure1 * measure2 / 2;
consumer.accept("Area formula for shape with " + angles + " angles is height times base divided by“ + “ 2: " + measure1 + " times " + measure2 + " divided by 2 = " + area);
break;
}
case 4: {
area = measure1 * measure2;
consumer.accept("Area formula for shape with " + angles + " angles is width times length: " + measure1 + " times" + measure2 + " = " + area);
break;
}
default: {
throw new RuntimeException("Unsupported shape with" + angles + "angles");
}
}
return area;
}
Functional Java
#5: Functions & Collections
The Consumer Functional Interface Family
- This method will use a different calculation depending
on the number of angles. - This is done by the Consumer interface
- The caller can do what it wants with the information
public void family_1() {
computeShapeArea(0, 7, 0, System.out::println); // Circle
computeShapeArea(3, 8, 2, System.out::println); // Triangle
computeShapeArea(4, 10, 5, System.out::println); // Rectangle
}
Area formula for shape with 0 angles is pi times radius:
3.141592653589793 times 7.0 squared = 153.93804002589985
Area formula for shape with 3 angles is height times base divided
by 2: 8.0 times 2.0 divided by 2 = 8.0
Area formula for shape with 4 angles is width times length: 10.0
times 5.0 = 50.0
Functional Java
#5: Functions & Collections
The Function Functional Interface Family
Archetype
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
Abstract
Map, transform, compute
- BiFunction<T, U, R>
- BinaryOperator<T>
- DoubleFunction<R>
- DoubleToIntFunction
- DoubleToLongFunction
- IntFunction<R>
- IntToDoubleFunction
- IntToLongFunction
- LongFunction<R>
- LongToDoubleFunction
- LongToIntFunction
- ToDoubleBiFunction<T, U>
- ToDoubleFunction<T>
- ToIntBiFunction<T, U>
- ToIntFunction<T>
- ToLongBiFunction<T, U>
- ToLongFunction<T>
- DoubleBinaryOperator
- DoubleUnaryOperator
- IntBinaryOperator
- IntUnaryOperator
- LongBinaryOperator
- LongUnaryOperator
- UnaryOperator<T>
Variants
Functional Java
#5: Functions & Collections
The Function Functional Interface Family
- Transforms one value to another
- Of the same kind or different kind
- As a generic type, <T> and <R> may be specified
- Some are marked as XxxXxxOperator - these are
non-generic versions of the XxxXxxFunction counterparts - Let's refactor to let the caller specify the formula
public static double computeShapeArea(double measure1,
double measure2,
BiFunction<Double,Double,Double> function) {
return function.apply(measure1, measure2);
}
public void family_2() {
computeShapeArea(7, 0, (m1,m2) -> Math.PI * Math.pow(m1, 2)); // Circle
computeShapeArea(8, 2, (m1,m2) -> m1 * m1 / 2); // Triangle
computeShapeArea(10, 5, (m1,m2) -> m1 * m2); // Rectangle
}
Functional Java
#5: Functions & Collections
The Predicate Functional Interface Family
Archetype
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
Abstract
Test, Filter
- BiPredicate<T, U>
- DoublePredicate
- IntPredicate
- LongPredicate
Variants
Functional Java
#5: Functions & Collections
The Predicate Functional Interface Family
- Used for test and filters
- Only four variants, two if you consider three are just for specific data types (Int, Long & Double)
public static double computeShapeArea(double measure1,
double measure2,
DoubleBinaryOperator function,
BiPredicate<Double, Double> roundUpPredicate) {
double area = function.applyAsDouble(measure1, measure2);
// Let the predicate decide to round or not
return roundUpPredicate.test(measure1, measure2) ? Math.rint(area) : area;
}
public void test() {
computeShapeArea(7, 0, (r, whatever) -> Math.PI * Math.pow(r, 2), // Circle
(r, whatever) -> r > 100);
computeShapeArea(8, 2, (b, h) -> b * h / 2, // Triangle
(b, h) -> b > 100 || h > 100);
computeShapeArea(10, 5, (w, h) -> w * h, // Rectangle
(w, h) -> w > 100 || h > 100);
}
Functional Java
#5: Functions & Collections
The Supplier Functional Interface Family
Archetype
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
Abstract
Create
- BooleanSupplier
- DoubleSupplier
- IntSupplier
- LongSupplier
Variants
Functional Java
#5: Functions & Collections
The Supplier Functional Interface Family
- Designed for lambdas that must supply objects through creation or other means
- We can use this to supply the parameters for our tests
public static double computeShapeArea(Supplier<Double> supplier1, Supplier<Double> supplier2,
DoubleBinaryOperator function) {
return function.applyAsDouble(supplier1.get(), supplier2.get());
}
public void test() {
computeShapeArea(
() -> 7.0,
() -> 0.0,
(r, whatever) -> Math.PI * Math.pow(r, 2)); // Circle
computeShapeArea(
() -> 8.0,
() -> 2.0,
(b, h) -> b * h / 2); // Triangle
computeShapeArea(
() -> 10.0,
() -> 5.0,
(w, h) -> w * h); // Rectangle
}
Functional Java
#5: Functions & Collections
Other Functional Interfaces
- In addition to the new interface an old one as been revamped for Java 8: Comparator
- Now is marked with @FunctionInterface
- The semantically-related Comparable interface, however, is still not.
- Comparable is a an object-oriented solution that holds state (a reference to this)
- Comparator also defines a slew of default and static methods to facility functional composition
Functional Java
#5: Functions & Collections
Functional Composition
- The standard functional interfaces deal in the currency of behavior
- These interfaces include default methods enabling a key aspect of functional programming: functional composition
- Combine two or more functions to produce super functions
Functional Java
#5: Functions & Collections
Consumer Composition
- The Consumer interface defines the default method andThen()
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
- One representation of higher order functions - the idea of a function returning another function
- andThen() creates a new Consumer
- The caller is effectively blind to the swap
Functional Java
#5: Functions & Collections
Consumer Composition
Logger logger = Logger.getLogger("ConsumerLogger");
Consumer<String> printToOut = System.out::println;
Consumer<String> printToLog = (s) -> {
// Log the message as severe if it contains the work "critical"
if (s.contains("critical")) {
logger.severe(s);
} else {
logger.info(s);
}
};
public void test() {
Consumer<String> superPrinter = printToOut.andThen(printToLog);
// or
superPrinter = printToOut.andThen(printToLog).andThen(s -> {/* and yet one more thing */} );
}
Functional Java
#5: Functions & Collections
Predicate Composition
- Predicates deal with logical operations
- It's abstract method is test(..):boolean
- It has three default methods
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
Functional Java
#5: Functions & Collections
Predicate Composition
- Let's test if a given integer is indivisble
IntPredicate isIndivisible = i -> {
for (int n = 2; n <= Math.sqrt(i); ++n) {
if (i % n == 0) return false;
}
return true;
};
- It could be used to test if a number is prime except that it would return true for 1 but 1 is not prime.
- Let's not break this predicate but rather create a new one
IntPredicate isGreaterThanOne = i -> i > 1;
- An now we can compose the two
IntPredicate isPrime = isGreaterThanOne.and(isIndivisible);
- What about a test for composite? Negating isPrime() would fail for 1. Instead...
IntPredicate isComposite = isIndivisible.negate().and(isGreaterThanOne);
- Simple, but takes time to appreciate and get accustomed to
Functional Java
#5: Functions & Collections
Function Composition
- Function's abstract method is apply(<T>):<R>
- It's well suited for mapping or transformation functions
- It provides two composition methods
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
- Linux provides a great analogy
- A finite set of command-line operations
- Each function is atomic, doing just one thing
- The real power comes from combining in infinite ways
Functional Java
#5: Functions & Collections
Function Composition
- Let's take the Linux commands ps and grep.
- For example: ps -ef | grep java
- Let's take a look at an example:
Function<List<String>, List<String>> ps = null; // whatever
Function<List<String>, List<String>> grep = null; // whatever
Function<List<String>, List<String>> listJavaProcs = ps.andThen(grep);
public void test1() {
for (String nextProcess : listJavaProcs.apply(null)) {
System.out.println(nextProcess);
}
}
- Think of asThen() Java's pipe equivalent.
Function<List<String>, List<String>> wcLInes = null; // whatever
public void test2() {
ps.andThen(grep).andThen(wcLInes);
}
Functional Java
#5: Functions & Collections
Function Composition
- A variation of andThen() is compose()
- It works in reverse order of andThen()
- It calls the given function and the calls itself.
- Our Linux analogy would be vi `which myScript.sh`
Function<List<String>, List<String>> which = null; // whatever
Function<List<String>, List<String>> vi = null; // whatever
Function<List<String>, List<String>> viWhich = vi.compose(which);
- This is equivalent to the call...
Function<List<String>, List<String>> whichVi = which.andThen(vi);
- There is a more subtle difference
- The compose() method's input function must have an output compatible with the input parameter.
- With andThen() it's the opposite
- Or: andThen() read left-to-right, compose() right-to-left
Functional Java
#5: Functions & Collections
Functionalizing Collections
- Externalizing code exposes every step
- Hallmark of imperative programming
- Functional Programming is about internalizing code
- Hide the details - declarative
- FP turns micro patterns into functions
- To support, Java had to revamp its Collections libary
- Because iteration is done over collections
- But we couldn't break the existing API
- Default methods was the right tonic to update and not break the Collections API.
- Brought value to the system, but owe their existence to FP
- But then we need to support parallelization
- The collections API has been updated for parallelization
Functional Java
#5: Functions & Collections
Collection Interface
- The Collections interface adds a new default method
@Override
public void forEach(Consumer<? super E> action) {
c.forEach(action);
}
- Allows the Consumer decide what to do for each element
- The details of how to iterate are not specified
- Let's see what we can do with The Three Stooges
public void test1() {
Collection<String> stooges = Arrays.asList("Larry", "Moe", "Curly");
// Print the contents of the stooges collection
stooges.forEach(System.out::println);
}
- Wide range of applicability
- Should be your de-facto standard
Functional Java
#5: Functions & Collections
Collection Interface
- Some things you cannot do
- Namely you cannot change the state of local variables
- You have to rethink your algorithms
- Perfect for algorithms that do not mutate state
- Let's take a look at another method, removeIf()
@Override
public boolean removeIf(Predicate<? super E> filter) {
throw new UnsupportedOperationException();
}
- This method internalizes the iterating, testing & removing
- We only need to express the condition
public void test2() {
Collection<String> stooges = Arrays.asList("Larry", "Moe", "Curly", "Tom", "Dick", "Harry");
Predicate<String> isAStooge = s -> "Larry".equals(s) || "Moe".equals(s) || "Curly".equals(s);
stooges.removeIf(isAStooge.negate());
}
Functional Java
#5: Functions & Collections
Collection Interface
- Another default method of interest is replaceAll()
@Override
public void replaceAll(UnaryOperator<E> operator) {
throw new UnsupportedOperationException();
}
- Let's see an example...
public void test3() {
List<String> stooges = new ArrayList<>(Arrays.asList("Larry", "Moe", "Curly"));
UnaryOperator<String> feminize = s -> {
if ("Larry".equals(s)) return "Lara";
if ("Moe".equals(s)) return "Maude";
if ("Curly".equals(s)) return "Shirley";
return s;
};
stooges.replaceAll(feminize);
}
- Both replaceIf() and replaceAll() have a caveat...
- The underlying class must support removal
Functional Java
#5: Functions & Collections
Map Interface
- One of the biggest pains with Maps is the constant need to check for the presence of a value's key before adding, updating or removing and element
private Map<Integer, List<String>> movieDatabase = new HashMap<>();
public void addMovieOO(Integer year, String title) {
List<String> movies = movieDatabase.get(year);
if (movies == null) {
// Need to create the array list if it doesn't yet exist
movies = new LinkedList<String>();
movieDatabase.put(year, movies);
}
movies.add(title);
}
Functional Java
#5: Functions & Collections
Map Interface
- Java 8 Offers a better solution with these default methods
V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
default V getOrDefault(Object key, V defaultValue)
default V putIfAbsent(K key, V value)
default V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)
Functional Java
#5: Functions & Collections
Map Interface
- Compute methods allow the map's value to be generated by the specified mapping function.
- The two computeIfXxxx() methods occur conditionally
private Map<Integer, List<String>> movieDatabase = new HashMap<>();
public void addMovieFP(Integer year, String title) {
movieDatabase.computeIfAbsent( year, k -> new LinkedList<>());
movieDatabase.compute(year, (key, value) -> {
value.add(title);
return value;
});
}
- In this case computeIfAbset() is overkill or is it?
movieDatabase.putIfAbsent(year, new LinkedList<>());
movieDatabase.compute(year, (key, value) -> {
value.add(title);
return value;
});
Functional Java
#5: Functions & Collections
Map Interface
- For extracting data we can use getOrDefault()
public void getMovie(Integer year) {
movieDatabase.getOrDefault(year, new LinkedList<>());
}
- One more option is to use the merge() function
- If the key (year) doesn't exist, the list is added to the map
- If the key does exist it calls the BiFunction.
public void addMovies(Integer year, List<String> titles) {
movieDatabase.merge(year, titles, (t1,t2) -> {
t1.addAll(t2);
return t1;
});
}
- Even though Map doesn't implement Collection it does also implement forEach(), replace() & replaceAll()
Functional Java
#5: Functions & Collections
Map Interface
- But the simplest just might be
public void addMovieFP3(Integer year, String title) {
movieDatabase.computeIfAbsent(year, k -> new LinkedList<>())
.add(title);
}
- In this way we don't create an empty list on every call that may or may not be used.
- The new list is only instantiated if the Function is executed, if the key doesn't exist
- The return value is either the existing list or the one returned by the Function.
Functional Java
#5: Functions & Collections
Spliterator
- The Collection library is still subject to the same constraints regarding concurrent access.
- You still have to pick the correct variant corresponding to your thread safety requirements
- These new methods still modify the collection which can be problematic for multi-threaded applications
- There is a new abstraction that is compatible
- The Spliterator is designed to partition the data and hand off chunks to different threads
Spliterator<T> trySplit();
void forEachRemaining(T_CONS action)
boolean tryAdvance(IntConsumer action)
Functional Java
#5: Functions & Collections
Spliterator
- The method trySplit() partitions the underlying data in two.
- 1/2 of the data goes into the new Spliterator
- 1/2 of the data stays in the old Spliterator
- Each partition can be handed to a thread and processed via the forEachRemaining().
- The tryAdvance() is a one-at-a-time variant that returns the next element or null if the list has been exhausted
- Spliterators do not handle parallel processing
- They provide the abstraction to do so.
Functional Java
#5: Functions & Collections
Spliterator
public boolean isMovieInLIst(String title, List<String> movieList) throws InterruptedException {
// Obtain a spliterator from the movie list
Spliterator<String> s1 = movieList.spliterator();
// Now split the original list in half
Spliterator<String> s2 = s1.trySplit();
AtomicBoolean isFound = new AtomicBoolean(false);
if (s2 == null) return isFound.get();
Consumer<String> finder = movie -> {
if (movie.equals(title)) isFound.set(true);
};
Thread t1 = new Thread( () -> s1.forEachRemaining(finder) );
Thread t2 = new Thread( () -> s2.forEachRemaining(finder) );
t1.start();
t2.start();
t1.join();
t2.join();
return isFound.get();
}
Functional Java
#5: Functions & Collections
Spliterator
- Additional splits can be made for additional threads
- Spliterators can be obtained from other Collection types
- Many different characteristics
- including
- finite/infinite
- ordered/non-ordered
- sorted/non-sorted
- mutable/immutable
- They inherit the qualities of their underlying data structures
- Spliterators are a lower-level abstraction designed for fined grain controls.
- Streams offer a higher-level abstraction and a much richer API for implementing fully-functional algorithms
Functional Java
#6: Streams
Overview
- Introduction
- Stream Operations
- Build Operations
- Iterator Operations
- Filter Operations
- Map Operations
- Reduce Operations
- Optional
- More Reduction
- Mutable Reduction
- Peek Operations
- Well-Behaved Stream
- Non-Interference of Streams
- Specialized Streams
Functional Java
#6: Streams
Introduction
- The piece de resistance of functional programming
- Rubber meets the road
- we put into practice everything we've learned so far
- Reminder:
- immutability
- minimizes/avoids state
- functional purity
- Streams are the embodiment of this
- monad is used to describe a structure whereby operations are bound together to form a pipleline
- Each operation processes a long, possibly infinite sttream of data.
Functional Java
#6: Streams
Introduction
- Let's think of streams as a conveyor belt
- Each station is managed by a worker enriching our widget
- We can have any number of workers
- They must all be able to work cohesively
- Each worker is a specialist with slight variations

Functional Java
#6: Streams
Introduction
- Some are intermediates, working on widgets passing by
- Others are terminal, finishing widgets at the end of the line
- It doesn't logically make sense to mix those up
- Workers specialize with some variation - for example an inspector inspects, but with a given set of standards
- No need to intervene
- Lazy: if it doesn't meet standards, no need to close the box
What if we need to scale?
Functional Java
#6: Streams
Introduction
- One option is to scale vertically

Functional Java
#6: Streams
Introduction
- Another option is to double the workers



Functional Java
#6: Streams
Introduction
- With this type of setup we see things 1/1000 widgets is defective which is nearly impossible to reproduce
- It works perfect in the test environment
- But in production we get random errors
What if we distribute these workers in a different way?
Functional Java
#6: Streams
Introduction




- How about more conveyor belts?
Functional Java
#6: Streams
Introduction
- If one belt can produce widgets consistently
- And two+ belts can produce widgets identically
- Then we can scale infinitely by simply adding more belts
- We simply adjust the number of belts to meet need
Let's break it down:
- The Conveyor Bet is the STream
- The Workers are Stream Methods (aka Operations)
- Actions can be customized w/behavioral parameters
- The process used to build the widget is the algorithm
Functional Java
#6: Streams
Introduction
- Specialized streams can be used in lieu of generic ones to handle primitive types in the same way we sa functional interfaces specialized for primitives
- Powered by the fork-join framework when used in parallel
Functional Java
#6: Streams
Stream Operations
- Set of interfaces with abstract, default and static methods
- These methods are known as operations
- There are many types but roughly fit into six families
- Java has realized FP at an library level

Functional Java
#6: Streams
Build Operations
concat(Stream<? extends T> a, Stream<? extends T> b) Inter Stat empty() Inter Stat generate(Supplier<T> s) Inter Stat iterate(final T seed, final UnaryOperator<T> f) Inter Stat of(T t) Inter Stat of(T... values) Inter Stat onClose(Runnable closeHandler) Inter Inst parallel() Inter Inst sequential() Inter Inst skip(final T seed, final UnaryOperator<T> f) Inter Inst sorted() Inter Inst sorted(Comparator<? super T> comparator) Inter Inst unordered() Inter Inst builder() Term Stat close() Term Inst iterator(final T seed, final UnaryOperator<T> f) Term Inst spliterator() Term Inst toArray() Term Inst toArray(IntFunction<A[]> generator) Term Inst
Abstract: Create and start the stream process
Functional Java
#6: Streams
Build Operations
- Starting point of streams
- Created out of thin air: generate(), of() and iterate()
- Or preexisting collections with stream()
- Other libraries offer the ability to create streams including
- bufferedReader
- Files
- bitSet
- Pattern
- JarFile
- and many more
- Or from other existing streams with concat()
- All can be paralleled by parallel()
Functional Java
#6: Streams
Build Operations
- Let's create a pointless stream...
public void test1() {
Stream.of(0, 1, 2, 3, 4, 5, 6, 7, 8 ,9);
}
- These widgets fall off the conveyor belt
- Operations have to be chained together
- The style is known as a fluent interface
- In Java, we call this a pipeline
- Any number of intermediate operations can be pipelined
public void test2() {
Integer[] integers = Stream
.of(0, 1, 2, 3, 4, 5, 6, 7, 8 ,9)
.toArray(Integer[]::new);
}
Functional Java
#6: Streams
Build Operations
- Most operations are backed by physical memory storage
- They are finite - constrained by physical memory
- They can be created from an infinite source via methods like iterate() and generate()
public void test() {
Stream.iterate(1, i -> ++i);
}
- iterate() requires a seed and a function (UnaryOperator)
- Sneaky bug: Use i++
- There is no state in a Lambda!
- Iterate operations are the functional programming equivalent of for/while loops
- An exit condition must specified or they will run for ever
Functional Java
#6: Streams
Build Operations
- Build operations also provide on/off ramps between streams and collections.
- Here we create a stream of integers and store them
public void test4() {
Integer[] integers = Stream.of(1, 2, 3).toArray(Integer[]::new);
}
- Allows back-and-forth transitioning
public void test5() {
Stream.Builder<Integer> builder = Stream.builder();
for (int i = 0; i < 100; i++) {
builder.add(i);
}
builder.build(); // Building is done
builder.add(9999); // throws IllegalStateException
}
Functional Java
#6: Streams
Build Operations
- We could use ArrayList and invoke stream() on it however the builder offers slightly better performance because the underlying mechanism is still a stream
Functional Java
#6: Streams
Iterator Operations
forEach(Consumer<? super T> action) Term Inst forEachOrdered(Consumer<? super T> action) Term Inst forEach(Consumer<? super T> action) Term Inst
- Iterate operation are always terminal
- Allow sequential iteration once the element through stream
public void test6() {
Stream.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
.forEach(System.out::println);
}
- forEachOrdered() guarantees to preserve order if the underlying stream source preserves order (ie a List)
- This is only relevant for parallel streams.
- forEach() makes no effort to preserve order
Abstract: Iterate through a stream
Functional Java
#6: Streams
Filter Operations
distinct() Inter Inst filter(Predicate<? super T> predicate) Inter Inst limit(long maxSize) Inter Inst
- Declarative equivalent of if statements.
- Means to remove an element from the stream
- "let these go through" rather than "filter these out"
- This example only prints even numbers
public void test7() {
Stream.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
.filter(i -> i % 2 == 0)
.forEach(System.out::println);
}
- The filter() requires a Predicate function
- Removes all elements where the Predicate returns false
Abstract: Filter out elements from the stream
Functional Java
#6: Streams
Filter Operations
- We can combine multiple (intermediate) filter operations
public void test8() {
Stream.iterate(1, i -> ++i)
.limit(10)
.filter(i -> i % 2 == 0)
.forEach(System.out::println);
}
- In this case the limit() works as a short-circuiting operation to provide an exit condition for the never ending iterate()
- distinct() ensure that duplicate elements are filtered out
- It is a minority in that it must maintain state in order to determine if an element is distinct or not.
public void test9() {
Stream.of(1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 5)
.distinct()
.forEach(System.out::println);
}
Functional Java
#6: Streams
Map Operations
flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) Inter Inst flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper) Inter Inst flatMapToInt(Function<? super T, ? extends IntStream> mapper) Inter Inst flatMapToLong( Function<? super T, ? extends LongStream> mapper) Inter Inst map(Function<? super T, ? extends R> mapper) Inter Inst mapToDouble(ToDoubleFunction<? super T> mapper) Inter Inst mapToInt(ToIntFunction<? super T> mapper) Inter Inst mapToLong(ToLongFunction<? super T> mapper) Inter Inst
Abstract: Maps one value to another / same or different kind
- Maps are the heart of any algorithm
- There are only two forms: map() and flatMap()
- The others are specializations for primitive types
Functional Java
#6: Streams
Map Operations
- map() simply perform a one-to-one transformation
- Can be of the same type or another type
- This examples doubles every value in the stream
public void test10() {
Stream.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
.map(i -> i * 2)
.forEach(System.out::println);
}
- We can easily transform integers to streams
public void test11() {
Stream.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
.map(i -> i % 2 == 0 ? i+": even" : i+": odd")
.forEach(System.out::println);
}
Functional Java
#6: Streams
Map Operations
- flatMap() does a one-to-many transformation
public void test12() {
Stream.of("I am a chicken", "We like to sing", "Who was that")
.flatMap( s -> Arrays.stream(s.split(" ")) )
.forEach(System.out::println);
}
I am a chicken We like to sing Who was that
Output:
Functional Java
#6: Streams
Reduce Operations
allMatch(Predicate<? super T> predicate) Term Inst anyMatch(Predicate<? super T> predicate) Term Inst collect(Collector<? super T, A, R> collector) Term Inst collect(Supplier<R> supplier, Term Inst BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner) count() Term Inst findAny() Term Inst findFirst() Term Inst max(Comparator<? super T> comparator) Term Inst min(Comparator<? super T> comparator) Term Inst noneMatch(Predicate<? super T> predicate) Term Inst reduce(BinaryOperator<T> accumulator) Term Inst reduce(T identity, Term Inst BinaryOperator<T> accumulator) reduce(U identity, Term Inst BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
Abstract: Reduce a stream to a single entity
Functional Java
#6: Streams
Reduce Operations
- Known as folds in functional programming terminology
- Traverse the sequence combining ("folding") each element with the already traversed portion
- Concludes with one value at the end
- How it's combined is specified with the behavioral param
- Are always terminal
- Let's look at the classic reduce...
Functional Java
#6: Streams
Reduce Operations
public void test13() {
int sum = Stream
.of(1, 2, 3, 4, 5)
.reduce(0, (l, r) -> l + r);
}
- reduce() expects a starting value & a BinaryOperator acting as an accumulator function
- The variable l represents "left"
- The variable r represents "right"
- Left is the initial value or the sum of all subsequent values
- This is a left-folding operation because the list is traversed from left to right.
- Right folding operations don't exist natively in the streams
- Simulated by reversing the order of elements
Functional Java
#6: Streams
Reduce Operations
(((((0 + 1) + 2) + 3) + 4) +5) ((((1 + 2) + 3) + 4) +5) (((3 + 3) + 4) +5) ((6 + 4) +5) (10 + 5)
15
{l = 0; r = 1} {l = 1; r = 2} {l = 3; r = 3} {l = 6; r = 4} {l = 10; r = 5}
- This example is "rigged" to guarantee the sum will always have a value because we started with the init value of zer
Functional Java
#6: Streams
Reduce Operations
- What happens if no values get passed a filter() operation?
public void test15() {
int sum = Stream
.of(1, 3, 5)
.filter(i -> i % 2 == 0)
.reduce((l, r) -> l + r)
.orElse(0);
}
- This version of reduce() does not have an initial value
- A return value cannot be guaranteed
- We account for this with the call orElse()
- Optional are intrinsic to functional programming because they internalize the result of a computation
Functional Java
#6: Streams
Optional
T get() boolean isPresent() void ifPresent(Consumer<? super T> consumer) T orElse(T other) T orElseGet(Supplier<? extends T> other) T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X Optional<U> map(Function<? super T, ? extends U> mapper) Optional<U> flatMap(Function<? super T, Optional<U>> mapper) Optional<T> filter(Predicate<? super T> predicate) Optional<T> empty() Optional<T> of(T value) Optional<U> ofNullable(T value)
Abstract: Provides contingencies when ret. values from meths.
- Optionals are not optional in Functional Programming !!!
Functional Java
#6: Streams
Optional
- Puts contingencies in place and react appropriately when streams don't produce anything
- We can naively invoke get() and run the risk of having a NoSuchElementException being thrown (unchecked)
- isPresent() is safer, allowing us to test for a value
- A more functional approach is to use one of orElse(), orElseGet(), or orElseThrow()
- Take for example...
public void test16() {
int sum = Stream
.of(-20, 2, 4, 6)
.reduce( (l,r) -> l + r )
.filter(i -> i > 0)
.orElse(0);
}
- You can also perform other stream-like operations
Functional Java
#6: Streams
More Reduction
- Reduce operations can be used to find things in a stream
- Or rather, they reduce a stream down to one value
boolean found = Stream.of("Villeray", "Plateau", "Rosemont", "Mile-End")
.anyMatch("Rosemont"::equals);
- Once the predicate is matched the stream is short-circuited
- matchAll() is more restrictive, require all to match
- Closely related are findFirst() and findAny()
- The difference is findAny() is allowed to shorts-circuit without respect to the order of the collection
boolean found = Stream.of("Villeray", "Plateau", "Rosemont", "Mile-End")
.filter("Rosemont"::equals)
.findFirst()
.isPresent();
Functional Java
#6: Streams
Mutable Reduction
- Reduction is a fundamental operation
- Once size does not fit all - sometimes we need to mutate
Map<String,Integer> population = new HashMap<>();
population.put("Villeray", 145000);
population.put("Plateau", 100390);
population.put("Rosemont", 134038);
population.put("Mile-End", 31910);
String densePlaces = population
.keySet()
.stream()
.filter(s -> population.getOrDefault(s, 0) > 110_000)
.reduce("", String::concat);
- We start with an empty string
- If the element passes the filter, we concatenate it
- The longer the stream, the more processing time
- We have an alternative for expensive accumulation
Functional Java
#6: Streams
Mutable Reduction
-
collect() operations change the responsibility of the
stream and behavioral patterns - The stream allows collect() to store each element into a container.
- The containers are "mutated" as elements are added to it
- They work with ....
- Suppliers to create the container
- Accumulator to populate the container
- Combiners are used in parallel execution (ignored here)
Functional Java
#6: Streams
Mutable Reduction
Map<String,Integer> population = new HashMap<>();
population.put("Villeray", 145000);
population.put("Plateau", 100390);
population.put("Rosemont", 134038);
population.put("Mile-End", 31910);
List<String> densePlaces = population
.keySet()
.stream()
.filter(s -> population.getOrDefault(s, 0) > 110_000)
.collect(ArrayList::new, // Supplier
ArrayList::add, // Accumulator
ArrayList::addAll); // Combiner
- The ArrayList is created for the first element
- Then the first (and all subsequent) are added to the ArrayList via the accumulator
- And lastly (if parallelized) multiple ArrayLists are combined
- Collectors can work with any time as long as the supplier creates the object used by the accumulator
Functional Java
#6: Streams
Mutable Reduction
- There's also a variant of collect() that works with the Collector interface allowing for customized collection impl
- There's a lot more to reduction
- parallelization
- re-ordering
- We'll revisit some of these later when we talk about parallelization
Functional Java
#6: Streams
Peek Operations
peek(Consumer<? super T) action) Inter Inst
Abstract: Inject orthogonal, non-interfering behavior
- Injects non-interfering behavior
- A typical usecase is for debugging
- Allows us to peak at the data flowing through the pipeline
- They are ALMOST harmless - eg mutate a collection
Stream.iterate(1, i-> ++i)
.limit(10)
.peek(i -> System.out.println("Before: " + i))
.map(i -> i * 3)
.peek(i -> System.out.println("After: " + i))
.forEach(System.out::println);
Functional Java
#6: Streams
Well-Behaved Stream
- Streams crystalize everything we've discussed about FP
- But Java remains flexible
- You disable the rules at your own peril
Streams Must Be Coherent
- Must start with a build operation
- Zero or more intermediate operations
- And capped with a final terminal operation
Functional Java
#6: Streams
Well-Behaved Stream
- Incoherent streams are at best useless at worst harmful
Stream.of(1, 2, 3, 4, 5).peek(System.out::println);
- This example never actually executes
- We need a terminal operation
Stream.of(1, 2, 3, 4, 5).peek(System.out::println).findFirst();
- This highlights an important point: lazy vs eager
- In this case findFirst() "pulls" data through the stream.
- We can cause other problems with infinite streams
Stream.generate( () -> 1).allMatch(i -> i == 1);
Stream.generate( () -> 1).limit(100).allMatch(i -> i == 1);
- This will "fix" our problem...
Functional Java
#6: Streams
Non-Interference of Streams
- A well-behaved stream has an unmodified source
- If we start w/a collection, the collection shouldn't be mod
List<String> words = new ArrayList<>(
Arrays.asList("This", "Sentence", "contains", "five", "words"));
words.stream()
.forEach(s -> {
if (s.equals("five")) words.add("thousands");
});
- Lambdas passed to operations should also be stateless and absent any side effects.
- This would interfere with the streams ability to process the stream in parallel
- The outcome would become nondeterministic
- Streams maintain state, but it's transient.
Functional Java
#6: Streams
Specialized Streams
- So far we've seen Integers and Strings used as parameterized types
- Specialized stream types for Integer, Long & Double
- Similar to the specialization in the Functional interfaces
- IntStream
- LongStream
- DoubleStream
- Compare these two examples:
Stream.iterate(1, i -> ++i).limit(10).count();
IntStream.range(1, 10).count();
- IntStream also gives us basic statistical analysis
Functional Java
#6: Streams
Specialized Streams
public static void printTestStats(int[] classOneScores, int[] classTwoScores) {
IntSummaryStatistics classOneStats = IntStream.of(classOneScores).summaryStatistics();
System.out.format("%s, %s, %s, %s",
classOneStats.getMax(),
classOneStats.getMin(),
classOneStats.getAverage(),
classOneStats.getCount());
IntSummaryStatistics classTwoStats = IntStream.of(classTwoScores).summaryStatistics();
System.out.format("%s, %s, %s, %s",
classTwoStats.getMax(),
classTwoStats.getMin(),
classTwoStats.getAverage(),
classTwoStats.getCount());
// Now combine the two
IntSummaryStatistics combinedStats = new IntSummaryStatistics();
combinedStats.combine(classOneStats);
combinedStats.combine(classTwoStats);
System.out.format("%s, %s, %s, %s",
combinedStats.getMax(),
combinedStats.getMin(),
combinedStats.getAverage(),
combinedStats.getCount());
}
Functional Java
#7: Using Streams
Overview
- Using Streams To Find Prime Numbers
- Using Streams To Find Perfect Numbers
- Parallel Perfect Number Streaming
- Imperative vs Serial & Parallel Streaming
- Imperative Loops vs Functional Streams
- Serial vs Parallel Streams
Functional Java
#7: Using Streams
Using Streams To Find Prime Numbers
- Everything we've so far are using VERY simple examples
- But streams can encapsulate entire algorithms.
- They can replace just about any for/while-loop
- But making the mental switch can be hard
- Let's revisit our old example of finding prime numbers
Functional Java
#7: Using Streams
Using Streams To Find Prime Numbers
- The first block treats 1 & 2 as special cases
- The for loop ensures that none of the integers divide evenly
public static boolean isPrimeImperative(int n) {
if (n <= 1) return false;
if (n == 2) return true;
int limit = (int)Math.sqrt(n);
for (int i = 2; i <= limit; ++i) {
if (n % i == 0) return false;
}
return true;
}
- This is a prime example of streaming:
- Iterate from 2 to the square root of N.
- Stop iterating when we encounter an evenly divisible #
Functional Java
#7: Using Streams
Using Streams To Find Prime Numbers
public static boolean isPrimeStream(int n) {
if (n <= 1) return false;
if (n == 2) return true;
return IntStream .rangeClosed(2, (int)Math.sqrt(n))
.noneMatch(i -> n % i == 0);
}
- Now that we have the method, we can use it like this...
IntStream.rangeClosed(1, 100)
.filter(PrimeFinder::isPrimeStream)
.forEach(System.out::println);
- Imperative code isn't always obvious
- We have to follow the steps to figure it out
Functional Java
#7: Using Streams
Using Streams To Find Prime Numbers
- Declarative-style programming tends to convey meaning rather explicitly
- rangeClosed() clearly indicates indicates iteration 1-100
- filter() acts as an if state deciding to keep the element
- isPrime(n) tells us that we are testing for a prime #
- Declarative programming puts labels on things and standardizes algorithms
- This helps code maintainability
- And we can write much more concise code with streams compared to traditional loops
- Let's take a look at a more complex example
- Perfect numbers are those whose sum of divisors is equal to the number itself
- 6 is perfect because 1 + 2 + 3 = 6
- 8 is not perfect because 1 + 2 + 4 = 7
- There are 48 perfect numbers known to man kind
- 6
- 28
- 496
- 8128
- Discovered by ancient Greek mathematicians
- "Super" computers last discovered another in 2013
- It contained more than 34 million digits
#7: Using Streams
Using Streams To Find Perfect Numbers
#7: Using Streams
Using Streams To Find Perfect Numbers
- Let's take a look at an imperative version of this algorithm
public static boolean isPerfectImperative(long n) {
long sum = 0;
for (long i = 1; i <= n /2; i++) {
if (n % i == 0) {
sum += 1;
}
}
return sum == n;
}
- To convert to a stream-based solution
- Replace the for-loop with an iterate operation w/bounds
- The if-statement is a filter()
- The processing in question is running sum
- The filter's condition is a test for even divisibility
- The sum operation is just a reduction
#7: Using Streams
Using Streams To Find Perfect Numbers
- Here is what the stream version will look like:
public static boolean isPerfectStream(long n) {
long sum = LongStream.rangeClosed(1, n/2)
.filter(i -> n % i == 0)
.reduce(0, (l,r) -> l + r);
return (sum == n) && (n > 0);
}
- rangeClosed() is doing our iteration
- filter() is testing our values
- reduce() is summing the result
As a side note...
- rangeClosed() and filter() are lazy
- Not until reduce() is called that it "pulls" values through
#7: Using Streams
Using Streams To Find Perfect Numbers
- Now we can put our new function to work
- We'll start with a function that can test a wide range of #s
public static List<Long> findPerfectNumbers(long maxLong, LongPredicate condition) {
return LongStream
.rangeClosed(1, maxLong)
.filter(condition)
.collect(ArrayList<Long>::new,
ArrayList<Long>::add,
ArrayList<Long>::addAll);
}
- Next we can run a simple test...
PerfectNumberFinder
.findPerfectNumbers(8128, PerfectNumberFinder::isPerfectStream)
.forEach(System.out::println);
Functional Java
#7: Using Streams
Parallel Perfect Number Streaming
- Besides easily communicating intent (declarative)
- Stream's major selling point is parallelization
- We don't need to know how
- In most cases, a standard algorithm can be converted to a parallel one by simply invoking parallel()
- Think of parallelization as cloned streams
- parallel() automatically creates multiple stream pipelines
- Each is a cloned pipeline having the same operations
- It will create as mean streams as there are cores on the host's CPU(s)
- Under the hood it uses the fork-join framework
- Once invoked, each stream "pulls" the elements through using a shared thread pool of the fork-join framework.
Functional Java
#7: Using Streams
Parallel Perfect Number Streaming

Functional Java
#7: Using Streams
Parallel Perfect Number Streaming
- As a reminder, we need only add one call parallel()
public static boolean isPerfectParallel(long n) {
long sum = LongStream.rangeClosed(1, n/2)
.parallel()
.filter(i -> n % i == 0)
.reduce(0, (l,r) -> l + r);
return (sum == n) && (n > 0);
}
- The internal state synchronization is automatically mnged
- It makes no difference where the parallel() operation is
- Once added, the entire pipeline is parallelized\
public static List<Long> findPerfectNumbers(long maxLong, LongPredicate condition) {
return LongStream
.rangeClosed(1, maxLong)
.parallel()
.filter(condition)
.collect(ArrayList<Long>::new,
ArrayList<Long>::add,
ArrayList<Long>::addAll);
}
Functional Java
#7: Using Streams
Parallel Perfect Number Streaming
- And lastly we can call it like this...
PerfectNumberFinder
.findPerfectNumbers(8128, PerfectNumberFinder::isPerfectParallel)
.forEach(System.out::println);
- Looking back at findPerfectNumbers(..), the combiner is now using the third argument to combine the result from each thread once execution is completed.
Functional Java
#7: Using Streams
Imperative vs Serial & Parallel Streaming
- Parallelization doesn't always improve performance
- For small numbers, the overhead could be costly
- But we do see the payoff for larger numbers
- Streams also have their own overhead which we can see in the Imperative vs Stream numbers
Number to Test | isPerfectImperative | IsPerfectStream | isPerfectParallelized |
---|---|---|---|
8,128 | 0 | 1 | 0 |
33,550,336 | 190 | 229 | 66 |
8,589,869,056 | 48,648 | 59,646 | 13,383 |
137,438,691,328 | 778,853 | 998,776 | 203,651 |
Functional Java
#7: Using Streams
Imperative Loops vs Functional Streams
- You can convert just about any loop into streams
- But should we?
- Is every member of your team ready?
- Should we introduce streams gently?
- Let's take a look at some of the pros and cons
Functional Java
#7: Using Streams
Imperative Loops vs Functional Streams
Some of the pros include
- They are declarative constructs: Code what, not how
- Communication of intent: intent of behavior is communicated effectively
- Conciseness: We remove the plumbing like for-loops
- Easier to test: Less code is easier to test, immutability is easier to test, functional-purity means no side effects
- On-demand parallel streaming: switching to parallel execution with one command is a HUGE bonus
Functional Java
#7: Using Streams
Imperative Loops vs Functional Streams
Some of the cons include
- Density: The dark side of conciseness. It takes a trained eye to parse the code at time
- Loss of flexibility: You give up control to declarative programming constructs - eg you cannot mutate local var
- Loss of efficiency: When a library or framework generalizes behavior it does so with some costs. As frameworks improve, performance goes up. The JVM's garbage collectors are an EXCELLENT example of this.
Functional Java
#7: Using Streams
Serial vs Parallel Streams
- Should we parallelize?
- So far we have ignored some of the pitfalls
- Functional Programming and Streams are a better mousetrap but YOU HAVE TO PLAY BY THE RULES!
- Streams must be stateless and have no side effects
Functional Java
#7: Using Streams
Serial vs Parallel Streams
Parallel streams work best when...
-
Must fit the associativity property
- The order cannot be guaranteed
- ((a + b) + c) = (a + (b + c))
- Reversing the reduce to provide a sum would have no side effects
- If the algorithm required subtraction it would not have worked in a parallel fashion
-
ROI on parallelization overhead
- The overhead must be justified
Functional Java
#7: Using Streams
Serial vs Parallel Streams
Parallel streams work best when...
-
Processing is CPU-bound
- Works best for CPU intensive operations
- Requiring no IO
- Ideally parallel streams are used after data has been read from disk or network and loaded into memory
-
Thread saturation:
- Since we rely on Java's fork-join framework a stream that with substreams mean they all work from the same thread pool
Functional Java
#8: Thinking Functionally
Overview
- Logging
- Design Patterns
- Currying
- Stretching Java To Its Functional Limits
- Recursion
Functional Java
#8: Thinking Functionally
Logging
Functional Java
#8: Thinking Functionally
Design Patterns
Functional Java
#8: Thinking Functionally
Currying
Functional Java
#8: Thinking Functionally
Stretching Java To Its Functional Limits
Functional Java
#8: Thinking Functionally
Recursion
Functional Java
#9: Conclusion
Overview
- The Functional Programming Panacea
- Workplace Adoption
- Community Acceptance
- Java 9 and Beyond
Functional Java
#9: Conclusion
The Functional Programming Panacea
Functional Java
#9: Conclusion
Workplace Adoption
Functional Java
#9: Conclusion
Community Acceptance
Functional Java
#9: Conclusion
Java 9 and Beyond
Functional Java
By Jacob D Parr
Functional Java
- 982