Functional Java

Copyright 2017 - Jacob D. Parr

https://www.jacobparr.com

(all rights reserved)

Functional Java

Agenda

Chapters

  1. Introduction
  2. The Lambda Form
  3. Functional, Default & Reference Methods
  4. The Fine Print
  5. Functions & Collections
  6. Streams
  7. Using Streams
  8. Thinking Functionally
  9. 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...

  1. State is kept between creation, runTimed() and
    getExecutionDuration().                    Lambdas forbid state
  2. Lambdas must be backed by a functional interface.
    This class is abstract.
  3. 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

  1. Overrides from concrete classes take precedence over interface default methods
  2. Sub-interface methods take precedence over super-interface methods
  3. Explicitness takes precedence over inference


Said another way...

  1. Let Java figure it out
  2. 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
    1. rangeClosed() clearly indicates indicates iteration 1-100
    2. filter() acts as an if state deciding to keep the element
    3. 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
    1. Replace the for-loop with an iterate operation w/bounds
    2. The if-statement is a filter()
    3. The processing in question is running sum
    4. The filter's condition is a test for even divisibility
    5. 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

Made with Slides.com