Exploring Java's Modern Features: From Java 7 to Java 17
Here is the features matrix that provides information about the availability of features starting from a specific version.
Version and features introduced
// Before Java 7
List<String> list = new ArrayList<String>();
// With Diamond Operator (Java 7)
List<String> list = new ArrayList<>();
Try-With-Resources : Try-With-Resources automates resource management by automatically closing resources like files or sockets when they are no longer needed, improving code readability and robustness
// Before Java 7
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("somefile.txt"));
// something crazy
} catch (IOException e) {
// handle
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
// Handle IOE
}
}
}
// With Try-With-Resources (Java 7)
try (BufferedReader reader = new BufferedReader(new FileReader("somefile.txt"))) {
// Read and process the file
} catch (IOException e) {
// Handle exception
}
Lambda Expressions: Lambda Expressions introduce a concise way to define and use anonymous functions, enabling more expressive and functional programming styles.
// Before Java 8 - Using anonymous inner class
Runnable runnable = new Runnable() {
public void run() {
System.out.println("Hello, Java 8!");
}
};
// With Lambda Expression (Java 8)
Runnable runnable = () -> {
System.out.println("Hello, Java 8!");
};
Stream API :The Stream API provides a powerful way to work with collections of data, allowing operations like filtering, mapping, and reducing to be performed in a functional and declarative manner. - Dive into Streams API
// Traditional Loop
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = 0;
for (int number : numbers) {
sum += number;
}
// Using Stream API (Java 8)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
Date and Time API (java.time) : The Date and Time API introduces a modern and comprehensive way to handle date and time-related operations, addressing the shortcomings of the old Date and Calendar classes.
// Working with Dates
LocalDate date = LocalDate.now();
LocalDate tomorrow = date.plusDays(1);
// Working with Times
LocalTime time = LocalTime.now();
LocalTime noon = LocalTime.of(12, 0);
// Combining Date and Time
LocalDateTime dateTime = LocalDateTime.of(date, time);
Nashorn JavaScript Engine: Nashorn is a JavaScript engine that allows Java applications to execute JavaScript code, enabling seamless integration of JavaScript and Java applications.
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class NashornExample {
public static void main(String[] args) throws ScriptException {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.eval("print('Hello from Nashorn JavaScript!')");
}
}
// Without var (explicit type declaration)
List<String> names = new ArrayList<>();
// With var (type inference)
var names = new ArrayList<String>();
before
if (object instanceof String) {
System.out.println("Length of str: " + (String)object.length());
}
//after
if (object instanceof String str) {
// Use 'str' as a String within this block
System.out.println("Length of str: " + str.length());
}
// no need of breaks
int dayNumber = switch (dayOfWeek) {
case "Monday" -> 1;
case "Tuesday" -> 2;
case "Wednesday" -> 3;
case "Thursday" -> 4;
case "Friday" -> 5;
case "Saturday" -> 6;
case "Sunday" -> 7;
default -> {
System.out.println("Invalid day of the week");
yield -1; // Default value for invalid input
}
};
Records : Records provide a concise way to declare classes that are primarily used to store data, automatically generating common methods like equals, hashCode, and toString.
// Traditional class- either we used to write or Use Lombok Data
public class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getters, equals, hashCode, toString...
}
// With Records (Java 17)
record Person(String name, int age) {}
Sealed Classes: Sealed Classes restrict the set of classes that can extend or implement them, enhancing code maintainability and security by explicitly defining the permitted subclasses.
// Traditional class hierarchy
public abstract class Shape {}
public class Circle extends Shape {}
public class Rectangle extends Shape {}
// ...
// With Sealed Classes (Java 17)
public sealed abstract class Shape permits Circle, Rectangle {}
public final class Circle extends Shape {}
public final class Rectangle extends Shape {}
Java 8 Streams
Here are the list of functions available in stream:
// need Upper case words list
List<String> words = Arrays.asList("apple", "banana", "cherry");
List<String> uppercasedWords = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// need only even numbers
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// print all Names
List<String> names = Arrays.asList("Ram", "Bob", "Charlie");
names.stream()
.forEach(System.out::println);
// print the stream in the process
List<Integer> values = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> doubledValues = values.stream()
.peek(v -> System.out.println("Doubling: " + v))
.map(v -> v * 2)
.collect(Collectors.toList());
//Sum the given int list
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.reduce(0, Integer::sum);
List<String> words = Arrays.asList("apple", "banana", "cherry");
Map<Integer, List<String>> wordsByLength = words.stream()
.collect(Collectors.groupingBy(String::length));
//merge the lists
List<List<Integer>> nestedLists = Arrays.asList(Arrays.asList(1, 2,4,5), Arrays.asList(3, 4,4,5,6));
List<Integer> flatList = nestedLists.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
//Distinct
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4);
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> sortedNames = names.stream()
.sorted()
.collect(Collectors.toList());
List<Integer> numbers = Arrays.asList(5, 2, 9, 1, 7);
Optional<Integer> minNumber = numbers.stream()
.min(Integer::compare);
List<Integer> numbers = Arrays.asList(5, 2, 9, 1, 7);
Optional<Integer> maxNumber = numbers.stream()
.max(Integer::compare);
List<String> words = Arrays.asList("apple", "banana", "cherry");
long wordCount = words.stream()
.count();
Recommended by LinkedIn
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
boolean hasEvenNumber = numbers.stream()
.anyMatch(n -> n % 2 == 0);
List<Integer> numbers = Arrays.asList(2, 4, 6, 8, 10);
boolean allEven = numbers.stream()
.allMatch(n -> n % 2 == 0);
List<Integer> numbers = Arrays.asList(1, 3, 5, 7, 9);
boolean noEvenNumber = numbers.stream()
.noneMatch(n -> n % 2 == 0);
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Optional<String> firstName = names.stream()
.findFirst();
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Optional<String> anyName = names.parallelStream()
.findAny();
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> afterSkipping = numbers.stream()
.skip(2)
.collect(Collectors.toList());
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> limitedNumbers = numbers.stream()
.limit(3)
.collect(Collectors.toList());
List<String> words = Arrays.asList("apple", "banana", "cherry");
String[] wordArray = words.stream()
.toArray(String[]::new);
Stream<Integer> numbersStream = Stream.of(1, 2, 3, 4, 5);
IntStream.range(1, 6) // Generates 1, 2, 3, 4, 5
Stream<String> emptyStream = Stream.empty();
Stream<Integer> stream1 = Stream.of(1, 2, 3);
Stream<Integer> stream2 = Stream.of(4, 5, 6);
Stream<Integer> combinedStream = Stream.concat(stream1, stream2);
Stream.iterate(1, n -> n * 2) // Generates 1, 2, 4, 8, 16, ...
Stream.generate(() -> "Hello, World!") // Generates "Hello, World!", "Hello, World!", ...
// Filtering payments above a certain amount
List<Payment> highValuePayments = payments.stream()
.filter(payment -> payment.getAmount() > 1000.0)
.collect(Collectors.toList());
// Extracting payment IDs from a list of payments
List<String> paymentIds = payments.stream()
.map(Payment::getPaymentId)
.collect(Collectors.toList());
// Sorting payments by date in ascending order
List<Payment> sortedPayments = payments.stream()
.sorted(Comparator.comparing(Payment::getPaymentDate))
.collect(Collectors.toList());
// Sorting by last name in ascending order, then by age in ascending order
List<Person> sortedPeople = people.stream()
.sorted(
Comparator.comparing(Person::getLastName)
.thenComparing(Person::getAge)
)
.collect(Collectors.toList());
List<Payment> payments = // List of Payment objects
// Calculate the average payment amount using stream aggregation
OptionalDouble averageAmount = payments.stream()
.mapToDouble(Payment::getAmount)
.average();
// Calculating the total sum of payments
double totalAmount = payments.stream()
.mapToDouble(Payment::getAmount)
.sum();
Optional<Double> largestAmount = payments.stream()
.map(Payment::getAmount)
.max(Double::compareTo);
// Grouping payments by payment method
Map<PaymentMethod, List<Payment>> paymentsByMethod = payments.stream()
.collect(Collectors.groupingBy(Payment::getPaymentMethod));
List<Payment> payments = // List of Payment objects
double thresholdAmount = 100.0; // Threshold amount
// Partition payments into two groups based on the threshold amount using stream partitioningBy aggregation
Map<Boolean, List<Payment>> partitionedPayments = payments.stream()
.collect(Collectors.partitioningBy(payment -> payment.getAmount() > thresholdAmount));
// Searching for a payment by payment ID
Optional<Payment> foundPayment = payments.stream()
.filter(payment -> payment.getPaymentId().equals(targetPaymentId))
.findFirst();
List<Payment> payments1 = new ArrayList<>();
payments1.add(new Payment("Payment1", 100.0));
payments1.add(new Payment("Payment2", 200.0));
List<Payment> payments2 = new ArrayList<>();
payments2.add(new Payment("Payment3", 300.0));
payments2.add(new Payment("Payment4", 400.0));
// Combining payment data from two collections into a single stream
Stream<Payment> combinedStream = Stream.concat(payments1.stream(), payments2.stream());
// Collecting the combined stream into a list of payments
List<Payment> combinedPayments = combinedStream.collect(Collectors.toList());
Flattening Data
List<Invoice> invoices = new ArrayList<>();
invoices.add(new Invoice("Invoice1", List.of(new Payment("Payment1"), new Payment("Payment2"))));
invoices.add(new Invoice("Invoice2", List.of(new Payment("Payment3"), new Payment("Payment4"))));
// Flatten the list of payments using flatMap
List<Payment> allPayments = invoices.stream()
.flatMap(invoice -> invoice.getPayments().stream())
.collect(Collectors.toList());
// Chaining filter, mapping, and aggregation operations
double averageAmount = payments.stream()
.filter(payment -> payment.getAmount() > 500.0)
.mapToDouble(Payment::getAmount)
.average()
.orElse(0.0);
// Parallel processing for calculating total amount
double totalAmount = payments.parallelStream()
.mapToDouble(Payment::getAmount)
.sum();
// Reading payments from a file and processing using streams
List<Payment> payments = Files.lines(Paths.get("payments.txt"))
.map(Payment::fromCsvLine)
.collect(Collectors.toList());
// Retrieving payments from a database and processing using streams
List<Payment> payments = paymentRepository.findAll()
.stream()
.filter(payment -> payment.getAmount() > 100.0) //NOT RECOMENDED
.collect(Collectors.toList());
List<Payment> payments = new ArrayList<>();
payments.add(new Payment("Payment1", 100.0));
payments.add(new Payment("Payment2", -50.0)); // Negative amount
payments.add(new Payment("Payment3", 200.0));
// Using streams to validate payment data
boolean allValid = payments.stream()
.allMatch(payment -> payment.getAmount() >= 0.0);
// Generating a range of payment amounts from $100 to $500 with a step of $100
List<Payment> generatedPayments = Stream.iterate(100.0, amount -> amount <= 500.0, amount -> amount + 100.0)
.map(amount -> new Payment("Payment" + amount, amount))
.collect(Collectors.toList());
// Sample payment data with null and empty values
List<Payment> payments = new ArrayList<>();
payments.add(new Payment("Payment1", 100.0));
payments.add(null); // Null value
payments.add(new Payment("Payment3", 0.0)); // Empty amount
// Using streams to filter out null and empty payment data
List<Payment> validPayments = payments.stream()
.filter(Objects::nonNull) // Filter out null payments
.filter(payment -> payment.getAmount() > 0.0) // Filter out empty payments
.collect(Collectors.toList());
// Sample event data representing events in an event-driven system
List<Event> events = new ArrayList<>();
events.add(new Event("Event1", EventType.LOGIN, "User1"));
events.add(new Event("Event2", EventType.LOGOUT, "User2"));
events.add(new Event("Event3", EventType.PURCHASE, "User1"));
// Using streams to filter and transform events
List<String> filteredUserEvents = events.stream()
.filter(event -> event.getType() == EventType.LOGIN)
.map(event -> "User: " + event.getUsername() + " logged in")
.collect(Collectors.toList());