Mohamed Taman
Chief Solutions Architect
Java Champion | CEO @SiriusXI | Oracle ACE Alumni | Jakarta EE Ambassador | Author | Trainer | Speaker.
Is Java still relevant and worth learning in 2024?
We will look at Java’s evolution and impact on software development to show why it matters more than ever.
Java is more than a language.
@_tamanm
|
|
|
@_tamanm
|
|
|
Or by googling 🕵 me
"Mohamed Taman"
@_tamanm
|
|
|
Java History.
What is an
LTS version?
Checkout Java
11 - 17 opinionated features.
Resources
Checkout Java
18 - 21 opinionated features.
@_tamanm
|
|
|
@_tamanm
|
|
|
@_tamanm
|
|
|
@_tamanm
|
|
|
@_tamanm
|
|
|
Marketing concept
Related to a specific vendor JDK
Changed from 3ys to every 2ys (now)
Usually 6-8 years
Bug fixes covered for long time
Performance improvement
New features
Vulnerabilities fixed
Commercial support
Each vendor decides how and when to ship
Azul has Medium Term Support (MTS)
@_tamanm
|
|
|
@_tamanm
|
|
|
Local Variable Syntax for Lambda Parameters (JEP 323)
Java 11 is the first (LTS) release with the new release cadence.
@_tamanm
|
|
|
(String a, String b) -> a.concat(b)
( a, b) -> a.concat(b)
(var a, var b) -> a.concat(b)
(@Nullable var a, @Nonnull var b) -> a.concat(b)
Because annotations needs a type
@_tamanm
|
|
|
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello Java 🧡 Developers");
}
}
Running .java
old days:
Should be compiled to have .class
Then run .class
with java
mohamed_taman$ javac HelloWorld.java
mohamed_taman$ java HelloWorld
Hello Java 🧡 Developers
And now RUN .java
WITH Java
like this:
mohamed_taman$ java HelloWorld.java
Hello Java 🧡 Developers
@_tamanm
|
|
|
#!/usr/bin/java --source 11
import java.nio.file.*;
public class DirectoryLister {
public static void main(String[] args) throws Exception {
var dirName = ".";
if ( args == null || args.length < 1 ){
System.err.println("Listing the current directory...");
} else {
dirName = args[0];
}
Files.walk(Paths.get(dirName)).forEach(out::println);
}}
SHELL SCRIPTING WITH JAVA - SHEBANG FILES:
mohamed_taman:code$ chmod +x dirlist
Run it as the following:
mohamed_taman:code$ ./dirlist
Listing the current directory...
.
./PalindromeChecker.java
./greater
./UsersHttpClient.java
./HelloWorld.java
./Greater.java
./dirlist
Save this code in a file named dirlist
without any extension and then mark it as executable:
Continue
@_tamanm
|
|
|
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
.uri(create("https://postman-echo.com/get/"))
.build();
client.sendAsync(request, ofString())
.thenApply(HttpResponse::body)
.thenAccept(out::println)
.join();
HTTP Client Module:
public final class EchoListener implements WebSocket.Listener {
@Override
public void onOpen(WebSocket webSocket) {
logger.info("CONNECTED");
....
}
....
}
Use an HttpClient to create a WebSocket
WebSocket webSocket = httpClient.newWebSocketBuilder()
.buildAsync(URI.create("ws://demos.kaazing.com/echo"), new EchoListener(executor)).join();
webSocket
.sendClose(WebSocket.NORMAL_CLOSURE, "ok")
.thenRun(() -> logger.info("Sent close"))
.join();
HTTP Websocket Module:
@_tamanm
|
|
|
Switch Expressions - 1st Preview (JEP 325)
The first Java release that included a preview feature (JEP 12). Such features are only available if the compiler and JVM is launched with the --enable-preview option.
@_tamanm
|
|
|
@_tamanm
|
|
|
@_tamanm
|
|
|
Here’s the old switch statement:
jshell> String getRomanNumber(int value){
String romanValue = "";
switch(value){
case 0: romanValue = "nulla";
break;
case 1: romanValue = "I";
break;
case 2: romanValue = "II";
break;
case 3: romanValue = "III";
break;
case 4: romanValue = "IV";
break;
case 5: romanValue = "V";
break;
case 6: romanValue = "VI";
break;
case 7: romanValue = "VII";
break;
case 8: romanValue = "VIII";
break;
case 9: romanValue = "IX";
break;
case 10: romanValue = "X";
break;
default: System.out.printf("Out of range value: %d %n", value);
romanValue = "N/A";
break;
}
return romanValue;
}
@_tamanm
|
|
|
Here’s the new switch statement
Continue
String getRomanNumber(int value){
String romanValue = "";
switch(value){
case 0 -> romanValue = "nulla";
case 1 -> romanValue = "I";
case 2 -> romanValue = "II";
case 3 -> romanValue = "III";
case 4 -> romanValue = "IV";
case 5 -> romanValue = "V";
case 6 -> romanValue = "VI";
case 7 -> romanValue = "VII";
case 8 -> romanValue = "VIII";
case 9 -> romanValue = "IX";
case 10 -> romanValue = "X";
default -> {
System.out.printf("Out of range value: %d %n", value);
romanValue = "N/A";
}
}
return romanValue;
}
New Switch expression
String getRomanNumber(int value){
return switch(value){
case 0 -> "nulla";
case 1 -> "I";
case 2 -> "II";
case 3 -> "III";
case 4 -> "IV";
case 5 -> "V";
case 6 -> "VI";
case 7 -> "VII";
case 8 -> "VIII";
case 9 -> "IX";
case 10 -> "X";
default -> throw new IllegalStateException("Out of range value: " + value);
};
}
@_tamanm
|
|
|
Multiple comma-separated labels in a single switch case
Continue
jshell> String getOddOrEvenNumber(int value){
String kind = "N/A";
switch(value){
case 0: kind = "Zero"; break;
case 1:
case 3:
case 5:
case 7:
case 9: kind = "Odd"; break;
case 2:
case 4:
case 6:
case 8:
case 10: kind = "Even"; break;
default: System.out.printf("Out of range: %d %n", value);
}
return kind;
}
The new way
jshell> var kind = switch(value){
...> case 0 -> "Zero";
...> case 1, 3, 5, 7, 9 -> "Odd";
...> case 2, 4, 6, 8, 10 -> "Even";
...> default -> throw new Exception("Out of range: " + value);
...> };
jshell> String kind = switch(value){
...> case 0: yield "Zero";
...> case 1, 3, 5, 7, 9: yield "Odd";
...> case 2, 4, 6, 8, 10: yield "Even";
...> default: throw new Exception("Out of range: " + value);
...> };
@_tamanm
|
|
|
Before Java 14, the JVM will print out the method, filename, and line number that caused the NPE:
a.i = 99;
Exception in thread "main" java.lang.NullPointerException at Prog.main(Prog.java:5)
In Java 14 - Suppose an NPE occurs in this code:
a.b.c.i = 99;
Exception in thread "main" java.lang.NullPointerException:
Cannot read field 'c' because 'a.b' is null.
at Prog.main(Prog.java:5)
-XX:+ShowCodeDetailsInExceptionMessages
To enable this feature use the following JVM flag:
Which is on by default in Java 15.
@_tamanm
|
|
|
@_tamanm
|
|
|
Before Java 15, for multi-line text we construnct it like this:
var html = "\n" +
"<html>\n" +
" <body>\n" +
" <p>Hello, world</p>\n" +
" </body>\n" +
"</html>\n";
In Java 15:
var html = """
<html>
<body>
<p>Hello, world</p>
</body>
</html>
""";
@_tamanm
|
|
|
The OpenJDK source tree has moved from Mercurial to Git and is now hosted on GitHub.
@_tamanm
|
|
|
class Point {
private final int x;
private final int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
int x() { return x; }
int y() { return y; }
public boolean equals(Object o) {
if (!(o instanceof Point)) return false;
Point other = (Point) o;
return other.x == x && other.y == y;
}
public int hashCode() {
return Objects.hash(x, y);
}
public String toString() {
return String.format("Point[x=%d, y=%d]", x, y);
}
}
@_tamanm
|
|
|
Continue
record Point(int x, int y) { }
By reducing the ceremony of Java by cutting boilerplate code. So the Point class could be re-written as:
Constructors for record classes
record Point(int x, int y){
Point(int x, int y){
if(x == 0 && y == 0)
throw new IllegalArgumentException(String.format("(%d,%d)", x, y));
this.x = x;
this.y = y;
}
}
record Point(int x, int y){
Point{
if(x == 0 && y == 0)
throw new IllegalArgumentException (String.format("(%d,%d)", x, y));
}
}
List<Merchant> findTopMerchants(List<Merchant> merchants, int month) {
// Local record
record MerchantSales(Merchant merchant, double sales) {}
return merchants.stream()
.map(merchant -> new MerchantSales(merchant, computeSales(merchant, month)))
.sorted((m1, m2) -> Double.compare(m2.sales(), m1.sales()))
.map(MerchantSales::merchant)
.collect(toList());
}
Local Record Classes
@_tamanm
|
|
|
public boolean equals(Object o) {
return (o instanceof CaseInsensitiveString) &&
((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
}
public boolean equals(Object o) {
return (o instanceof CaseInsensitiveString cis) &&
cis.s.equalsIgnoreCase(s);
}
public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
Point other = (Point) o;
return x == other.x
&& y == other.y;
}
public boolean equals(Object o) {
return (o instanceof Point other)
&& x == other.x
&& y == other.y;
}
//More complex examples
if (obj instanceof String str && str.length() > 5)
{ .. str.contains(..) .. }
@_tamanm
|
|
|
Java 17 is the latest long term support (LTS) release after Java 11.
@_tamanm
|
|
|
package com.example.geometry;
public abstract sealed class Shape
permits Circle, Rectangle, Square, WeirdShape { ... }
public final class Circle extends Shape { ... }
public sealed class Rectangle extends Shape
permits TransparentRectangle, FilledRectangle { ... }
public final class TransparentRectangle extends Rectangle { ... }
public final class FilledRectangle extends Rectangle { ... }
public final class Square extends Shape { ... }
public non-sealed class WeirdShape extends Shape { ... }
A class is sealed by applying the sealed modifier to its declaration. Then, after any extends and implements clauses, the permits clause specifies the classes permitted to extend the sealed class.
@_tamanm
|
|
|
Here is another classic example of a class hierarchy with a known set of subclasses: modeling mathematical expressions.
Sealed classes work well with record classes. Record classes are implicitly final, so a sealed hierarchy of record classes is slightly more concise than the example above:
package com.example.expression;
public sealed interface Expr
permits ConstantExpr, PlusExpr, TimesExpr, NegExpr { ... }
public final class ConstantExpr implements Expr { ... }
public final class PlusExpr implements Expr { ... }
public final class TimesExpr implements Expr { ... }
public final class NegExpr implements Expr { ... }
package com.example.expression;
public sealed interface Expr
permits ConstantExpr, PlusExpr, TimesExpr, NegExpr { ... }
public record ConstantExpr(int i) implements Expr { ... }
public record PlusExpr(Expr a, Expr b) implements Expr { ... }
public record TimesExpr(Expr a, Expr b) implements Expr { ... }
public record NegExpr(Expr e) implements Expr { ... }
Sealing and record classes
The combination of sealed classes and record classes is sometimes referred to as algebraic data types: Record classes allow us to express product types, and sealed classes allow us to express sum types.
Continue
@_tamanm
|
|
|
Supports native packaging formats to give end-users a natural installation experience. These formats include:
- msi and exe on Windows,
- pkg, and dmg on macOS, and
- deb and rpm on Linux.
Suppose you have an application composed of JAR files, all in a directory named lib, and that lib/main.jar contains the main class. Then the command:
[mtaman]:~ jpackage --name myapp --input lib --main-jar main.jar
mohamed_taman -- -bash
--main-class myapp.Main
--type pkg
@_tamanm
|
|
|
@_tamanm
|
|
|
Write this on macOS or Linux:
@_tamanm
|
|
|
private static void writeToFile() {
try (FileWriter writer = new FileWriter("Jep400-example.txt");
BufferedWriter bufferedWriter = new BufferedWriter(writer)) {
bufferedWriter.write("مرحبا بكم في جافا جيب ٤٠٠");
}
catch (IOException e) {
System.err.println(e);
}
}
private static void readFromFile() {
try (FileReader reader = new FileReader("Jep400-example.txt");
BufferedReader bufferedReader = new BufferedReader(reader)) {
String line = bufferedReader.readLine();
System.out.println(line);
}
catch (IOException e) {
System.err.println(e);
}
}
Read it on Windows OS
You will get this:
٠رØبا ب٠٠٠٠جا٠ا ج٠ب Ù¤Ù Ù
@_tamanm
|
|
|
[mtaman]:~ jwebserver
Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::".
Serving /Users/mohamed_taman/Hidden Gems in Java 18/code and subdirectories on 127.0.0.1 port 8000
URL http://127.0.0.1:8000/
jwebserver
command. By default, this command listens to localhost on port 8000. The server also provides a file browser to the current directory.@_tamanm
|
|
|
/**
* How to read a text file with Java 8:
* {@snippet :
* try (var reader = Files.newBufferedReader(path)) {
* String line = reader.readLine();
* System.out.println(line);
* }
* }
*/
Declare code fragments to appear in the generated documentation.
@_tamanm
|
|
|
@_tamanm
|
|
|
@_tamanm
|
|
|
This is the LTS Release after Java 17 after 2 years.
Collections in Java have always lacked an ordered approach. A new consistent methods across collections has been added as an efficient way of getting the elements in reverse order.
@_tamanm
|
|
|
Collection new Methods:
SortedMap / LinkedHashMap new Methods:
SortedSet / LinkedHastSet new Method:
addFirst, addLast, getFirst, getLast, removeFirst, removeLast
reversed
reversed, sequecedKeySet, sequencedValues, sequencedEntrySet, putFirst, putLast, firstEntry, lastEntry, pollFirstEntry, pollLastEntry
Example before:
After Java 21:
var first = list.iterator().next();
var last = list.get(arrayList.size() - 1);
var first = list.getFirst();
var last = list.getLast();
@_tamanm
|
|
|
Consider the following record:
public record Position(int x, int y) {}
Case 1: Record Patterns and Pattern Matching for instanceof
// Before Java 21
public void print(Object o) {
if (o instanceof Position p) {
System.out.printf("o is a position: %d/%d%n", p.x(), p.y());
} else if (object instanceof String s) {
System.out.printf("o is a string: %s%n", s);
} else {
System.out.printf("o is something else: %s%n", o);
}
}
// In Java 21
public void print(Object o) {
if (o instanceof Position(int x, int y)) {
System.out.printf("o is a position: %d/%d%n", x, y);
} else if (o instanceof String s) {
System.out.printf("o is a string: %s%n", s);
} else {
System.out.printf("o is something else: %s%n", o);
}
}
@_tamanm
|
|
|
Case 2: Record Patterns and Pattern Matching for switch
// Before Java 21
public void print(Object o) {
switch (o) {
case Position p -> System.out.printf("o is a position: %d/%d%n", p.x(), p.y());
case String s -> System.out.printf("o is a string: %s%n", s);
default -> System.out.printf("o is something else: %s%n", o);
}
}
// In Java 21
public void print(Object o) {
switch (o) {
case Position(int x, int y) -> System.out.printf("o is a position: %d/%d%n", x, y);
case String s -> System.out.printf("o is a string: %s%n", s);
default -> System.out.printf("o is something else: %s%n", o);
}
}
Continue
@_tamanm
|
|
|
Case 3: Nested Record Patterns
// Before Java 21
public void print(Object o) {
switch (o) {
case Path p ->
System.out.printf("o is a path: %d/%d -> %d/%d%n",
p.from().x(), p.from().y(), p.to().x(), p.to().y());
// other cases
}
}
// In Java 21
public void print(Object o) {
switch (o) {
case Path(Position from, Position to) ->
System.out.printf("o is a path: %d/%d -> %d/%d%n",
from.x(), from.y(), to.x(), to.y());
// other cases
}
}
Continue
Consider the following record:
public record Path(Position from, Position to) {}
@_tamanm
|
|
|
Case 3: Nested Record Patterns
// In Java 21
public void print(Object o) {
switch (o) {
case Path(Position(int x1, int y1), Position(int x2, int y2)) ->
System.out.printf("o is a path: %d/%d -> %d/%d%n", x1, y1, x2, y2);
// other cases
}
}
Continue
Secondly, we can also use a nested record pattern as follows:
@_tamanm
|
|
|
Case 3: Nested Record Patterns
public sealed interface Position permits Position2D, Position3D {}
public record Position2D(int x, int y) implements Position {}
public record Position3D(int x, int y, int z) implements Position {}
public record Path<P extends Position>(P from, P to) {}
Continue
The Real Power of Record Patterns
public void print(Object o) {
switch (o) {
case Path(Position2D from, Position2D to) ->
System.out.printf("o is a 2D path: %d/%d -> %d/%d%n",
from.x(), from.y(), to.x(), to.y());
case Path(Position3D from, Position3D to) ->
System.out.printf("o is a 3D path: %d/%d/%d -> %d/%d/%d%n",
from.x(), from.y(), from.z(), to.x(), to.y(), to.z());
// other cases
}
}
@_tamanm
|
|
|
Case 3: Nested Record Patterns
Continue
Without record patterns, we would have to write the following code:
public void print(Object o) {
switch (o) {
case Path p when p.from() instanceof Position2D from
&& p.to() instanceof Position2D to ->
System.out.printf("o is a 2D path: %d/%d -> %d/%d%n",
from.x(), from.y(), to.x(), to.y());
case Path p when p.from() instanceof Position3D from
&& p.to() instanceof Position3D to ->
System.out.printf("o is a 3D path: %d/%d/%d -> %d/%d/%d%n",
from.x(), from.y(), from.z(), to.x(), to.y(), to.z());
// other cases
}
}
@_tamanm
|
|
|
With pattern matching for switch, we could write the following code:
// Before Java 21
Object obj = getObject();
if (obj instanceof String s && s.length() > 5) System.out.println(s.toUpperCase());
else if (obj instanceof String s) System.out.println(s.toLowerCase());
else if (obj instanceof Integer i) System.out.println(i * i);
else if (obj instanceof Position(int x, int y)) System.out.println(x + "/" + y);
// In Java 21
Object obj = getObject();
switch (obj) {
case String s when s.length() > 5 -> System.out.println(s.toUpperCase());
case String s -> System.out.println(s.toLowerCase());
case Integer i -> System.out.println(i * i);
case Position(int x, int y) -> System.out.println(x + "/" + y);
default -> {}
}
@_tamanm
|
|
|
// Before
int a = ...;
int b = ...;
String concatenated = a + " times " + b + " = " + a * b;
String format = String.format("%d times %d = %d", a, b, a * b);
String formatted = "%d times %d = %d".formatted(a, b, a * b);
// In Java 21
int a = ...;
int b = ...;
String interpolated = STR."\{a} times \{b} = \{a * b}";
String interpolated = STR."\{a} times \{b} = \{Math.multiplyExact(a, b)}";
// In Java 21
String dateMessage = STR."Today's date: \{
LocalDate.now().format(
// We could also use DateTimeFormatter.ISO_DATE
DateTimeFormatter.ofPattern("yyyy-MM-dd")
)}";
// In Java 21
int httpStatus = ...;
String errorMessage = ...;
String json = STR."""
{
"httpStatus": \{httpStatus},
"errorMessage": "\{errorMessage}"
}""";
@_tamanm
|
|
|
// Example 1: Exceptions – here, we don’t use e:
try {
int number = Integer.parseInt(string);
} catch (NumberFormatException e) {
System.err.println("Not a number");
}
// Example 2: Map.computeIfAbsent() – this time, we don’t use k:
map.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
// In Java 21
// Here is the exception example with an unnamed variable:
try {
int number = Integer.parseInt(string);
} catch (NumberFormatException _) {
System.err.println("Not a number");
}
// And Map.computeIfAbsent()
map.computeIfAbsent(key, _ -> new ArrayList<>()).add(value);
Unnamed Variable
@_tamanm
|
|
|
if (object instanceof Position(int x, int y)) {
System.out.println("object is a position, x = " + x);
}
Unnamed Pattern Variable
Continue
if (object instanceof Position(int x, int _)) {
System.out.println("object is a position, x = " + x);
}
if (object instanceof Position(int x, _)) {
System.out.println("object is a position, x = " + x);
}
Unnamed Pattern
@_tamanm
|
|
|
Continue
switch (obj) {
case Byte b -> System.out.println("Integer number");
case Short s -> System.out.println("Integer number");
case Integer i -> System.out.println("Integer number");
case Long l -> System.out.println("Integer number");
case Float f -> System.out.println("Floating point number");
case Double d -> System.out.println("Floating point number");
default -> System.out.println("Not a number");
}
switch (obj) {
case Byte _ -> System.out.println("Integer number");
case Short _ -> System.out.println("Integer number");
case Integer _ -> System.out.println("Integer number");
case Long _ -> System.out.println("Integer number");
case Float _ -> System.out.println("Floating point number");
case Double _ -> System.out.println("Floating point number");
default -> System.out.println("Not a number");
}
switch (obj) {
case Byte _, Short _, Integer _, Long _ -> System.out.println("Integer number");
case Float _, Double _ -> System.out.println("Floating point number");
default -> System.out.println("Not a number");
}
@_tamanm
|
|
|
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello world!");
}
}
void main() {
System.out.println("Hello world!");
}
final String HELLO_TEMPLATE = "Hello %s!";
void main() {
System.out.println(hello("world"));
}
String hello(String name) {
return HELLO_TEMPLATE.formatted(name);
}
The "Unnamed Class"
@_tamanm
|
|
|
@_tamanm
|
|
|