The Game Changers
(if desired)
// starting in 1.5 interfaces could be declared with generics
public class Dog implements Comparable<Dog> {
final String ownerLastName;
final String name;
public Dog(String name, String ownerLastName) {
this.name = name;
this.ownerLastName = ownerLastName;
}
public String toString() {
return ownerLastName + ", " + name;
}
@Override // starting in 1.6 @Override applied to interfaces
public int compareTo(Dog other) {
int result = ownerLastName.compareTo(other.ownerLastName);
if (result == 0) {
result = name.compareTo(other.name);
}
return result;
}
...
...
public static void main(String[] args) {
Dog[] dogs = { new Dog("Fido", "Smith"),
new Dog("Fluffy", "Jones"),
new Dog("Fido", "Jones") };
System.out.println("Unsorted:");
for(Dog dog: dogs) {
System.out.println(dog);
}
List<Dog> dawgs = Arrays.asList(dogs); // Varargs came along in 1.5
Collections.sort(dawgs); // Collections as of 1.5
System.out.println("\nSorted:");
for(Dog dog: dawgs) {
System.out.println(dog);
}
}
}
Unsorted:
Smith, Fido
Jones, Fluffy
Jones, Fido
Sorted:
Jones, Fido
Jones, Fluffy
Smith, Fido
// For illustration only. Java 8 doesn't do this!
public interface Comparable<T> {
int compareTo(T o);
// Let's add a new method
boolean isComparativelyEqual(T o);
}
https://marketingmuster.files.wordpress.com/2011/06/homer.png
http://www.memes.com/meme/498049
http://i.giphy.com/mvoxdYnpyk23u.gif
Something had to change or Java might be doomed by its inability to adapt to developer expectations
// For illustration only. Java 8 doesn't do this!
public interface Comparable<T> {
int compareTo(T o);
// Let's add a new method
default boolean isComparativelyEqual(T o) {
return compareTo(o) == 0;
}
}
Java API designers can add default behaviors to interfaces!
public interface CustomComparable<T> extends Comparable<T> {
// This is contrived!!! From JavaDoc for Comparable:
// "It is strongly recommended (though not required) that
// natural orderings be consistent with equals."
default boolean isComparativelyEqual(T o) {
return compareTo(o) == 0;
}
}
public class Dog2 implements CustomComparable<Dog2> {
final String ownerLastName;
final String name;
final LocalDate lastVaccination; // LocalDate new to Java 8
public Dog2(String name, String ownerLastName, LocalDate lastVaccination) {
this.name = name;
this.ownerLastName = ownerLastName;
this.lastVaccination = lastVaccination;
}
public String toString() {
return String.format("%s, %s [Last Vaccination: %s]",
ownerLastName, name,
lastVaccination.format(DateTimeFormatter.ISO_LOCAL_DATE));
}
@Override
public int compareTo(Dog2 other) {
int result = ownerLastName.compareTo(other.ownerLastName);
if (result == 0) {
result = name.compareTo(other.name);
}
return result;
}
...
...
// Let's include lastVaccination date in equals
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Dog2 dog2 = (Dog2) o;
return Objects.equals(ownerLastName, dog2.ownerLastName) &&
Objects.equals(name, dog2.name) &&
Objects.equals(lastVaccination, dog2.lastVaccination);
}
@Override
public int hashCode() {
return Objects.hash(ownerLastName, name, lastVaccination);
}
...
...
public static void main(String[] args) {
Dog2 fido_smith = new Dog2("Fido", "Smith", LocalDate.now());
Dog2 fido_smith2 = new Dog2("Fido", "Smith", LocalDate.now()
.minus(3, ChronoUnit.WEEKS));
Dog2 fluffy_jones = new Dog2("Fluffy", "Jones", LocalDate.of(2015, Month.JULY, 1));
Dog2 fido_jones = new Dog2("Fido", "Jones", LocalDate.parse("2015-02-28"));
Dog2[] dogs = { fido_smith, fido_jones, fluffy_jones, fido_smith2 };
// omitted sorting demo code as it was the same as before
System.out.println("\nComparatively Equal");
System.out.println("Are Fido Jones & Fido Smith equal? "
+ fido_jones.equals(fido_smith));
System.out.println("Are Fido Jones & Fido Smith comparatively equal? "
+ fido_jones.isComparativelyEqual(fido_smith));
System.out.println("Are Fido Smith & Fido Smith #2 equal? "
+ fido_smith.equals(fido_smith2));
System.out.println("Are Fido Smith & Fido Smith #2 comparatively equal? "
+ fido_smith.isComparativelyEqual(fido_smith2));
}
Unsorted:
Smith, Fido [Last Vaccination: 2015-10-04]
Jones, Fido [Last Vaccination: 2015-02-28]
Jones, Fluffy [Last Vaccination: 2015-07-01]
Smith, Fido [Last Vaccination: 2015-09-13]
Sorted:
Jones, Fido [Last Vaccination: 2015-02-28]
Jones, Fluffy [Last Vaccination: 2015-07-01]
Smith, Fido [Last Vaccination: 2015-10-04]
Smith, Fido [Last Vaccination: 2015-09-13]
Comparatively Equal
Are Fido Jones & Fido Smith equal? false
Are Fido Jones & Fido Smith comparatively equal? false
Are Fido Smith & Fido Smith #2 equal? false
Are Fido Smith & Fido Smith #2 comparatively equal? true
Subtypes carry default methods from super types
(if desired)
public interface Greetable {
void greet();
}
public interface English extends Greetable {
default void greet() { System.out.println("Hello"); }
}
public interface French extends Greetable {
default void greet() { System.out.println("Bonjour"); }
}
public interface German extends Greetable {
default void greet() { System.out.println("Guten Tag"); }
}
public interface AmericanEnglish extends English {
default void greet() { System.out.println("Hi"); }
// We need to do this to unhide the English method
default void greetFormalEnglish() {
English.super.greet();
}
}
public class Greeter implements AmericanEnglish, French, German {
// Comment greet() implementation out to see the compiler complain
@Override
public void greet() {
System.out.println("Howdy");
}
public static void main(String[] args) {
Greeter greeter = new Greeter();
greeter.greet();
greeter.greetInFrench();
greeter.greetInGerman();
greeter.greetInAmericanEnglish();
greeter.greetInEnglish();
}
private void greetInFrench() { French.super.greet(); }
private void greetInGerman() { German.super.greet(); }
private void greetInAmericanEnglish() { AmericanEnglish.super.greet(); }
private void greetInEnglish() { AmericanEnglish.super.greetFormalEnglish(); }
}
Howdy
Bonjour
Guten Tag
Hi
Hello
If a method takes a functional interface as a parameter,
then we can pass the following:
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
// some default methods omitted here
}
// a method that accepts a Predicate
public List<Integer> pickSome(List<Integer> values,
Predicate<Integer> pickingStrategy) {
List<Integer> results = new ArrayList<>();
for(Integer candidate: values) {
if(pickingStrategy.test(candidate)) {
results.add(candidate);
}
}
return results;
}
List<Integer> values = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evens = pickSome(values, new Predicate<Integer>() {
@Override
public boolean test(Integer value) {
return value % 2 == 0;
}
});
// results: 2,4,6,8,10
List<Integer> values = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evens = pickSome(values, value -> value % 2 == 0);
// results: 2,4,6,8,10
// Think of lambda expressions as disembodied methods
// the -> operator separates arguments from implementation
// general form
(TypeA argument1, TypeB argument2) -> {
doSomething... ;
doDomethingElse... ;
return something;
}
// no-args
() -> { statement body }
// one argument
(TypeC argument1) -> { statement body }
// type can often be inferred by compiler (but will be non-final)
(argument1) -> { statement body }
// if statement body is one statement, braces optional
(argument1) -> statement
// argument parentheses optional for one argument
value -> statement
// The Iterable interface now has a default forEach method
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
.........
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
// some default methods omitted here
}
// Think of method references are shorthand expressions
// where argument(s) can be inferred
// General syntax is
// instance::methodName or Type::methodName
// When to use? Whenever you might use a lamdba expression
// just to pass a value through to a method
Instead of:
list.forEach(value -> System.out.println(value))
use:
list.forEach(System.out::println)
If you have a method declared like:
map(Function<T, R> f) {...}
instead of:
map(value -> value.toUpperCase())
use:
map(String::toUpperCase)
Kind | Example |
---|---|
Reference to a static method | ContainingClass::staticMethodName |
Reference to an instance method of a particular object | containingObject:: instanceMethodName |
Reference to an instance method of an arbitrary object of a particular type | ContainingType::methodName |
Reference to a constructor | ClassName::new |
// Example of how we can get to point of using method reference
List<Integer> values = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
values.forEach((Integer value) -> System.out.println(value));
// better
values.forEach((value) -> System.out.println(value));
// better still
values.forEach(value -> System.out.println(value));
// best
values.forEach(System.out::println);
// results: 1,2,3,4,5,6,7,8,9,10
public class MethodReferenceDemo {
public static void main(String[] args) {
List<String> names = Arrays.asList("Barb", "Bob", "Sarah", "Steve");
System.out.println("Using lambda");
names.forEach(name -> System.out.println(name));
System.out.println("\nLet's imperatively transform the collection");
System.out.println("Then use method reference to print them out...");
List<String> transformedValues = new ArrayList<String>();
names.forEach(name -> transformedValues.add(name.toUpperCase()));
transformedValues.forEach(System.out::println);
}
}
Using lambda
Barb
Bob
Sarah
Steve
Let's imperatively transform the collection
Then use method reference to print them out...
BARB
BOB
SARAH
STEVE
public class CtorReferenceDemo {
static class Foo {
static int instanceCount;
private Foo() {
instanceCount++;
}
static Foo create(Supplier<Foo> supplier) {
return supplier.get();
}
}
public static void main(String[] args) {
for(int i = 0; i < 10; i++) {
Foo foo = Foo.create(Foo::new); // Foo:new returns Supplier<Foo>
System.out.println(foo.instanceCount);
}
}
}
1
2
3
4
5
6
7
8
9
10
public class CtorReferenceDemo2 {
private static class Foo {
static int instanceCount;
private Foo() {
instanceCount++;
}
private Foo(LocalTime now) {
this();
System.out.println(now + ": " + instanceCount);
}
static Foo create(LocalTime now, Function<LocalTime, Foo> function) {
return function.apply(now);
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
Foo foo = Foo.create(LocalTime.now(), Foo::new); //Function<LocalTime, Foo>
Thread.sleep(500);
}
}
}
11:53:31.232: 1
11:53:31.805: 2
11:53:32.308: 3
11:53:32.811: 4
11:53:33.315: 5
11:53:33.818: 6
11:53:34.321: 7
11:53:34.822: 8
11:53:35.326: 9
11:53:35.831: 10
public class CtorReferenceDemo3 {
@FunctionalInterface
private static interface FooFactory<String, LocalTime> {
Foo create(String msg, LocalTime time);
}
private static class Foo {
static int instanceCount;
private Foo() { instanceCount++; }
private Foo(String msg, LocalTime now) {
this();
System.out.println(msg + " - " + now + ": " + instanceCount);
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
FooFactory<String, LocalTime> factory = Foo::new;
factory.create("Hello", LocalTime.now());
Thread.sleep(500);
}
}
}
Hello - 15:13:07.757: 1
Hello - 15:13:08.268: 2
Hello - 15:13:08.772: 3
Hello - 15:13:09.273: 4
Hello - 15:13:09.777: 5
Hello - 15:13:10.281: 6
Hello - 15:13:10.784: 7
Hello - 15:13:11.285: 8
Hello - 15:13:11.790: 9
Hello - 15:13:12.293: 10
BigDecimal calculateTaxableTotal() {
BigDecimal taxableTotal = BigDecimal.ZERO;
for(LineItem item: lineItems) {
if(item.isTaxable()) {
taxableTotal = taxableTotal.add(item.calculateTotal());
}
}
return taxableTotal;
}
Declare mutable
Iterate
Filter
Mutate
What's wrong here?
We finally yield an answer. Now do it concurrently!
public BigDecimal calculateTaxableTotal() {
return lineItems.stream().filter(LineItem::isTaxable)
.map(LineItem::calculateTotal)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
What's going on here?
public class LazyEvalExample {
static int inTheZone;
static Integer map(final Integer val) {
System.out.println("Transforming " + val + "...");
return val * val;
}
static boolean filter(final Integer val) {
System.out.print("Filtering " + val + "... ");
boolean filtered = val >= inTheZone - 5 && val % 5 == 0;
System.out.println(filtered ? "Included" : "Skipped");
return filtered;
}
public static void main(String[] args) {
inTheZone = new Random().nextInt(100);
System.out.println("The inTheZone value: " + inTheZone);
Stream<Integer> values = IntStream.rangeClosed(1, 100).boxed();
Stream<Integer> partiallyProcessed = values.filter(LazyEvalExample::filter)
.map(LazyEvalExample::map);
System.out.println("We've prepared to filter and map transform the values...");
System.out.println("... now find and transform upon executing the terminal method");
int firstFoundSquare = partiallyProcessed.findFirst().get();
System.out.println("The square of the first found value " + firstFoundSquare);
}
}
The inTheZone value: 24
We've prepared to filter and map transform the values...
... now find and transform upon executing the terminal method
Filtering 1... Skipped
Filtering 2... Skipped
Filtering 3... Skipped
Filtering 4... Skipped
Filtering 5... Skipped
Filtering 6... Skipped
Filtering 7... Skipped
Filtering 8... Skipped
Filtering 9... Skipped
Filtering 10... Skipped
Filtering 11... Skipped
Filtering 12... Skipped
Filtering 13... Skipped
Filtering 14... Skipped
Filtering 15... Skipped
Filtering 16... Skipped
Filtering 17... Skipped
Filtering 18... Skipped
Filtering 19... Skipped
Filtering 20... Included
Transforming 20...
The square of the first found value 400
public class LazyEvalExample2 {
private static int inTheZone;
static Integer map(final Integer val) {
System.out.println("Transforming " + val + "...");
return val * val;
}
static boolean filter1(final Integer val) {
System.out.print("Filter 1: Filtering " + val + "... ");
boolean filtered = val >= inTheZone - 5 && val <= inTheZone + 5;
System.out.println(filtered ? "Included" : "Skipped");
return filtered;
}
static boolean filter2(final Integer val) {
System.out.print("Filter 2: Filtering " + val + "... ");
boolean filtered = val % 3 == 0;
System.out.println(filtered ? "Included" : "Skipped");
return filtered;
}
private static Integer terminate(Stream<Integer> values) {
// return values.reduce(0, (Integer a, Integer b) -> a + b);
return values.findFirst().get();
}
...
}
...
public static void main(String[] args) {
inTheZone = 21;
System.out.println("The inTheZone value: " + inTheZone);
Stream<Integer> values = IntStream.rangeClosed(1, 25).boxed();
Stream<Integer> lazilyProcessed = values.filter(LazyEvalExample2::filter1)
.filter(LazyEvalExample2::filter2)
.map(LazyEvalExample2::map);
System.out.println("We've prepared to filter and map transform the values...");
System.out.println("... now find and transform upon executing the terminal method");
int sumOfSquares = terminate(lazilyProcessed);
System.out.println("The sum of squares in the zone: " + sumOfSquares);
}
}
The inTheZone value: 21
We've prepared to filter and map transform the values...
... now find and transform upon executing the terminal [findFirst] method
Filter 1: Filtering 1... Skipped
Filter 1: Filtering 2... Skipped
Filter 1: Filtering 3... Skipped
Filter 1: Filtering 4... Skipped
Filter 1: Filtering 5... Skipped
Filter 1: Filtering 6... Skipped
Filter 1: Filtering 7... Skipped
Filter 1: Filtering 8... Skipped
Filter 1: Filtering 9... Skipped
Filter 1: Filtering 10... Skipped
Filter 1: Filtering 11... Skipped
Filter 1: Filtering 12... Skipped
Filter 1: Filtering 13... Skipped
Filter 1: Filtering 14... Skipped
Filter 1: Filtering 15... Skipped
Filter 1: Filtering 16... Included
Filter 2: Filtering 16... Skipped
Filter 1: Filtering 17... Included
Filter 2: Filtering 17... Skipped
Filter 1: Filtering 18... Included
Filter 2: Filtering 18... Included
Transforming 18...
The sum of squares in the zone: 324
The inTheZone value: 21
We've prepared to filter and map transform the values...
... now find and transform upon executing the terminal [reduce] method
Filter 1: Filtering 1... Skipped
Filter 1: Filtering 2... Skipped
Filter 1: Filtering 3... Skipped
...
Filter 1: Filtering 15... Skipped
Filter 1: Filtering 16... Included
Filter 2: Filtering 16... Skipped
Filter 1: Filtering 17... Included
Filter 2: Filtering 17... Skipped
Filter 1: Filtering 18... Included
Filter 2: Filtering 18... Included
Transforming 18...
Filter 1: Filtering 19... Included
Filter 2: Filtering 19... Skipped
Filter 1: Filtering 20... Included
Filter 2: Filtering 20... Skipped
Filter 1: Filtering 21... Included
Filter 2: Filtering 21... Included
Transforming 21...
Filter 1: Filtering 22... Included
Filter 2: Filtering 22... Skipped
Filter 1: Filtering 23... Included
Filter 2: Filtering 23... Skipped
Filter 1: Filtering 24... Included
Filter 2: Filtering 24... Included
Transforming 24...
Filter 1: Filtering 25... Included
Filter 2: Filtering 25... Skipped
The sum of squares in the zone: 1341
class LineItem {
static long TIME_TO_CALCULATE_TOTAL = 50;
private final Order order;
private final String product;
private final int quantity;
private final BigDecimal salePrice;
private final boolean taxable;
LineItem(Order order, String product, int quantity,
BigDecimal salePrice, boolean taxable) {
this.order = order;
this.product = product;
this.quantity = quantity;
this.salePrice = salePrice;
this.taxable = taxable;
}
BigDecimal calculateTotal() {
sleepAwhile();
return salePrice.multiply(new BigDecimal(quantity));
}
boolean isTaxable() { return taxable; }
...
class Order {
String orderId;
LocalDate orderDate;
BigDecimal taxRate;
final List<LineItem> lineItems = new ArrayList<>();
Order(String orderId, LocalDate orderDate, BigDecimal taxRate) {
...
}
BigDecimal calculateTaxableTotal() {
BigDecimal taxableTotal = BigDecimal.ZERO;
for(LineItem item: lineItems) {
if(item.isTaxable()) { taxableTotal = taxableTotal.add(item.calculateTotal()); }
}
return taxableTotal;
}
BigDecimal calculateNontaxableTotal() {
BigDecimal nontaxableTotal = BigDecimal.ZERO;
for(LineItem item: lineItems) {
if(!item.isTaxable()) { nontaxableTotal = nontaxableTotal.add(item.calculateTotal()); }
}
return nontaxableTotal;
}
BigDecimal calculateTax() {
return calculateTaxableTotal().multiply(taxRate).setScale(2, BigDecimal.ROUND_HALF_EVEN);
}
BigDecimal calculateTotal() {
return calculateNontaxableTotal().add(calculateTaxableTotal().add(calculateTax()));
}
...
public class OrderFactory {
static Order createOrder() {
Order order = new Order("Order 1", LocalDate.now(), new BigDecimal("0.05"));
addLineItems(order);
return order;
}
static void addLineItems(Order order) {
order.addLineItem(new LineItem(order, "Radio", 1, new BigDecimal("100.00"), true));
order.addLineItem(new LineItem(order, "Book", 2, new BigDecimal("15.00"), true));
order.addLineItem(new LineItem(order, "DVD", 1, new BigDecimal("10.00"), true));
order.addLineItem(new LineItem(order, "Milk", 1, new BigDecimal("3.50"), false));
order.addLineItem(new LineItem(order, "Fruit", 5, new BigDecimal("3.00"), false));
order.addLineItem(new LineItem(order, "Alcohol", 5, new BigDecimal("20.00"), true));
order.addLineItem(new LineItem(order, "Cereal", 3, new BigDecimal("5.00"), false));
order.addLineItem(new LineItem(order, "Soup", 10, new BigDecimal("1.00"), false));
order.addLineItem(new LineItem(order, "Bread", 2, new BigDecimal("3.00"), false));
order.addLineItem(new LineItem(order, "Eggs", 2, new BigDecimal("2.50"), false));
}
...
}
public class ImperativeExample {
public static void main(String[] args) {
long start = System.currentTimeMillis();
Order order = OrderFactory.createOrder();
System.out.println("Non-taxable total: " + order.calculateNontaxableTotal());
System.out.println("Taxable total: " + order.calculateTaxableTotal());
System.out.println("Tax: " + order.calculateTax());
System.out.println("Order total: " + order.calculateTotal());
System.out.println("Total time (secs): " +
(System.currentTimeMillis() - start) / 1000.0);
}
}
Non-taxable total: 54.50
Taxable total: 240.00
Tax: 12.00
Order total: 306.50
Total time (secs): 1.61
public class Order2 extends Order {
Order2(String orderId, LocalDate orderDate, BigDecimal taxRate) {
super(orderId, orderDate, taxRate);
}
@Override
public BigDecimal calculateTaxableTotal() {
return getStream().filter(LineItem::isTaxable)
.map(LineItem::calculateTotal)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
@Override
public BigDecimal calculateNontaxableTotal() {
return getStream().filter(item -> !item.isTaxable())
.map(LineItem::calculateTotal)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
private Stream<LineItem> getStream() {
return lineItems.stream();
}
}
public class OrderFactory {
...
static Order2 createOrder2() {
Order2 order = new Order2("Order 2", LocalDate.now(), new BigDecimal("0.05"));
addLineItems(order);
return order;
}
...
}
public class FunctionalExample {
public static void main(String[] args) {
Order order = OrderFactory.createOrder2();
long start = System.currentTimeMillis();
System.out.println("Non-taxable total: " + order.calculateNontaxableTotal());
System.out.println("Taxable total: " + order.calculateTaxableTotal());
System.out.println("Tax: " + order.calculateTax());
System.out.println("Order total: " + order.calculateTotal());
System.out.println("Total time (secs): "
+ (System.currentTimeMillis() - start) / 1000.0);
}
}
Non-taxable total: 54.50
Taxable total: 240.00
Tax: 12.00
Order total: 306.50
Total time (secs): 1.489
The imperative way...
Non-taxable total: 54.50
Taxable total: 240.00
Tax: 12.00
Order total: 306.50
Total time (secs): 1.61
Let's change it up a bit...
import java.util.function.Predicate
import java.util.stream.Collectors
public List<Integer> pickSome(List<Integer> values,
Predicate<Integer> pickingStrategy) {
values.stream()
.filter(pickingStrategy)
.collect(Collectors.toList())
}
List<Integer> values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
List<Integer> evens = pickSome(values, {value -> value % 2 == 0})
println evens
List<Integer> odds = pickSome(values) { value -> value % 2 != 0 }
println odds
// Groovy already had these functional behaviors years ago :-)
def threes = values.findAll { value -> value % 3 == 0 }
println threes
//... package and imports omitted
public class LambdaTestDemo {
final List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Predicate<Integer> numberPicker;
@Before
public void setUp() { numberPicker = value -> value % 2 != 0; }
@Test
public void testIsOddNumberLambda() { assertTrue("101 is odd",
numberPicker.test(101)); }
@Test
public void testOddNumberLambda_YieldsFalseForEvenNumbers() {
assertFalse("100 is not odd", numberPicker.test(100));
}
@Test
public void testOddNumberPicker() {
List<Integer> odds = numbers.stream()
.filter(numberPicker)
.collect(Collectors.toList());
assertEquals(Arrays.asList(1, 3, 5, 7, 9), odds);
}
}
http://384uqqh5pka2ma24ild282mv.wpengine.netdna-cdn.com/wp-content/uploads/2014/01/JavaLam-1.jpg
Title image of Duke chiseling Lambda from:
"Computers laugh at us after we go home..." courtesy of Neal Ford, @neal4d
Q. What about functional programming with futures?