In Java 8 major changes where introduced to Java. This is a review of the main new features from Java 8 to 17 focusing in the practical uses of the language
Java 8 |
Java 9 |
Java 10
|
Java 11
|
Java 12 |
Java 13
|
Java 14
|
Java 15
|
Java 16
|
Java 17 (TBD) |
Java 8
Interfaces
Prior to java 8, interface in Java can only have abstract methods. Now default and static methods are allowed in.
interface MyInterface{
// Default method do not need to be implemented the implementation classes
default void newMethod() {
System.out.println("Newly added default method");
}
// Static method in interface is similar to default method except that we
// cannot override them in the implementation classes.
static void anotherNewMethod() {
System.out.println("Newly added static method");
}
// Typical abstract method that must be implemented in the implementation classes
void existingMethod(String str);
}
Functional programming
Functional interface (@FunctionalInterface)
Interface with only one abstract method (default or static methods don't count!!!)
@FunctionalInterface public interface Comparator {
int compare(T o1, T o2);
// Methods implemented from Object (e.g. equals)
boolean equals(Object obj);
}
Lambda expression
Implementation of an anonymous class. The type is a functional interface. Do not creates an object (not using new) so it is much cheaper (do not need to gets the memory, static and non-static blocks, executes the constructor and all super constructors). It is an object without identity.
// (params) -> {body}
Comparator c = (String s1, String s2) -> Integer.compare(s1.length(), s2.length));
// Compiler can infer the type
Comparator c = (s1, s2) -> Integer.compare(s1.length(), s2.length));
// using an anonymous class
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
// using a lambda expression
Runnable r2 = () -> System.out.println("Hello");};
//Anonymous Consumer (this instantiate an implementation of the Consumer interface using an anonymous class) vs Lambda
Consumer<String> printConsumer= new Consumer<String>() {
public void accept(String name) {
System.out.println(name);
}
}; // same than: name -> System.out.println(name)
Functional classes predefined
- Supplier(() -> T)
- Consumer(T -> ()), BiConsumer((T , Z) -> ())
- Predicate (T -> Boolean), BiPredicate((T , Z) -> Boolean)
- Function((T -> Z))
- BiFunction((T , Z -> U))
- UnaryOperator((T -> T))
- BinaryOperator((T , T -> T))
Predicate p1 = s -> s.length() < 20;
Predicate p2 = s -> s.length() > 10;
Predicate p3 = p1.and(p2);
List strings = Arrays.asList("one", "two");
List list = new ArrayList<>();
Consumer print = System.out::println;
Consumer add = list::add;
// Channing consumers!
strings.forEach(print.andThen(add));
Method reference
Simplified way to write some labmda expresions
a -> Class.method(a) === Class::method
Comparator c = (i1,i2) -> Integer.compare(i1, i2)
Comparator c = Integer::compare
Streams
A Stream in Java can be defined as a sequence of elements from a source that supports aggregate operations on them.
Can be executed only once (the terminate operation closes the stream so only one terminate operation can be executed for one stream). Only declares a pipeline (very cheap -until terminate is executed-)
It is a way to process data (in parallel and pipelined). There are 3 kinds of operations: Stream, intermediary and terminate.
Stream operation
Creates the stream. A stream is lazy executed. This means it is not executed until the terminate operation does. Then the stream is closed (only can be executed once)
Intermediary operations
They are lazy. Only declares a pipeline, do not executes the operation. Returns a stream (Stream<T>).
- map(Function<T,R>): Applies a function over the Stream. people.map(person -> person.getName())
- filter(Predicate<T>): Filter elements depending on a Predicate(T -> Boolean). people.filter(person -> person.getName().startsWith("A"))
- flatMap(Function<T,Stream<R>> flatMapper): Flats object of objects into objects. Unwraps types to apply to Stream operations that do not support them.
Stream<String[]> -> flatMap -> Stream<String> Stream<Set<String>> -> flatMap -> Stream<String> { {'Jose','John'}, {'Charles'}, {'Mr.T'} } -> flatMap -> {'Jose','John','Charles','Mr.T'} // List all people of a list of teams (containing a List<String> people) Function<List<Integer>,Stream<Integer> flatmapper = l -> l.stream(); teams.stream().map(team -> team.getPeople()) // returns Stream<List<String>> .flatmap(flatmapper) // returns Stream<String> .distinct() .collect(Collectors.List());
- peek(Consumer<T>)
Teminate operation
Executes all the stream pipeline and then closes the Stream. So the Stream can be executed only once. Examples:
- forEach(Consumer<? super T> action): Executes a function for each element of the collection.
- collect: Mutable reduction. Put results into container like list. collect(Collectors.toList()) collect(Collectors.groupingBy(Person::Age)) // this returns a map where the key are ages and values people of this values.
- reduce (Object identity, BinaryOperator<T> accumulator). Identity is the initial value. BinaryOperator extends BiFunction with (T, T) -> T. The reduction of a stream empty argument is the identity. If has only one argument then is this argument. If it is executed in parallel needs a combiner.
- Aggregation: min,max,sum, count...
- Boolean: allMatch,noneMatch,anyMatch...
- Optional: findFirst(), findAny()...
Examples
people.stream().forEach(System.out::println);
Map<Integer,List> result = persons.stream().collect(Collectors.groupingBy(Person::getAge());
Map<Integer,Long> result = persons.stream().collect(Collectors.groupingBy(Person::getAge(),Collectors.counting()); //downstream collector counts the number of people of each age
Map<Integer,List> result = persons.stream().collect(Collectors.groupingBy(Person::getName(),Collectors.toList());
ages.stream().reduce(0,(age1,age2) -> age1+age2); (T,T->T)
[-10,-5].stream().reduce(0, Integer::max) // max(0,max(-10,-5))) = 0
Optional
Store an Object or a void value.
Optional opt = Optional.ofNullable(myNullableString);
List people = ...
Optional minAgeBiggerThan20 = people.stream()
.map(person -> person.getAge()) // Stream, intermediary
.filter(age -> age > 20) // Stream, intermediary
.min(Comparator.naturalOrder()); // Optional, terminal
New Date API
- New package: java.time
- Precision is nanosecond
- Instant.MIN 1 billions years ago / Instant.MAX Dec 31 of the year 1.000.000.000
- New types: Instant, Duration, LocalDate, Period, LocalTime, ZoneDateTime
- Old Date is interoperable with the new types e.g. Date.from(instant);
Instant instant = Instant.now() //is the current instant. Is inmutable (small cost)
Duration elapsed = Duration.between(startInstant, endInstant); // amount of time between two instants
long millis = elapsed.toMillis();
// LocalDate: Is just a date with a day presition (not nanosecond presition)
LocalDate now = LocalDate.now();
LocalDate birth = LocalDate.of(1985, Month.APRIL, 23);
Period: Amount of time between to LocalDates;
Period p = birth.until(LocalDate.now(); p.getYears();
long days = birth.until(LocalDate.now(), ChronoUnit.DAYS);
// DateAdjuster: Useful to add or substract amounts of time to Instants or LocalDate
LocalDate nextSunday = LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
LocalTime: Is just a time of a day
LocalTime time = LocalTime.of(10, 20); // 10:20
// Zone Time: Implements the time different time zones
String ukTimeZone= ZoneId.getAvailableZoneIds().of("Europe/London");
ZonedDateTime.of(1943, Month.APRIL.getValue(), 23, // year month day
10, 0, 0, 0, // h / mn / s / nanos
ZoneIf.of("Europe/London")); // 1943-04-23T10:00-00:01:15[Europe/London]
ZonedDateTime z = ZonedDateTime.of(LocalDate.of(2014,Month.MARCH,12), Localtime.of(9,30), ZoneId.of("Europe/London"));
z.plus(Period.ofMonth(1));
z.withZoneSameInstant(ZoneId.of("US/Central")); // change time zone
System.out.println(DateTimeFormatter.ISO_DATE_TIME.format(z)); // format date to print it
//Use java.time instead java.util.Date
LocalDate date1 = LocalDate.of(1985,12,5);
date1.datesUntil(LocalDate.now(),Period.ofYears(1)).map(date -> Year.of(date.getYear())).count():
String
// Stream of chars
IntStream stream = "Hello".chars();
stream.mapToObj(letter -> (char)letter).map(Character::toUpperCase).forEach(System.out::print);
//Concatenation: Use StringJoiner
StringJoiner sj = new StringJoiner(", ");
sj.add("one").add("two").add("three"); // one, two, three
StringJoiner sj = new StringJoiner(", ", "{","}");
sj.add("one").add("two").add("three"); // {one, two, three}
String.join(", ","one","two","three");
Try with resources
Autoclose the try enclosed object. Implements AutoCloseable interface (Java 7)
Scanner scanner = null;
try {
scanner = new Scanner(new File("test.txt"));
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
// Multiple catch exception also added in Java 7
} catch (FileNotFoundException | IOException e) {
e.printStackTrace();
} finally {
if (scanner != null) {
scanner.close();
}
}
// this is equivalent, and closes automatically the scanner object since it is inside the try-with-resources
try (Scanner scanner = new Scanner(new File("test.txt"))) {
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
}
Java I/O
Reading text files (File.lines)
// try with resources from Java 7 + Files.lines from Java 8
// Stream implements AutoCloseable and closes the file
Path path = Paths.get("d:", "tmp", "debug.log");
try (Stream<String> stream = Files.lines(path)) {
stream.filter(line -> line.contains("ERROR")).findFirst().ifPresent(System.out::println);
} catch (IOException ioe) {
// handle exception
}
Reading directory entries (File.list) - Only visitrs first level / Files.walk -> visits the whole subtree Files or the deph level set Files.walk(path,2);
try (Stream<String> stream = Files.list(path)) {
stream.filter(path -> path.toFile().isDirectory()).forEach(System.out::println);
} catch (IOException ioe) {
// handle exception
}
Collections
Reading text files (File.lines)
Collection<String> list = new ArrayList<>(strings);
boolean b = list.removeIf(s -> s.length() > 4); // returns if the list was modified
list.replaceAll(String::toUpperCase); // modifies the list
list.sort(Comparator.naturalOrder()); // order the list using the comparator
list.stream().collect(Collectors.joining(", "));
// Comparator:
Comparator<Person> compareLastName = Comparator.comparing(Person::getLastName).thenComparing(Person::getFirstName);
compareLastName.reversed(); // compares the reverse
Comparator c = Comparator.naturalOrder();
Comparator c = Comparator.nullsFirst(Comparator.naturalOrder()); // or nullsLast
// Utility methods:
long max = Long.max(1L,2L);
BinaryOperator sum = (l1, l2) -> l1 + l2; // = Long::sum;
int hash = new Long.hashCode(3141592653589793238L); // -1985256439
Map<String, Person> map = ...
Person p = map.getOrDefault(key,defaultPerson); // prevents a null
map.putIfAbsent(key,person);
map.replace(key,person);
Java 9
Collection Factory Methods
Initialization methods for collections that creates Inmutable Collection (In Java 11 the name is changed to Unmodifiable Collection because although the collection itself is not modifiable, the elements contained are not inmutable)
List<Integer> list = List.of(1,2,3)
Set<Integer> set = Set.of(1,2,3)
Map<String,Integer> map = Map.of("Key1",1,"Key2",2) // up to 10 key/value -> remember iteration order not guaranteed
Map.ofEntries(Map.entry("key","value"), Map.entry("key","value") // no limit
list.add(4) -> UnsupportedOperationException
list.getClass() -> InmutableCollection$ListN
List.of(1).getClass() -> InmutableCollection$List1 (optimized implementation for single/dual collections)
Stream API
New methods added in the Stream API.
// For ordered streams
takeWhile(Predicate <? super T> p) -> list.takeWhile(a -> a < 4) [1,4,5] -> [1]
dropWhile(Predicate <? super T> p) -> drop.takeWhile(a -> a < 4) [1,4,5] -> [4,5]
// Print code inside comments -> File.lines -> Stream
Files.lines(Path.get("file.html")).dropWhile(l -> !l.contains("<<<<<<<<<")).skip(1).takeWhile(l -> !l.contains(">>>>>")).forEach(...);
// ofNullable(T t)
Stream.ofNullable(null).count() -> 0 // Stream.ofNullable(getBook()).count() -> 1
Stream.ofNullable(getPossibleNull()).flatmap(b -> b.authors.stream()).forEach(System.out::println);
iterate(T seed, Predicate <? super T> hasNext, UnaryOperator next)
// Stream collectors: collect -> Transform Stream into a Collection
Stream.of(1,2,3).map(n -> n + 1).collect(Collectors.toList()); -> [2,3,4]
Stream.of(1,2,3,4).collect(groupingBy(i -> i % 2, toList())); -> {0=[2], 1=[1,3,3]}
// Optional: Holds single value or null, new methods added
ifPresentOrElse(Consumer action, Runnable emptyAction)
or(Supplier<Optional<T>> supplier)
Stream<T> stream()
Stream<Optional<Integer>> optionals = Stream.of(Optional.of(1), Optional.empty(), Optional.of(2));
Stream ints = optionals.flatMap(Optional::steam);
ints.forEach(System.out::print); -> 1 2
myObject.ifPresentOrElse(System.out::println, () -> System.out.println("Object null"));
Private interface methods
New methods added in the Stream API.
public interface Price {
double getPrice();
default int getDefaultPrice1() {
return 1 + defaultPriceInternal();
}
default int getDefaultPrice2() {
return 2 + defaultPriceInternal();
}
private int defaultPriceInternal() { // vision only in the interface
return 1;
}
}
Http Client
New HTTP API.
- Supports HTTP2/Websockets
- Reactive Streams Integration
- 1. HttpClient.Builder
- 2. HttpRequest.Builder
- 3. HttpResponse.Builder
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder(URI.create("https://...")).GET().build();
//Blocking synchronous
HttpResponse response = client.send(request,HttpResponse.BodyHandler.asString());
// Non-blocking asyncrhonous
CompletableFuture<HttpResponse> response = client.sendAsync(request,HttpResponse.BodyHandler.asString());
response.thenAccept(r-> {System.out.println(r.body())});
response.join(); // waits for the completable future to be completed
Reactive Streams
Flow API - To be usable for other thrid party libraries (Akka Streams, RxJava2, Spring 5)
Java 11
Scripting
Java 11 can be executed as script directly in the OS.
// With the shebang we can execute scripts directly in command line:
./listfiles
#!/usr/bin/java --source 11
import java.nio.file.*;
public class ListFiles {
public static void main(String[] args) throws Exception {
Files.walk(Paths.get(args[0])).forEach(System.out::println);
}
}
Unmodifiable Collections
Java 11 changes from inmutable to unmodifiable because although the object itself is inmutable, the objects contained by the unmodifiable collection can be mutable (e.g. UnmodifiableList of Person -mutable-). https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/List.html#unmodifiable
Annotations in "var"
Use of var to allow annotations in lambdas (@Nonnull var a, @Nullable var b) -> a.concat(b)
New methods
// isBlank, repeat, strip, trim
"".isBlank() -> true
"na".repeat(3) -> "nanana"
"\n\t text \u2005".strip() -> "text"
"\n\t text \u2005".trim() -> "text \u2005"
"1\n2\n".lines().forEach(System.println::out) ->
1
2
strings.stream().filter(Predicate.not(String::isBlank))).forEach(...)
Optional.isEmpty() -> true if empty object
Java 12
String and compact numbers
// New String methods
"hello".indent(2) -> " hello\n"
"1.aaa\n2.bbb"indent(5).lines().forEach(System.out::println) ->
1.aaa
2.bbb
String::transform
StringUtils.words(StringUtils.clean(text));
// Compact numbers
// 1K -> 1000
// 1M -> 1000000
Teeing Collector. Collectors.teeing(c1,c2,combiner)
Stream.of(10,20,30).collect(Collectors.teeing(
Collectors.summingInt(Integer::valueOf),
Collectors.counting(),
(sum, count) -> sum / count)); -> 10+20+30 / 3
Switch expressions
String monthName = switch(monthNumber) {
case 1 -> monthName = "January";
case 2,3 -> {String month = "February"; break monnth;}
default -> monthName = "Unknown";
};
Switch Expressions
Switch expressions
String monthName = switch(monthNumber) {
case 1 -> monthName = "January";
case 2,3 -> {String month = "February"; break monnth;}
default -> monthName = "Unknown";
};
Micro-benchmarking suite with JMH
@Benchmarkmode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
@Fork(1) //number of JVM forked
// @Setup @Teardown
public class mybenchmark {
@Benchmark
public void testMethod() {}
}
Java 13
Text Blocks (Preview)
Multiline text definition similar to Python using """..."""
String html = "\n" +
" \n" +
" <p class=\"text\">Hello, Escapes</p>\n" +
" \n" +
"\n";
//In Java 13 also can be defined as:
String html = """
Hello, Text Blocks
""";
Auxiliary methods in Strings (preview in Java 13 & 14 and promoted to full feature in Java 15)
String::stripIndent
String::translateEscapes
String::formatted -> In java 13 now is possible "Value: %s".formatted(value) that is equivalent to String.format("Value: %s", value)
Switch Expressions (Preview)
Java 12 introduced returning a value in the Switch expressions & Java 13 extends this feature allowing blocks and using yield to return the values
// Java 12
String numericString = switch(integer) {
case 0 -> "zero";
case 1, 3 -> "odd";
default -> "N/A";
};
//In Java 13
String numericString = switch(integer) {
case 0 -> {
String value = calculateZero();
yield value;
} ;
case 1, 3 -> {
String value = calculateOdd();
yield value;
};
default -> {
String value = calculateDefault();
yield value;
};
};
Java 14
Records (Preview)
This is a very powerful feature added to the language. It is a simplified way to define a classical POJO. It adds:Private fields for each variable defined, a constructor, getters and setters, equals and hashCode & toString methods
Constraints: Records can't extend any other class. Can't declare more fields than the set in the description (any other has to be declared as static). Records are final and can't be abstract. Componets of a Record are final and then inmutable
For reflection the following methods are added to class Class: RecordComponent[], getRecordComponents(), isRecord()
// Before
public class PhoneNumber {
private Integer lineNumber;
private Integer prefix;
private Integer areaCode;
public Integer getLineNumber() {
return lineNumber;
}
public Integer getPrefix() {
return prefix;
}
public Integer getAreaCode() {
return areaCode;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null)
return false;
if (getClass() != o.getClass())
return false;
PhoneNumber that = (PhoneNumber) o;
return super.equals(that)
&& Objects.equals(this.lineNumber, that.lineNumber)
&& Objects.equals(this.prefix, that.prefix)
&& Objects.equals(this.areaCode, that.areaCode);
}
}
// Now AWESOME simplification!
public record PhoneNumber(Integer lineNumber, Integer prefix, Integer areaCode) {
}
Pattern Matching for instanceof (Preview)
Now an object can be defined in the same sentence than instance of without casting it
// Before the object had to be cast
if (obj instanceof String) {
String s = (String) obj;
// use s
}
// Now the object can be defined in the same sentence
if (obj instanceof String s) {
// use s
}
Helpful NullPointerExceptions
Before Java indicates just the line where the NullPointerExceptions was produced. Now the exceptions indicates also which part of the line produced the NullPointerException
a.b.c.i = 99;
a[i][j][k] = 99;
a.i = b.j;
x().y().i = 99;
// Output
Exception in thread "main" java.lang.NullPointerException:
Cannot assign field "i" because "a" is null
at Prog.main(Prog.java:5)
Exception in thread "main" java.lang.NullPointerException:
Cannot read field "c" because "a.b" is null
at Prog.main(Prog.java:5)
Exception in thread "main" java.lang.NullPointerException:
Cannot load from object array because "a[i][j]" is null
at Prog.main(Prog.java:5)
Exception in thread "main" java.lang.NullPointerException:
Cannot read field "j" because "b" is null
at Prog.main(Prog.java:5)
Java 15
Sealed Classes (Preview)
Restricts a class to be inherited only by certain Classes
// Shape can be inherited only by Circle and Rectangle
public abstract sealed class Shape permits Circle, Rectangle {
...
}
public Circle extends Shape {
...
}
public Rectangle extends Shape {
...
}
// Error can't be inherited because is sealed to Circle and Rectangle
public Polygon extends Shape {
...
}