Default Methods For Interfaces: Streams Are Mondas
Default Methods For Interfaces: Streams Are Mondas
Default Methods For Interfaces: Streams Are Mondas
Lambda expressions
//For one line method bodies you can skip both the braces {} and the return keyword. But
it gets even more shorter:
Collections.sort(names, (a, b) -> b.compareTo(a));
//The java compiler is aware of the parameter types so you can skip them as well. Let's
dive deeper into how lambda expressions can be used in the wild.
Functional Interfaces
What? added in java 8
They are interface with only one abstract method
Examples of such interfaces (before java8)
1. Comparator interface
Had only one abstract method compare
That was used to sorting list of object
NOTE: it has two abstract methods compare and equal. How it is possible?
o Equal method is present in object class. Any object by default extends
the Object class. Usually they do implicitly but here they are doing it
explicitly. As long as method signature of equals method is same as
what you have in object class, that kind of abstract method is allowed
2. Runnable interface
had only one method run
is implemented by the class which wants to create a thread
Example:
1.
2. @FunctionalInterface
3. interface Converter<F, T>{
4. T convert(F from);
5. }
6.
7. Converter <String, Integer> converter = (from)->Integer.valueOf(from);
8. Integer converted = converter.convert("123");
9. System.out.println(converted); // 123
Java 8 enables you to pass references of methods or constructors via the s::` keyword. The
above example shows how to reference a static method (which method?). But we can also
reference object methods:
1. class Something {
2. String startsWith(String s) {
3. return String.valueOf(s.charAt(0));
4. }
5. }
6. Something something = new Something();
7. Converter < String, String > converter = something::startsWith;
8. String converted = converter.convert("Java");
9. System.out.println(converted); // "J"
Let's see how the `::` keyword works for constructors. First we define an example bean with
different constructors:
1. class Person {
2. String firstName;
3. String lastName;
4. Person() {}
5. Person(String firstName, String lastName) {
6. this.firstName = firstName;
7. this.lastName = lastName;
8. }
9. }
Next we specify a person factory interface to be used for creating new persons:
Instead of implementing the factory manually, we glue everything together via constructor
references:
We create a reference to the Person constructor via Person::new. The Java compiler
automatically chooses the right constructor by matching the signature
of PersonFactory.create.
Lambda Scopes
1. Accessing outer scope variables from lambda expressions
o is very similar to anonymous objects.
2. You can access Final variables, instance fields and static variable from the local
outer scope
But different to anonymous objects the variable num does not have
to be declared final. However, num must be implicitly final for the
code to compile. The following code does not compile:
1. int num = 1;
2. Converter<Integer, String> stringConverter = (from)-> String.valueOf(from + num);
3. num = 3;
Functions
They accept one argument and produce a result.
Default methods can be used to chain multiple functions together (compose,
andThen).
Suppliers#
They produce a result of a given generic type.
Unlike Functions, Suppliers don't accept arguments.
Consumers#
Consumers represents operations to be performed on a single input argument.
1. Consumer < Person > greeter = (p) - > System.out.println("Hello, " + p.firstName);
2. greeter.accept(new Person("Luke", "Skywalker"));
Comparators#
Java 8 adds various default methods to the interface.
Optionals#
It is a nifty utility to prevent NullPointerException. (not functional interfaces)
How it works?.
a simple container for a value which may be null or non-null.
o Think of a method which may return a non-null result but sometimes return
nothing. Instead of returning null you return an Optional in Java 8.
// NOTE: Sorted create a sorted view of the stream without manipulating the ordering of
the backed collection.
o
4. Map# boolean anyStartsWithA = stringCollection
o The intermediate operation .stream()
o converts each element into .anyMatch((s) -> s.startsWith("a"));
another object via the given System.out.println(anyStartsWithA);
function. // true
5. Match# // other example
o Various matching operations allMatch,
using predicates. noneMatch
o It is terminal and return a
boolean result.
6. Count# Optional<String> reduced=stringCollection
o terminal operation .stream()
o returning the number of .sorted()
elements in the stream as a .reduce((s1, s2) -> s1 + "#" + s2);
long
7. Reduce reduced.ifPresent(System.out::println);
o terminal operation //"aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"
o performs a reduction on the
elements of the stream with
the given function.
o The result is
an Optional holding the
reduced value.
Parallel Streams#
o Operations on sequential streams are performed on a single thread
o operations on parallel streams are performed concurrent on multiple threads.
Stream operations
Behavior same as a functional.
Operation must be
1. non-interfering
o it does not modify the underlying data source of the stream.(adding/
removing or updating elements)
2. stateless
o A function is stateless when the execution of the operation is deterministic,
no lambda expression depends on any mutable variables or states
from the outer scope which might change during execution.
personsByAge
.forEach((age, p) -> System.out.format("age
%s: %s\n", age, p));
System.out.println(map);
// {18=Max, 23=Peter;Pamela, 12=David}
Now that we know some of the most powerful built-in collectors, let's try to
build our own special collector. We want to transform all persons of the
stream into a single string consisting of all names in upper letters separated
by the | pipe character. In order to achieve this we create a new collector
via Collector.of() . We have to pass the four ingredients of a collector:
a supplier, an accumulator, a combiner and a finisher.
Collector<Person, StringJoiner, String> personNameCollector =
Collector.of(
() -> new StringJoiner(" | "), // supplier
(j, p) -> j.add(p.name.toUpperCase()), // accumulator
(j1, j2) -> j1.merge(j2), // combiner
StringJoiner::toString); // finisher
Clock#
Clock provides access to the current date and time. Clocks are aware of a timezone and may be used instead of System.currentTimeMillis() to retrieve the current milliseconds. Such an
instantaneous point on the time-line is also represented by the class Instant . Instants can be used to create legacy java.util.Date objects.
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
Timezones#
Timezones are represented by a ZoneId . They can easily be accessed via static factory methods. Timezones define the offsets which are important to convert between instants and local dates and
times.
System.out.println(ZoneId.getAvailableZoneIds());
// prints all available timezone ids
// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]
LocalTime#
LocalTime represents a time without a timezone, e.g. 10pm or 17:30:15. The following example creates two local times for the timezones defined above. Then we compare both times and calculate the
difference in hours and minutes between both times.
LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);
System.out.println(now1.isBefore(now2)); // false
System.out.println(hoursBetween); // -3
System.out.println(minutesBetween); // -239
LocalTime comes with various factory method to simplify the creation of new instances, including parsing of time strings.
LocalTime late = LocalTime.of(23, 59, 59);
System.out.println(late); // 23:59:59
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedTime(FormatStyle.SHORT)
.withLocale(Locale.GERMAN);
LocalDate#
LocalDate represents a distinct date, e.g. 2014-03-11. It's immutable and works exactly analog to LocalTime. The sample demonstrates how to calculate new dates by adding or substracting days, months
or years. Keep in mind that each manipulation returns a new instance.
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
LocalDate yesterday = tomorrow.minusDays(2);
3. System.out.println(xmas); // 2014-12-24
LocalDateTime#
LocalDateTime represents a date-time. It combines date and time as seen in the above sections into one instance. LocalDateTime is immutable and works similar to LocalTime and LocalDate. We can
utilize methods for retrieving certain fields from a date-time:
3. System.out.println(dayOfWeek); // WEDNESDAY
5. System.out.println(month); // DECEMBER
7. System.out.println(minuteOfDay); // 1439
With the additional information of a timezone it can be converted to an instant. Instants can easily be converted to legacy dates of type java.util.Date .
Annotations#
Annotations in Java 8 are repeatable. Let's dive directly into an example to
figure that out.
First, we define a wrapper annotation which holds an array of the actual
annotations:
@interface Hints {
Hint[] value();
}
@Repeatable(Hints.class)
@interface Hint {
String value();
}
Java 8 enables us to use multiple annotations of the same type by
declaring the annotation @Repeatable .
Variant 1: Using the container annotation (old school)#
@Hints({@Hint("hint1"), @Hint("hint2")})
class Person {}
Variant 2: Using repeatable annotations (new school)#
@Hint("hint1")
@Hint("hint2")
class Person {}
Using variant 2 the java compiler implicitly sets up the @Hints annotation
under the hood. That's important for reading annotation informations via
reflection.
Hint hint = Person.class.getAnnotation(Hint.class);
System.out.println(hint); // null