java from 8 to 16
Java 16 is released and it’s time to leave our good ol’ Java 8 and begin to use the new language features Java brings with its versions.
In this post, I will only cover some of the newest Java features.
Java 9
Private methods in interfaces
interface PetStore {
// You can now define private methods in interfaces. It's quite useful if you
// want to factorize some code from default methods
private long getNumberAvailablePets(List<Pet> pets, Predicate<Pet> predicate) {
return pets.stream()
.filter(Pet::isAvailable)
.filter(predicate)
.count();
}
default long getNumberAvailableCats(List<Pet> pets) {
return getNumberAvailablePets(pets, pet -> Pet.Type.CAT == pet.getType());
}
default long getNumberAvailableDogs(List<Pet> pets) {
return getNumberAvailablePets(pets, pet -> Pet.Type.DOG == pet.getType());
}
}
New Optional APIs
class NewOptionalAPIs {
private static final List<Cat> CATS = Arrays.asList(
new Cat("Tadar sauce"),
new Cat("Nyan cat"),
new Cat("Grumpy cat")
);
private static Optional<Cat> findByName(String name) {
return CATS.stream().filter(c -> name.equals(c.getName())).findAny();
}
public static void main(String[] args) {
/*
* Optional.ifPresentOrElse
*/
Optional<Cat> cat = findByName("Grumpy cat");
// Before
if (cat.isPresent()) {
System.out.println(cat.get().getName());
} else {
System.out.println("No cat found");
}
// After
cat.ifPresentOrElse(c -> System.out.println(c.getName()), () -> System.out.println("No cat found"));
findByName("foobar").ifPresentOrElse(c -> System.out.println(c.getName()), () -> System.out.println("No cat found"));
/*
* Optional.isEmpty
*/
// Before
if (cat.isEmpty()) {
System.out.println("No cat found");
} else {
System.out.println(cat.get().getName());
}
// After
// Optional.or
Optional<Cat> c = findByName("Garfield")
.or(() -> findByName("Tata"))
.or(() -> findByName("Toto"));
System.out.println(c.orElse(new Cat("titi")));
// Optional.stream
String name = "Tom";
Cat[] cats = Optional.of(new Cat(name)).stream().toArray(Cat[]::new);
System.out.println(cats);
}
}
New Future APIs
// Annotation @Deprecated now has the attributes "forRemoval" and "since", useful to document Java classes
@Deprecated(forRemoval = true, since = "0.1.0")
public class NewCompletableFutureAPIs {
public static void main(String[] args) throws InterruptedException, ExecutionException {
CompletableFuture<String> future = new CompletableFuture<>();
// Throw a TimeoutException if the task is not finished
future.orTimeout(2, TimeUnit.SECONDS);
// Completes with the given value upon timeout
// future.completeOnTimeout("foobar", 2, TimeUnit.SECONDS);
Runnable runnable = () -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
future.complete("finished");
};
runnable.run();
System.out.println(future.get());
}
}
New Collection APIs
public static void main(String[] args) {
/*
* Set.of / List.of
*/
// Before
Set<String> catNames = new HashSet<>();
catNames.add("Tadar sauce");
catNames.add("Grumpy cat");
catNames.add("Nyan cat");
System.out.println(Collections.unmodifiableSet(catNames));
// After
System.out.println(Set.of("Tadar sauce", "Grumpy cat", "Nyan cat"));
/*
* Map.of
*/
// Before
Map<String, Integer> catLikes = new HashMap<>();
catLikes.put("Tadar sauce", 10);
catLikes.put("Grumpy cat", 1);
catLikes.put("Nyan cat", 1000);
System.out.println(Collections.unmodifiableMap(catLikes));
// After
System.out.println(Map.of("Tadar sauce", 10, "Grumpy cat", 1, "Nyan cat", 1000));
}
JShell
$ ${JAVA_HOME}/bin/jshell
| Welcome to JShell -- Version 14
| For an introduction type: /help intro
jshell> String greeting = "Hello, world";
greeting ==> "Hello, world"
jshell> System.out.println(greeting);
Hello, world
jshell> /vars
| String greeting = "Hello, world"
jshell> 2+2*4
$3 ==> 10
Jigsaw
Java 9 also brings the Project Jigsaw whose goal is to have a standard module system for the Java Platform.
This is a large topic that I will not cover here.
Java 10
Inference type
public class InferenceType {
// Does not compile
// private final var foobar = new String();
public static void main(String[] args) {
var catName = "Grumpy cat"; // String
var nbCat = Integer.valueOf(1); // int
var isCute = Boolean.TRUE; // Boolean
System.out.println("There are " + nbCat + " " + catName);
for (var i = 0; i < 10; i++) {
System.out.println(i);
}
var catNames = new ArrayList<String>(); // Type resolved
// Does not compile
// catNames = new LinkedList<String>();
// Does not compile
// var foo;
// var ints = {0, 1};
// var appendSpace = (String a) -> a + " ";
}
// Does not compile
// private var doSomething() {
// return "foobar";
// }
// Does not compile
// private String foobar(var s) {
// return "";
// }
}
New Optional APIs
public static void main(String[] args) {
Optional.ofNullable(null).orElseThrow();
}
New Collection APIs
public static void main(String[] args) {
// List.copyOf / Set.copyOf
List.copyOf(List.of("foo", "bar"))
.stream()
.collect(
// Collectors.toUnmodifiableList() / Collectors.toUnmodifiableSet
Collectors.toUnmodifiableList()
);
// Map.copyOf
Map.copyOf(Map.of("foo", "bar"));
}
Java 11
Inference type on lambda
public class NewInferenceType {
static IntFunction<Integer> doubleIt1 = x -> x * 2;
// Compiles on java 8
static IntFunction<Integer> doubleIt2 = (@Deprecated int x) -> x * 2;
// Compiles only from java 11
// Useful if we want to add annotations to the parameter
static IntFunction<Integer> doubleIt3 = (@Deprecated var x) -> x * 2;
}
New String APIs
// String.isBlank
System.out.println(" ".isBlank());
System.out.println("".isBlank());
System.out.println("foo".isBlank());
// String.lines
"foo\nbar".lines().forEach(System.out::println);
// String.strip ~= String.trim with "Unicode aware"
System.out.println("foo" + " azert ".strip() + "bar");
System.out.println("foo" + " azert ".stripLeading() + "bar");
System.out.println("foo" + " azert ".stripTrailing() + "bar");
// String.repeat
System.out.println("foobar".repeat(3));
New Files APIs
// Files
var path = Files.writeString(Files.createTempFile("foobar", ".txt"), "foobar");
System.out.println(path);
System.out.println(Files.readString(path));
New HttpClient APIs
// HttpClient
var httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.build();
var request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:8080/actuator/health"))
.GET()
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
System.out.println();
var future = httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenAccept(r -> {
System.out.println(r.statusCode());
System.out.println(r.body());
r.body();
});
future.get();
Java 12
New Switch expressions
// Before
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
System.out.println(6);
break;
case TUESDAY:
System.out.println(7);
break;
case THURSDAY:
case SATURDAY:
System.out.println(8);
break;
case WEDNESDAY:
System.out.println(9);
break;
}
// After
switch (day) {
case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
case TUESDAY -> System.out.println(7);
case THURSDAY, SATURDAY -> System.out.println(8);
case WEDNESDAY -> System.out.println(9);
}
// Can be used as an expression
var d = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
default -> 10;
};
Java 13
Switch expressions enhancements
// yield keyword used when assigning / returning value
// and when we need to have multiple instructions in a block
var d = switch (day) {
case MONDAY, FRIDAY, SUNDAY: yield 6;
case TUESDAY : yield 7;
case THURSDAY, SATURDAY : yield 8;
case WEDNESDAY : yield 9;
default :
// yield useful here compared to the Java 12 arrow "->"
System.out.println("In default case");
yield 10;
};
To have this feature, you need to add a flag to enable it:
# when compiling
javac --release 13 --enable-preview YourJavaClass.java
# when running
java --enable-preview YourJavaClass.java
Text blocks
Finally…
// Before
var html = "<html>\n" +
" <body>\n" +
" <h1>Hello, world!</h1>\n" +
" </body>\n" +
"</html>\n";
// After
var html = """
<html>
<body>
<h1>Hello, world!</h1>
</body>
</html>
""";
Java 14
Pattern matching for instanceof
// Before
if (obj instanceof String) {
String s = (String) obj;
if (!s.isBlank()) {
System.out.println(s.repeat(3));
}
}
// After
if (obj instanceof String s && !s.isBlank()) {
System.out.println(s.repeat(3));
}
Records
// Lightweight class intended to carry data only (same as "data" class in Kotlin)
public record Cat(
// Compact constructor to avoid writing verbose code (e.g. getters / setters)
String name,
String type,
int age
){
// No explicit parameter in constructor
public Cat{
// Can be used to validate inputs
if (age < 0) {
throw new IllegalArgumentException("Don't try to challenge time!");
}
}
// Can create methods, however, records only have immutable data
public void meow() {
System.out.println("Meow! :3");
}
}
Helpful NullPointerExceptions
public record Pet(PetId petId) {
public static void main(String[] args) {
Pet pet = new Pet(null);
// will log the following:
// Exception in thread "main" java.lang.NullPointerException: Cannot invoke "lin.louis.demo.PetId.value()" because the return value of "lin.louis.demo.Pet.petId()" is null
at lin.louis.clean.domain.exception.Pet.main(Pet.java:8)
System.out.println(pet.petId().value());
}
}
JPackage
# create a deb installer file to install the application without needing to install Java
jpackage --input target/ \
--name PetStoreApp \
--main-jar PetStoreApp.jar \
--main-class lin.louis.PetStoreApp \
--type deb \
--java-options '--enable-preview'
Java 15
Sealed classes
// fine-grained control over inheritance
public abstract sealed class Pet permits Cat, Dog {
// ...
}
// extending sealed class must itself be declared as "sealed", "non-sealed" or "final"
public final class Cat extends Pet {}
public non-sealed class Dog extends Pet {}