Java 8
(notes on Java 8 Tutorial )
Default Methods for Interfaces
a non-abstract method to interface, by utilizing the default
keyword. also known as Extension Methods
interface Formula {
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
// usage (anonymous object)
Formula formula = new Formula() {
@Override
public doble calculate(int a) {
return sqrt(a * 100);
}
};
formula.calculate(100); // 100.0
formula.sqrt(16); // 4.0
Lambda expresssions
before Java 8:
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
Collection.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
with Java 8 lambda:
Collection.sort(names, (String a, String b) -> {
return b.compareTo(a);
});
// one line method body you can skip {}
Collection.sort(names, (String a, String b) -> return b.compareTo(a));
// you can skip parameter types too, java will get it from the context
Collection.sort(names, (a, b) -> return b.compareTo(a));
Function Interfaces
each lambda corresponds to a given type, specified by an interface - functional interface
the interface must contain exactly one abstract method declaration.
since default methods (mentioned above) are not abstract, you can add default methods to your functional interface.
@FunctionalInterface
annotation allows compiler check your interface meets the requirements.
example:
@FunctionalInterface
interface Converter<F, T> {
T convert(F from);
}
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123"); // 123
Method and Constructor References
the above example can be further simplified by utilizing static method reference:
Converter<String, Integer> converter = Integer::valueOf;
Java 8 enables you to pass references of methods or constructors via the ::
keyword.
// can also reference non-static methods
class Something {
String startsWith(String s) {
return String.valueOf(s.charAt(0));
}
}
Something something = new Something();
Converter<String, String> converter = something::startsWith;
String converted = converter.convert("Java"); // "J"
how ::
keyword works for constructors:
class Person {
String firstName;
String lastName;
Person() {}
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
interface PersonFactory<P extends Person> {
P create(String firstName, String lastName);
}
PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");
Java autometically choose the right constructor by matching the signature of PersonFactory.create
for the reference Person::new
Lambda Scopes
similar to anonymous objects, you can access final variables from the local outer scope, instance fields, and static variables.
read local variables
final int num = 1;
Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);
stringConverter.convert(2); // 3
different to anonymous object, non-final varables also available in lambda:
int num = 1;
Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);
stringConverter.convert(2); // 3
however, num
must be implicitly final
// does not compile
int num = 1;
Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);
num = 3;
writing to num
from within the lambda expresssion is also prohibited.
Accessing fields and static variables
we have both read and write access to instance fields and static variables from within lambda expresssions (just like anonymous objecs)
class Lambda4 {
static int outerStaticNum;
int outerNum;
void testScope() {
Converter<Integer, String> stringConverter1 = (from) -> {
outerNum = 23;
return String.valueOf(from);
};
Converter<Integer, String> stringConverter2 = (from) -> {
outerStaticNum = 72;
return String.valueOf(from);
};
}
}
Accessing default interface methods
Default methods cannot be accessed from within lambda expresssions.
// will not compile:
Formula formula = (a) -> sqrt( a * 100 );
Build-in Functional Interfaces
Existing interfaces are extended to enable lambda support via the @FunctionalInterface
annotation, like Comparator
and Runnable
.
New functional interfaces:
Predicates
Predicate<T>
: take a T as input, return a boolean as output
Predicates are boolean-valued functions of one argument. The interface contains various default methods for composing predicates to complex logical terms (and
, or
, negate
)
Predicate<String> predicate = (s) -> s.length() > 0;
predicate.test("foo"); // true
predicate.negate().test("foo"); // false
Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();
Functions
Function<T,R>
: take a T as input, return an R as output
Functions accept one argument and produce a result. Default methods can be used to chain multiple functions together (compose
, andThen
)
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);
backToString.apply("123"); // "123"
Suppliers
Supplier<T>
: with nothing as input, return a T
Suppliers produce a result of a given generic type. Unlike Functions, Suppliers don't accept arguments.
Supplier<Person> personSupplier = Person::new;
personSupplier.get(); // new Person
Consumers
Consumer<T>
: take a T as input, return nothing
Consumers represents operations to be performed on a single input argument.
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));
Comparators
Java 8 addes various default methods to the interface.
Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
comparator.compare(p1, p2); // > 0
comparator.reversed().compare(p1, p2); // < 0
Optionals
Optionals are not functional interface, it's a nifty utility to prevent NullPointerException
.
Optional is a simple container for a value which maybe null or no-null.
for a method which may return nothing, instead of returning null
you return an Optional
in Java 8.
Optional<String> optional = Optional.of("bam");
optional.isPresent(); // true
optional.get(); // "bam"
optional.orElse("fallback"); // "bam"
optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
Streams
A java.util.Stream
represents a sequence of elements on which one or more operations can be performed.
Stream operations are either intermediate (return stream itself) or terminal (return result).
Stream operations can either be executed sequential or parallel.
Streams are created on a source (e.g. a java.util.Collection
like lists or sets [maps are not supported])
List<String> stringCollection = new ArrayList<>();
stringCollection.add("ddd2");
stringCollection.add("aaa2");
stringCollection.add("bbb1");
// ...
Collections in Java 8 are extended, you can create streams either by Collection.stream()
or Collection.parallelStream()
Filter
intermediate operation
stringCollection
.stream()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
forEach
is a terminal operation.
Sorted
intermediate operation
sorted in natural order unless you pass a custom Comparator
stringCollection
.stream()
.sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
sorted()
only creates a sorted view of the stream, the ordering of stringCollection
is untouched.
Map
intermediate operation
you can use map
to transform each object into another type. the generic type of the resulting stream depends on the generic type of the function you pass to map
.
stringCollection
.stream()
.map(String::toUpperCase)
.sorted((a, b) -> b.compareTo(a))
.forEach(System.out::println);
Match
terminal operations and return a boolean result.
boolean anyStartsWithA =
stringCollection
.stream()
.anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA); // true
boolean allStartsWithA =
stringCollection
.stream()
.allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA); // false
boolean noneStartsWithZ =
stringCollection
.stream()
.noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ); // true
Count
terminal operation and returns the number of elements in the stream as a long
.
long startsWithB =
stringCollection
.stream()
.filter((s) -> s.startsWith("b"))
.count();
Reduce
terminal operation and returns an Optional
holding the reduced value.
Optional<String> reduced =
stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);
Parallel Streams
int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}
// sequential sort
long t0 = System.nanoTime();
long count = value.stream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("sequential sort took: %d ms", millis));
// parallel sort
...
long count = value.parallelStream().sorted().count();
...
Map
As already mentioned maps don't support streams. Instead maps now support various new and useful methods for doing common tasks.
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
map.putIfAbsent(i, "val" + i);
}
map.forEach(id, val) -> System.out.println(val));
map.computeIfPresent(3, (num, val) -> val + num);
map.get(3); // val33
// utilizing functions
map.computeIfPresent(9, (num, val) -> null);
map.containKey(9); // false
map.computeIfAbsent(23, num -> "val" + num);
map.containKey(23); // true
map.computeIfAbsent(3, num -> "bam");
map.get(3); // val33
// remove
map.remove(3, "val3");
map.get(3); // val33
map.remove(3, "val33");
map.get(3); // null
// another helpful method
map.getOrDefault(42, "not found"); // not found
// merging
map.merge(9, "val9", (value, newValue) -> vale.concat(newValue));
map.get(9); // val9
map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9); // val9concat
merge either put the key/value into the map if no entry for the key exists, or the merging function will be called to change the existing value.
Date API
the new Date API is not the same as Joda-Time library.
Clock
Clock provides access to the current date and time.
Clocks are aware of a timezone.
Instant
class can be used to create legacy java.util.Date
objects.
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
Instant instant = clock.instant();
Date legacyDate = Date.from(instant); // legacy java.util.Date
Timezones
Timezones are represented as a ZoneId
// prints all available timezone ids
System.out.println(ZoneId.getAvailableZoneIds());
ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
System.out.println(zone1.getRules());
System.out.println(zone2.getRules());
LocalTime
LocalTime represents a time without a timezone.
LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);
System.out.println(now1.isBefore(now2)); // false
long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2));
System.out.println(hoursBetween); // -3
System.out.println(minutesBetween); // -239
LocalTime comes with various factory method to simplify the creation of new instances.
LocalTime late = LocalTime.of(23, 59, 59);
System.out.println(late); // 23:59:59
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedTime(FormatStyle.SHORT)
.withLocale(Locale.GERMAN);
LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);
System.out.println(leetTime); // 13:37
LocalDate
LocalDate represents a distinct date, e.g. 2014-03-11.
it's immutable.
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
LocalDate yesterday = tomorrow.minusDays(2);
LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DateOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek); // FRIDAY
parsing a LocalDate from string is just like parsing a LocalTime
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.GERMAN);
LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas); // 2014-12-24
LocalDateTime
LocalDateTime represents a date-time. immutable.
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek); // WEDNESDAY
Month month = sylvester.getMonth();
System.out.println(month); // DECEMBER
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay); // 1439
// convert to instant
Instant instant = sylvester
.atZone(ZoneId.systemDefault())
.toInstant();
Date legacyDate = Date.from(instant);
System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014
// formatting
DateTimeFormatter formatter =
DateTimeFormatter
.ofPattern("MMM dd, yyyy - HH:mm");
LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string); // Nov 03, 2014 - 07:13
Unlike java.text.NumberFormat
the new DateTimeFormatter
is immutable and thread-safe.