I'm always excited to take on new projects and collaborate with innovative minds.
contact@niteshsynergy.com
https://www.niteshsynergy.com/
Here’s an overview of the main Java 17 features, their use cases, and examples illustrating their application in complex projects. Java 17 is a long-term support (LTS) release, making it highly relevant for enterprise projects.
1. Sealed Classes
public sealed class Payment permits CreditCardPayment, PaypalPayment {
public abstract void process();
}
public final class CreditCardPayment extends Payment {
@Override
public void process() {
// Implementation
}
}
public final class PaypalPayment extends Payment {
@Override
public void process() {
// Implementation
}
}
sealed
Keyword:Payment
class is declared as sealed
, restricting which classes can extend it.permits
clause specifies the allowed subclasses: CreditCardPayment
and PaypalPayment
.CreditCardPayment
and PaypalPayment
are declared final
, meaning no other class can extend them.
2. Pattern Matching for Switch (Preview)
public static String formatValue(Object value) {
return switch (value) {
case Integer i -> "Integer: " + i;
case String s -> "String: " + s;
case null -> "Null value";
default -> "Unknown type";
};
}
Pattern Matching for switch
was introduced as a preview feature in Java 17 but fully implemented and finalized in Java 19. The transition from a preview feature to a fully implemented feature in Java 19 reflects several improvements and enhancements that were made based on developer feedback and additional language design considerations.
switch
and integrates it more smoothly into the language, making it easier to use and more powerful. Some key improvements include:instanceof
with type patterns, more advanced guards, and type checking.switch
statements.switch
, which were part of other Java features (e.g., sealed classes in Java 17).switch
feature in Java 19 brings language consistency, making it easier for developers to express patterns in their code without needing to mix multiple language features (like instanceof
and switch
).String
and Integer
types within the same case
label, which was not as straightforward in earlier versions.instanceof
checks).--enable-preview
flag.switch
expressions without additional boilerplate code, improving readability and maintainability.switch
in Java 19:In Java 19, the switch
statement has been enhanced to support full pattern matching. Here's an example:
public class PatternMatchingSwitchExample {
public static void main(String[] args) {
Object obj = "Hello";
String result = switch (obj) {
case Integer i -> "Integer: " + i;
case String s -> "String: " + s;
case null -> "Null value"; // Handling null
default -> "Unknown type";
};
System.out.println(result);
}
}
3. Text Blocks
package org.niteshsynergy.java17;
public class Demo03TextBlocks {
public static void main(String[] args) {
// Example 1: SQL Query
String sqlQuery = """
SELECT id, name, email
FROM users
WHERE status = 'active'
ORDER BY created_at DESC;
""";
System.out.println("SQL Query:");
System.out.println(sqlQuery);
// Example 2: JSON String
String jsonExample = """
{
"name": "John Doe",
"age": 30,
"email": "johndoe@example.com",
"roles": ["user", "admin"]
}
""";
System.out.println("JSON Example:");
System.out.println(jsonExample);
// Example 3: HTML Template
String htmlTemplate = """
<!DOCTYPE html>
<html>
<head>
<title>Sample Page</title>
</head>
<body>
<h1>Welcome to Text Blocks</h1>
<p>This is a sample page using text blocks.</p>
</body>
</html>
""";
System.out.println("HTML Template:");
System.out.println(htmlTemplate);
}
}
RandomGenerator
and support for stream-based programming.import java.util.random.RandomGenerator;
import java.util.random.RandomGeneratorFactory;
public class EnhancedRandomExample {
public static void main(String[] args) {
// Using the RandomGenerator interface
RandomGenerator randomGenerator = RandomGenerator.of("Xoshiro256PlusPlus");
// Generate random numbers
System.out.println("Random int: " + randomGenerator.nextInt());
System.out.println("Random double: " + randomGenerator.nextDouble());
System.out.println("Random long: " + randomGenerator.nextLong());
// Generate a stream of random integers
System.out.println("Stream of random integers:");
randomGenerator.ints(5, 1, 100).forEach(System.out::println);
// Using RandomGeneratorFactory to explore available generators
System.out.println("\nAvailable Random Generators:");
RandomGeneratorFactory.all().forEach(factory -> {
System.out.println(factory.name());
});
// Using a jumpable generator
RandomGenerator.JumpableGenerator jumpableGenerator =
RandomGenerator.of("Xoroshiro128Plus") instanceof RandomGenerator.JumpableGenerator
? (RandomGenerator.JumpableGenerator) RandomGenerator.of("Xoroshiro128Plus")
: null;
if (jumpableGenerator != null) {
System.out.println("\nRandom number from JumpableGenerator: " + jumpableGenerator.nextInt());
jumpableGenerator.jump();
System.out.println("Random number after jump: " + jumpableGenerator.nextInt());
}
}
}
RandomGenerator
Interface:Xoshiro256PlusPlus
in this case).nextInt()
, nextDouble()
, or nextLong()
for random numbers.ints()
to generate a stream of random integers.RandomGeneratorFactory.all()
to list all available generators.
try (MemorySegment segment = MemorySegment.allocateNative(1024)) {
MemoryAccess.setIntAtOffset(segment, 0, 42);
int value = MemoryAccess.getIntAtOffset(segment, 0);
System.out.println(value);
}
Foreign Function & Memory API (Preview), introduced in Java 16 as a preview feature and refined in later versions, provides a way for Java programs to interact with native code (such as C or C++ libraries) and manage memory outside of the Java heap. It was designed to replace the old Java Native Interface (JNI) and Direct ByteBuffers with a safer, more efficient, and easier-to-use API.
The goal of this API is to allow Java programs to interface with native code in a way that is more straightforward and avoids the complexities of traditional JNI.
Let's walk through a simple example where Java interacts with native code (using C) and accesses memory outside the heap. We will demonstrate using this preview feature to allocate memory, write to it, and read from it using the Foreign Memory Access API.
Note: Make sure you are using Java 16 or later and enable preview features.
nativeLib.c
void sayHello() {
printf("Hello from native code!\n");
}
int addNumbers(int a, int b) {
return a + b;
}
Compile the C code into a shared library:
gcc -shared -o libnativeLib.dylib -fPIC nativeLib.c
This command compiles the C code into a shared library that can be loaded by the Java program.
Here’s a Java program that interacts with the native code:
ForeignFunctionExample.java
public class ForeignFunctionExample {
// Define the layout for a function (using C signatures)
private static final MemoryLayout ADD_NUMBERS_LAYOUT = MemoryLayout.ofStruct(
ValueLayout.JAVA_INT, // return type: int
ValueLayout.JAVA_INT, // first parameter: int
ValueLayout.JAVA_INT // second parameter: int
);
public static void main(String[] args) {
// Load the shared library (on macOS, assuming .dylib format)
Path nativeLibPath = Path.of("libnativeLib.dylib");
// Access the foreign function from the native library
try (var scope = MemoryScope.openConfined()) {
// Load the native library
var linker = Linker.nativeLinker();
var symbol = linker.lookup(nativeLibPath, "addNumbers");
// Create a function handle for 'addNumbers'
MethodHandle addNumbersHandle = MethodHandles.lookup()
.findStatic(symbol, "addNumbers", MethodType.methodType(int.class, int.class, int.class));
// Call the native function
try {
int result = (int) addNumbersHandle.invoke(5, 10);
System.out.println("Sum from native code: " + result);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
} catch (Throwable e) {
e.printStackTrace();
}
}
}
Linker.nativeLinker()
method is used to load the native shared library and access the function symbols defined in the library (in this case, addNumbers
).MethodHandles.lookup()
to create a MethodHandle
for the native function. The method signature is specified using MethodType
.MethodHandle.invoke()
method.If you want to allocate and manipulate native memory (outside the Java heap), you can use the MemorySegment and MemoryAccess classes, which are part of the Foreign Memory API.
import java.lang.foreign.*;
public class MemoryAccessExample {
public static void main(String[] args) {
try (var scope = MemoryScope.openConfined()) {
// Allocate native memory
MemorySegment segment = MemorySegment.allocateNative(4); // Allocating 4 bytes (e.g., for an integer)
// Write to the memory segment
MemoryAccess.setInt(segment, 0, 42); // Write the value 42 at the start of the segment
// Read from the memory segment
int value = MemoryAccess.getInt(segment, 0);
System.out.println("Value from native memory: " + value); // Should print 42
}
}
}
MemorySegment.allocateNative()
method allocates memory outside of the Java heap (native memory).MemoryAccess.setInt()
method writes the integer value 42
into the allocated memory.MemoryAccess.getInt()
method reads the value from the native memory segment.
try-with-resources
.
import com.niteshsynergy.java17
import java.io.*;
import java.util.logging.*;
public class ContextSpecificDeserializationFilter {
// Define a custom filter for deserialization
public static final ObjectInputFilter MY_FILTER = new ObjectInputFilter() {
@Override
public Status checkInput(ObjectInputFilter.FilterInfo filterInfo) {
// Example: only allow objects of type "String"
if (filterInfo.serialClass() != null && filterInfo.serialClass() == String.class) {
return Status.ALLOWED;
}
return Status.REJECTED;
}
};
public static void main(String[] args) {
// Example object to serialize and deserialize
String originalObject = "This is a safe object.";
// Serialize the object
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serialized_object.ser"))) {
oos.writeObject(originalObject);
System.out.println("Object serialized successfully!");
} catch (IOException e) {
e.printStackTrace();
}
// Deserialize the object using the custom filter
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("serialized_object.ser"))) {
// Apply the filter when deserializing
ois.setObjectInputFilter(MY_FILTER);
Object deserializedObject = ois.readObject();
if (deserializedObject instanceof String) {
System.out.println("Deserialized Object: " + deserializedObject);
} else {
System.out.println("Rejected object during deserialization.");
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
MY_FILTER
):String
objects are allowed to be deserialized. If any other class is encountered, the object is rejected.String originalObject
) is serialized into a file serialized_object.ser
using ObjectOutputStream
.ObjectInputStream
, the filter MY_FILTER
is applied using the setObjectInputFilter
method.String
objects during deserialization.String
, it is printed.String
or if the filter rejects it, a message indicating rejection is printed.
Let’s consider a financial application leveraging Java 17 features:
public sealed interface Transaction permits Deposit, Withdrawal, Transfer {}
public final class Deposit implements Transaction {
private final double amount;
public Deposit(double amount) { this.amount = amount; }
}
public final class Withdrawal implements Transaction {
private final double amount;
public Withdrawal(double amount) { this.amount = amount; }
}
public final class Transfer implements Transaction {
private final double amount;
private final String account;
public Transfer(double amount, String account) {
this.amount = amount;
this.account = account;
}
}
public static void handleTransaction(Transaction tx) {
switch (tx) {
case Deposit d -> System.out.println("Processing deposit: " + d.amount);
case Withdrawal w -> System.out.println("Processing withdrawal: " + w.amount);
case Transfer t -> System.out.println("Transferring " + t.amount + " to " + t.account);
default -> throw new IllegalArgumentException("Unknown transaction");
}
}
Want to donate ? Pay → https://razorpay.me/@niteshsynergy
Your email address will not be published. Required fields are marked *