Created by Mark Harrison / @markglh
default
& static
methods!
public interface Comparator<T> {
default Comparator<T> reversed() {
// Implementation here
}
public static Comparator<T> naturalOrder() {
// Implementation here
}
}
this
is effectively shared with the surrounding method)
(int x, int y) -> x + y
Argument types can be inferred by the compiler:
(x, y) -> x + y
Zero-arg Lambdas are also possible:
() -> "Java 8!"
But how can we use them?
Consumer<String> inferredConsumer =
x -> System.out.println(x);
@FunctionalInterface
annotation is provided to highlight these interfaces, it is just a marker though, any SAM interface will work - the annotation simply enforces this at compile timeHave you ever written code like this?
@FunctionalInterface //Added in Java8
public interface Predicate<T> {
boolean test(T t);
}
private void printMatchingPeople(
List<Person> people,
Predicate<Person> predicate) {
for (Person person : people) {
if (predicate.test(person)) {
System.out.println(person);
}
}
}
Pre Java 8:
public class PersonOver50Predicate
implements Predicate<Person> {
public boolean test(Person person) {
return person.getAge() > 50;
}
}
printMatchingPeople(loadsOfPeople,
new PersonOver50Predicate());
Java 8:
printMatchingPeople(loadsOfPeople,
x -> x.getAge() > 50);
Notice the signature matches that of the Predicate, the compiler automatically figures out the rest
What if we had an existing Predicate
we wanted to enhance?
Predicate<Person> ageCheck = x -> x.getAge() > 50;
printMatchingPeople(loadsOfPeople,
ageCheck.and(x -> x.getIq() > 100));
Composite Predicates
FTW... :-)
Pre Java 8 mess:
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Meh!");
}
};
r1.run();
Java 8:
Runnable r = () -> System.out.println("Woop!");
r.run();
Lambdas make lots of new Collection methods possible...
List<String> strings = new ArrayList<>();
Collections.addAll(strings, "Java", "7", "FTW");
Do something on every element in the List
strings.forEach(x -> System.out.print(x + " "));
//Prints: "Java 7 FTW"
Replace every matching element in the List
strings.replaceAll(x -> x == "7" ? "8" : x);
strings.forEach(x -> System.out.print(x + " "));
//Prints: "Java 8 FTW"
Remove matching elements from the List
strings.removeIf(x -> x == "8");
strings.forEach(x -> System.out.print(x + " "));
//Prints: Java FTW
@FunctionalInterface //Added in Java8 version
public interface Comparator<T> {
int compare(T o1, T o2);
//Java 8 adds loads of default methods
}
List<Person> loadsOfPeople = ...;
Pre Java 8:
public class SortByPersonAge
implements Comparator<Person> {
public int compare(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
}
Collections.sort(loadsOfPeople,
new SortByPersonAge());
Java 8:
Collections.sort(loadsOfPeople,
(p1, p2) -> p1.getAge() - p2.getAge());
As usual in Java 8... the Comparator
interface provides plenty of useful default & static methods...
//"comparing" static method simplifies creation
Comparator<Person> newComparator =
Comparator.comparing(e -> e.getIq());
//"thenComparing" combines comparators
Collections.sort(loadsOfPeople,
newComparator.thenComparing(
Comparator.comparing(e -> e.getAge())));
and more...
//More useful Collection methods...
loadsOfPeople4.sort(
Comparator.comparing(e -> e.getIq()));
//And finally... Method references
loadsOfPeople.sort(
Comparator.comparing(Person::getAge));
Reference to... | Example |
---|---|
a static method | Class::staticMethodName |
an instance method of a specific object | object::instanceMethodName |
an instance method of an arbitrary object | Class::methodName |
a constructor | ClassName::new |
A simple reference to a static method
//isPersonOver50 is a static method
printMatchingPeople(loadsOfPeople,
PersonPredicates::isPersonOver50);
This is equivalent to
printMatchingPeople(loadsOfPeople,
x -> x.getAge() > 50);
A reference to a method on an object instance
List<String> strings = ...
//print is a method on the "out" PrintStream object
strings.forEach(System.out::print);
This is equivalent to
strings.forEach(x -> System.out.print(x));
Examine this simplified definition of a map function
public interface Function<T,R> {
public R apply(T t);
}
public <T, R> List<R> map(Function<T, R> function,
List<T> source) {
/* applies the function to each element,
converting it from T to R */
}
Although it looks like we're referencing a Class method, we're invoking an instance method on the object(s) passed in the call
List<Person> loadsOfPeople = ...
List<Integer> namesOfPeople =
map(Person::getAge, loadsOfPeople);
This is equivalent to
map(person -> person.getAge(), loadsOfPeople);
Uses the constructor to create new objects, the constructor signature must match that of the @FunctionalInterface
List<String> digits = Arrays.asList("1", "2", "3");
//Transforms a String into a new Integer
List<Integer> numbers = map(Integer::new, digits);
This is equivalent to
map(s -> new Integer(s), digits);
Imagine writing this manually
Stream<String> words=Stream.of("Java", "8", "FTW");
Map<String, Long> letterToNumberOfOccurrences =
words.map(w -> w.split(""))
.flatMap(Arrays::stream)
.collect(Collectors.groupingBy(
Function.identity(),
Collectors.counting()));
//Prints: //{a=2, T=1, F=1, v=1, W=1, 8=1, J=1}
Stream
is a conceptually fixed data structure in which elements are computed on demandparallelStream
or parallel()
Can be created from multiple sources:
Arrays.stream(...)
, Stream.of(1, 2, 3, 4)
, Stream.iterate(...)
, Stream.range(...)
, Random.ints()
, Files.lines(...)
...Two types of operations:
Specialised Streams:
public <R> Stream<R> map(Function<T, R> mapper);
The mapper
Function converts each element from T
to R
. The result is then added, as is, to the Stream
public <R> Stream<R> flatMap(Function<T,
Stream<R>> mapper);
mapper
Function converts each element from T
to a Stream
of R
map
, the function itself returns a Stream
rather than one elementStream
is then flattened (merged) into the main Stream
To put it another way: flatMap lets you replace each value of a Stream with another Stream, and then it concatenates all the generated streams into one single stream
public T reduce(T identity,
BinaryOperator<T> accumulator);
public Optional<T> reduce(
BinaryOperator<T> accumulator);
//This is the function contained in BinaryOperator
R apply(T t, U u);
Stream
of values and repeatedly applies the accumulator to reduce them into a single valueT
) and the current element (U
)identity
provides the initial value, rather than the first element
int totalAgeUsingReduce = loadsOfPeople.stream()
.map(Person::getAge) //contains 5, 10, 15
.reduce(0, (total, current) -> total + current);
Person
to the age int
0
, and adds the current element, 5
5
, to the next element, 10
15
, to the next element 15
30!
The collect method is a terminal operation which takes various "recipes", called Collectors
for accumulating the elements of a stream into a summary result, or converting to a specific type (such as a List
)
List<String> listOfStrings = loadsOfPeople.stream()
.map(x -> x.getName())
.collect(Collectors.toList());
Grouping
, Partitioning
, Averaging
, ...
List<Integer> numbers = Arrays.asList(1, 2 ... 8);
List<Integer> twoEvenSquares = numbers.stream()
.filter(n -> n % 2 == 0) //Filter odd numbers
.map(n -> n * n) ////Multiply by itself
.limit(2)//Limit to two results
.collect(Collectors.toList()); //Finish!
Imagine a println
in each step...
filtering 1
filtering 2
mapping 2
filtering 3
filtering 4
mapping 4
twoEvenSquares = List[4, 16]
List<String> youngerPeopleSortedByIq =
loadsOfPeople.stream()
.filter(x -> x.getAge() < 50)
.sorted(Comparator
.comparing(Person::getIq).reversed())
.map(Person::getName)
.collect(Collectors.toList());
List
int combinedAge =
loadsOfPeople.stream()
.mapToInt(Person::getAge) //returns IntStream
.sum(); //this HAS to be a specialised Stream
IntStream
average
String xml =
"<people>" +
loadsOfPeople.stream()
.map(x -> "<person>"+ x.getName() +"</person>")
.reduce("", String::concat) //start with ""
+ "</people>";
map each Person
to an XML element (<person>Steve</person>), then use String.concat to reduce
the Stream
into one XML String
<people>
<person>Dave</person>
<person>Helen</person>
<person>Laura</person>
<person>Ben</person>
</people>
List<Stream<Person>> clonedPeople =
loadsOfPeople.stream()
.map(person -> Stream.of(person, person.dolly()))
.collect(Collectors.toList());
Stream
is then added to the main Stream as-is, leaving us with a pretty useless: List<Stream<Person>>
List<Person> clonedPeople2 = loadsOfPeople.stream()
.flatMap(person -> Stream.of(person,
person.dolly()))
.collect(Collectors.toList());
Streams
into one Stream<Person>
List<Person>
Sweeet!
int totalAgeUsingReduce = loadsOfPeople.stream()
.map(Person::getAge)
.reduce((total, current) -> total + current)
.get(); //get the result from the Optional
Optional
... if the Stream
is empty then so is the result!
Map<Integer, List<Person>> peopleGroupedByAge =
loadsOfPeople.stream()
.filter(x -> x.getIq() > 110)
.collect(Collectors.groupingBy(Person::getAge));
{52=[Person{... age=52, iq=113, gender=MALE}],
60=[Person{... age=60, iq=120, gender=FEMALE}],
28=[Person{... age=28, iq=190, gender=MALE}]}
Map<Boolean, List<Person>> peoplePartitionedByAge =
loadsOfPeople.stream().filter(x -> x.getIq() > 110)
.collect(Collectors
.partitioningBy(x -> x.getAge() > 55));
Map
will have two entries, true and false, according to the Predicate
{false=[Person{... age=28, iq=190, gender=MALE}],
true=[Person{... age=60, iq=120, gender=FEMALE}]}
Map<Integer, Double> peopleGroupedBySexAndAvgAge =
loadsOfPeople.stream()
.filter(x -> x.getIq() > 110)
.collect(
Collectors.groupingBy(Person::getAge,
Collectors.averagingInt(Person::getIq)));
Collectors
{52=113.0, 60=117.5, 28=190.0}
loadsOfPeople.stream()
.filter(t -> t.getGender() == Person.Sex.FEMALE)
.findAny()
.ifPresent(System.out::println);
findAny
either returns an element or nothing, hence we get an OptionalifPresent
executes the Lambda if we get a resultLets iterate over 10,000,000 elements!
x -> Stream.iterate(1L, i -> i + 1)
.limit(x)
.reduce(Long::sum).get();
Executes in 80ms - we incur a penalty here because the long
is repeatedly boxed and unboxed
x -> Stream.iterate(1L, i -> i + 1)
.parallel().limit(x)
.reduce(Long::sum).get();
Executes in 211ms?! It turns out parallel isn't always a free win!
x -> LongStream.rangeClosed(1L, x)
.reduce(Long::sum).getAsLong();
Executes in 24ms - much better using an unboxed Stream
x -> LongStream.rangeClosed(1L, x)
.parallel()
.reduce(Long::sum).getAsLong();
Executes in 7ms - now that the Stream
isn't dynamic, parallel works much better!
Optional
instead of passing null around, helps prevent NullPointerExceptions
Optional
is a container that’s either empty or present, in which case it contains a valuenull
value in a method, use Optional
instead!
public class Computer {
private Optional<Mainboard> mainboard;
}
public class Mainboard {
private Optional<Cpu> cpu;
}
public class Cpu {
private String type;
}
Several ways to create an Optional
:
Optional.of(cpu); //Throws an NPE if cpu is null
Optional.ofNullable(cpu); //empty if cpu is null
Getting the contents from the Optional
:
cpu.get(); //get CPU or throw NoSuchElementException
cpu.orElse(new Cpu()); //safe get, provides default
And more...
cpu.isPresent(); //true if present, false if empty
cpu.ifPresent(x -> System.out.println(x.getType()));
Also supports map, flatMap and filter!
if (mainboard.isPresent() &&
mainboard.get().getCpu().isPresent()) {
mainboard.get().getCpu().get().getType();
}
Eww! Lets try something else!
Optional<String> cpuType =
mainboard.map(Mainboard::getCpu)
.map(Cpu::getType); //Optional<Optional<Cpu>>
***Fails to compile, calling getType
on an Optional
... if only we could flatten it???
Optional<String> stringOptional = mainboard
.flatMap(Mainboard::getCpu)
.map(Cpu::getType);
Lets take this further and safely get cpu type of a Computer
!
Computer computer = new Computer(mainboard);
String cpu = computer.getMainboard()
.flatMap(Mainboard::getCpu)
.map(Cpu::getType)
.filter(x -> "Intel".equals(x))
.orElse("Nothing we're interested in!");