Java 8 Programming Language Enhancements
Java 8 provides following features for Java Programming:
- Lambda expressions,
- Method references,
- Functional interfaces,
- Stream API,
- Default methods,
- Base64 Encode Decode,
- Static methods in interface,
- Optional class,
- Collectors class,
- ForEach() method,
- Nashorn JavaScript Engine,
- Parallel Array Sorting,
- Type and Repating Annotations,
- IO Enhancements,
- Concurrency Enhancements,
- JDBC Enhancements etc.
Lambda Expressions
Lambda expression helps us to write our code in functional style. It provides a clear and concise way to implement SAM interface (Single Abstract Method) by using an expression. It is very useful in collection library in which it helps to iterate, filter and extract data.
For more information and examples: click here
Syntax
The basic syntax of a lambda expression is:
- (argument-list) -> {body}
- argument-list: It can be empty or non-empty as well. It represents the parameters used by the expression.
- arrow-token (->): It links the arguments to the body of the expression.
- body: It contains expressions and statements for the lambda expression.
Filename: LambdaExample.java
- import java.util.Arrays;
- import java.util.List;
- import java.util.function.Predicate;
- public class LambdaExample {
- public static void main(String[] args) {
- List<String> languages = Arrays.asList(“Java”, “Python”, “JavaScript”, “C++”);
- System.out.println(“Languages which starts with ‘J’:”);
- filter(languages, (str) -> str.startsWith(“J”));
- }
- public static void filter(List<String> names, Predicate<String> condition) {
- for(String name: names) {
- if(condition.test(name)) {
- System.out.println(name + ” “);
- }
- }
- }
- }
Output:
Languages which starts with 'J': Java JavaScript
Method References
Java 8 Method reference is used to refer method of functional interface. It is compact and easy form of lambda expression. Each time when you are using lambda expression to just referring a method, we can replace our lambda expression with method reference.
For more information and examples: click here
Types of Method References
There are four main types of method references in Java:
1. Static Method References: They reference methods that are static within classes.
Syntax:
- ClassName::staticMethodName
Example: Math::max references the max method of the Math class.
2. Instance Method References of a Particular Object: They reference methods of a specific instance of a class.
Syntax:
- instance::instanceMethodName
Example: Suppose str is an instance of String, str::length references the length method of str.
3. Instance Method References of an Arbitrary Object of a Particular Type: They reference methods of an instance that will be supplied at the time of calling.
Syntax:
- ClassName::methodName
Example: String::toLowerCase references the toLowerCase method on an instance of String that will be determined at runtime.
4. Constructor References: They reference constructors of classes.
Syntax:
- ClassName::new
Example: ArrayList::new references the constructor of ArrayList.
Filename: MethodReferenceExample.java
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.List;
- import java.util.function.Function;
- import java.util.function.Supplier;
- import java.util.stream.Collectors;
- public class MethodReferenceExample {
- public static void main(String[] args) {
- List<String> words = Arrays.asList(“Java”, “Stream”, “Method”, “References”);
- // Static Method Reference: Converting all strings to uppercase
- List<String> upperCaseWords = words.stream()
- .map(String::toUpperCase) // static method reference
- .collect(Collectors.toList());
- System.out.println(“Uppercase Words: ” + upperCaseWords);
- // Instance Method Reference of an Arbitrary Object of a Particular Type
- System.out.println(“Printing each word:”);
- words.forEach(System.out::println); // instance method reference
- // Constructor Reference: Creating new instances
- Supplier<List<String>> listSupplier = ArrayList::new; // constructor reference
- List<String> newList = listSupplier.get();
- newList.addAll(words);
- System.out.println(“New List: ” + newList);
- // Additional Example: Using Function Interface for Constructor Reference
- Function<String, Integer> stringToInteger = Integer::new; // constructor reference
- Integer number = stringToInteger.apply(“100”);
- System.out.println(“String to Integer: ” + number);
- }
- }
Output:
Uppercase Words: [JAVA, STREAM, METHOD, REFERENCES] Printing each word:Java Stream Method References New List: [Java, Stream, Method, References] String to Integer: 100
Functional Interface
An Interface that contains only one abstract method is known as functional interface. It can have any number of default and static methods. It can also declare methods of object class.
Functional interfaces are also known as Single Abstract Method Interfaces (SAM Interfaces).
For more information and examples: click here
Filename: FunctionalInterfaceExample.java
- @FunctionalInterface
- interface Converter<F, T> {
- T convert(F from);
- }
- public class FunctionalInterfaceExample {
- public static void main(String[] args) {
- // Using the Converter functional interface with a lambda expression
- Converter<String, Integer> stringToInteger = Integer::valueOf;
- // Applying the converter to convert a string to an integer
- int convertedValue = stringToInteger.convert(“123”);
- System.out.println(“Converted Value: ” + convertedValue);
- // Another example, converting case of a string
- Converter<String, String> upperCaseConverter = String::toUpperCase;
- String convertedString = upperCaseConverter.convert(“java”);
- System.out.println(“Converted String: ” + convertedString);
- }
- }
Output:
Converted Value: 123 Converted String: JAVA
Optional
Java introduced a new class Optional in Java 8. It is a public final class which is used to deal with NullPointerException in Java application. We must import java.util package to use this class. It provides methods to check the presence of value for particular variable.
For more information and examples: click here
Filename: OptionalMain.java
- import java.util.Optional;
- public class OptionalMain {
- public static void main(String[] args) {
- String[] str = new String[10]; // Initialize an array of strings with default null values.
- str[5] = “Hello, Optional!”; // Uncomment this line to test with a non-null value.
- // Create an Optional object from the value of str[5].
- Optional<String> checkNull = Optional.ofNullable(str[5]);
- // Check if the Optional object contains a value.
- if (checkNull.isPresent()) {
- // Convert the string to lowercase if it’s not null.
- String word = str[5].toLowerCase();
- System.out.println(word); // Print the lowercase string.
- } else {
- System.out.println(“string is null”); // Indicate that the string is null.
- }
- }
- }
Output:
hello, optional!
forEach
Java provides a new method forEach() to iterate the elements. It is defined in Iterable and Stream interfaces.
It is a default method defined in the Iterable interface. Collection classes which extends Iterable interface can use forEach() method to iterate elements.
This method takes a single parameter which is a functional interface. So, you can pass lambda expression as an argument.
For more information and examples: click here
Filename: ForEachMapExample.java
- import java.util.Map;
- import java.util.HashMap;
- public class ForEachMapExample {
- public static void main(String[] args) {
- // Create a map of Integer keys and String values
- Map<Integer, String> map = new HashMap<>();
- map.put(1, “One”);
- map.put(2, “Two”);
- map.put(3, “Three”);
- map.put(4, “Four”);
- // Use forEach to iterate over the map and print each key-value pair
- map.forEach((key, value) -> System.out.println(“Key: ” + key + “, Value: ” + value));
- }
- }
Output:
Key: 1, Value: One Key: 2, Value: Two Key: 3, Value: Three Key: 4, Value: Four
Date/Time API
Java has introduced a new Date and Time API since Java 8. The java.time package contains Java 8 Date and Time classes.
For more information and examples: click here
API Specification
- java.time: Core classes for dates, times, combined date and time, instants, durations, periods, and clocks using the ISO-8601 system.
- java.time.chrono: Supports non-ISO calendar systems with predefined and custom chronologies.
- java.time.format: For formatting and parsing date-time objects.
- java.time.temporal: Advanced features for date-time manipulation, aimed at library developers.
- java.time.zone: Handles time zones, offsets, and rules.
Filename: DateTimeApiShortExample.java
- import java.time.LocalDate;
- import java.time.format.DateTimeFormatter;
- public class DateTimeApiShortExample {
- public static void main(String[] args) {
- // Current Date
- LocalDate today = LocalDate.now();
- System.out.println(“Today: ” + today);
- // Adding 5 days
- LocalDate futureDate = today.plusDays(5);
- System.out.println(“Future Date: ” + futureDate);
- // Formatting the future date
- DateTimeFormatter formatter = DateTimeFormatter.ofPattern(“dd-MM-yyyy”);
- String formattedDate = futureDate.format(formatter);
- System.out.println(“Formatted Future Date: ” + formattedDate);
- // Parsing a date string
- String dateString = “25-12-2024”;
- LocalDate parsedDate = LocalDate.parse(dateString, formatter);
- System.out.println(“Parsed Date: ” + parsedDate);
- }
- }
Output:
Today: 2024-02-13 Future Date: 2024-02-18 Formatted Future Date: 18-02-2024 Parsed Date: 2024-12-25
Static Method in Interface in Java
Static methods in interfaces are similar to static methods in classes. They are defined with the static keyword and can be called without an instance of the class that implements the interface. These methods are part of the interface and not the objects that implement the interface. Thus, they provide a convenient place for utility methods related to the interface.
Filename: ActionExecutor.java
- // Define an interface for displaying messages.
- interface MessageDisplay {
- // Static method to display a static greeting message.
- static void showStaticMessage() {
- // Print a static greeting message to the console.
- System.out.println(“Static Greeting: Welcome!”);
- }
- // Abstract method to be implemented by classes for executing a custom action with a string input.
- void executeCustomAction(String input);
- }
- // Class that implements the MessageDisplay interface to execute actions.
- public class ActionExecutor implements MessageDisplay {
- // Main method – the entry point of the program.
- public static void main(String[] args) {
- // Create an instance of the ActionExecutor class.
- ActionExecutor executor = new ActionExecutor();
- // Call the static method from the MessageDisplay interface to show the static message.
- MessageDisplay.showStaticMessage();
- // Call the implemented abstract method with a custom message.
- executor.executeCustomAction(“Overridden Message: Action Completed.”);
- }
- // Implementation of the abstract method from the MessageDisplay interface.
- @Override
- public void executeCustomAction(String inputData) {
- // Print the input data to the console.
- System.out.println(inputData);
- }
- }
Output:
Static Greeting: Welcome! Overridden Message: Action Completed.
IO Enhancements
Java 8 introduced several enhancements to the Input/Output (IO) and New Input/Output (NIO) frameworks, focusing primarily on improving the ease of use and efficiency of file and stream handling. These enhancements are part of the java.nio package and include the following notable features:
Stream API Enhancements for IO
- list(Path dir): This method returns a lazily filled stream of Path objects, where each element represents a directory entry in the specified directory (dir).
- lines(Path path): This method reads all lines from a file specified by the path parameter and returns them as a Stream<String>. Each element of the stream represents a line of text from the file.
- find(): This method is used to search for files in the file tree rooted at a provided starting file. It returns a stream filled with Path objects representing the files found during the search. However, you haven’t provided the complete method signature, so it’s unclear how this method is used.
- lines(): This method returns a stream containing all of the lines from the BufferedReader’s input source. Each element of the stream represents a line of text from the input source. This method is useful for processing text data in a file or from any other input source.
Type and Repeating Annotations
Type Annotations
Type Annotations augment Java’s type system by allowing annotations to be used in any context where a type is used. This enhancement enables developers to convey more information to the compiler, aiding in error detection and prevention at compile time. For example, to safeguard against NullPointerException, a variable declaration can be annotated to ensure that it never holds a null value:
- @NonNull String str;
Further examples of Type Annotations include:
Ensuring a list does not contain null elements:
- @NonNull List<String>
Specifying that elements of a list must not be null:
- List<@NonNull String> str
Declaring that an array should only contain non-negative integers:
- Arrays<@NonNegative Integer> sort
Marking a file as encrypted for security purposes:
- @Encrypted File file
Indicating that a connection is open and should be managed accordingly:
- @Open Connection connection
Specifying an exception thrown under a particular condition, such as division by zero:
- void divideInteger(int a, int b) throws @ZeroDivisor ArithmeticException
Repeating Annotations
Java 8 introduced the concept of Repeating Annotations, allowing you to apply the same annotation multiple times to a single element in your code. This feature is particularly useful for situations where you need to repeatedly annotate an element with the same annotation to convey multiple pieces of information or apply multiple settings.
To use Repeating Annotations, Java requires two key pieces:
1. Declare a Repeatable Annotation Type:
First, we declare the @Review annotation and mark it as repeatable using the @Repeatable meta-annotation. The value of @Repeatable is set to the container annotation type, which in this case is Reviews.
- import java.lang.annotation.Repeatable;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- @Repeatable(Reviews.class)
- @Retention(RetentionPolicy.RUNTIME) // Make this annotation available at runtime.
- @interface Review {
- String reviewer();
- String date();
- String comment();
- }
In this example, @Review annotations can include the reviewer’s name, the date of the review, and a comment.
2. Declare the Containing Annotation Type:
We define the Reviews container annotation. It must have a value element that returns an array of the repeatable annotation type (Review[]). This container is used to hold all the @Review annotations applied to the same element.
- @Retention(RetentionPolicy.RUNTIME) // Make this annotation available at runtime.
- @interface Reviews {
- Review[] value();
- }
Filename: EmployeeRoles.java
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- import java.lang.annotation.Repeatable;
- // Define the repeatable annotation type
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.TYPE) // Apply to class level
- @Repeatable(Roles.class)
- @interface Role {
- String value();
- }
- // Define the container annotation for the repeatable annotation
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.TYPE) // Apply to class level
- @interface Roles {
- Role[] value();
- }
- // Use the repeating annotation on a class
- @Role(“Developer”)
- @Role(“Lead”)
- @Role(“Manager”)
- public class EmployeeRoles {
- public static void main(String[] args) throws NoSuchMethodException {
- // Access and print the repeated annotations
- if (EmployeeRoles.class.isAnnotationPresent(Roles.class)) {
- Roles rolesAnnotation = EmployeeRoles.class.getAnnotation(Roles.class);
- for (Role role : rolesAnnotation.value()) {
- System.out.println(“Role: ” + role.value());
- }
- } else {
- System.out.println(“No Roles Annotation present.”);
- }
- }
- }
Output:
Role: Developer Role: Lead Role: Manager
Default Methods
Java provides a facility to create default methods inside the interface. Methods which are defined inside the interface and tagged with default keyword are known as default methods. These methods are non-abstract methods and can have method body.
For more information and examples: click here
Filename: DefaultMethodsExample.java
- interface Vehicle {
- // Abstract method
- String getBrand();
- // Default method
- default void turnAlarmOn() {
- System.out.println(“The vehicle alarm is now on.”);
- }
- // Another default method
- default void turnAlarmOff() {
- System.out.println(“The vehicle alarm is now off.”);
- }
- }
- class Car implements Vehicle {
- private String brand;
- Car(String brand) {
- this.brand = brand;
- }
- @Override
- public String getBrand() {
- return brand;
- }
- // The class can choose to override a default method
- @Override
- public void turnAlarmOn() {
- System.out.println(“The car alarm is now on.”);
- }
- }
- public class DefaultMethodsExample {
- public static void main(String[] args) {
- Vehicle myCar = new Car(“Tesla”);
- System.out.println(“Brand: ” + myCar.getBrand());
- myCar.turnAlarmOn(); // Overridden method
- myCar.turnAlarmOff(); // Inherited default method
- }
- }
Output:
Brand: Tesla The car alarm is now on. The vehicle alarm is now off.
Nashorn JavaScript Engine
Nashorn JavaScript Engine
Nashorn is a JavaScript engine. It is used to execute JavaScript code dynamically at JVM (Java Virtual Machine). Java provides a command-line tool jjs that is used to execute JavaScript code.
We can execute JavaScript code by two ways:
- By Using jjs command-line tool, and
- By embedding into Java source code.
StringJoiner
Java added a new final class StringJoiner in java.util package. It is used to construct a sequence of characters separated by a delimiter. Now, we can create string by passing delimiters like comma(,), hyphen(-) etc.
For more information and examples: click here
Filename: StringJoinerExample.java
- import java.util.StringJoiner;
- public class StringJoinerExample {
- public static void main(String[] args) {
- // Create a StringJoiner with a delimiter, prefix, and suffix
- StringJoiner joiner = new StringJoiner(“, “, “[“, “]”);
- // Add strings to the StringJoiner
- joiner.add(“Apple”);
- joiner.add(“Banana”);
- joiner.add(“Cherry”);
- joiner.add(“Date”);
- // Convert the StringJoiner to String and print the result
- String result = joiner.toString();
- System.out.println(result);
- }
- }
Output:
[Apple, Banana, Cherry, Date]
Collectors
Collectors is a final class that extends Object class. It provides reduction operations, such as accumulating elements into collections, summarizing elements according to various criteria etc.
For more information and examples: click here
Filaname: CollectorsExample.java
- import java.util.Arrays;
- import java.util.List;
- import java.util.Map;
- import java.util.stream.Collectors;
- public class CollectorsExample {
- public static void main(String[] args) {
- // Example list of people’s names
- List<String> names = Arrays.asList(“John”, “Sara”, “Mark”, “Sara”, “Chris”, “Paula”);
- // Collecting into a List
- List<String> nameList = names.stream().collect(Collectors.toList());
- System.out.println(“Names List: ” + nameList);
- // Grouping names by the first letter
- Map<Character, List<String>> namesByFirstLetter = names.stream()
- .collect(Collectors.groupingBy(name -> name.charAt(0)));
- System.out.println(“Names Grouped by First Letter: ” + namesByFirstLetter);
- // Joining names into a single string separated by commas
- String allNames = names.stream().collect(Collectors.joining(“, “));
- System.out.println(“All Names Joined: ” + allNames);
- // Counting the distinct names
- long distinctNameCount = names.stream().distinct().count();
- System.out.println(“Distinct Names Count: ” + distinctNameCount);
- }
- }
Output:
Names List: [John, Sara, Mark, Sara, Chris, Paula]
Names Grouped by First Letter: {J=[John], S=[Sara, Sara], M=[Mark], C=[Chris], P=[Paula]}
All Names Joined: John, Sara, Mark, Sara, Chris, Paula
Distinct Names Count: 5
Stream API
Java 8 java.util.stream package consists of classes, interfaces and an enum to allow functional-style operations on the elements. It performs lazy computation. So, it executes only when it requires.
For more information and examples: click here
Filename: StreamApiExample.java
- import java.util.Arrays;
- import java.util.List;
- import java.util.stream.Collectors;
- public class StreamApiExample {
- public static void main(String[] args) {
- // A list of integers
- List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
- // Use Stream API to filter, map, and collect operations
- List<Integer> evenSquares = numbers.stream()
- .filter(n -> n % 2 == 0) // Filter even numbers
- .map(n -> n * n) // Map to their squares
- .collect(Collectors.toList()); // Collect results into a list
- // Print the resulting list
- System.out.println(evenSquares);
- }
- }
Output:
[4, 16, 36, 64, 100]
Stream Filter
Java stream provides a method filter() to filter stream elements on the basis of given predicate. Suppose, you want to get only even elements of your list, you can do this easily with the help of filter() method.
This method takes predicate as an argument and returns a stream of resulted elements.
For more information and examples: click here
Syntax:
The filter method accepts a Predicate<T> as its argument. A Predicate<T> is a functional interface specifying a single boolean-valued method with one argument of type T.
- Stream<T> filter(Predicate<? super T> predicate)
Filename: StreamFilterExample.java
- import java.util.Arrays;
- import java.util.List;
- import java.util.stream.Collectors;
- public class StreamFilterExample {
- public static void main(String[] args) {
- // A list of names
- List<String> names = Arrays.asList(“John”, “Sara”, “Mark”, “Jennifer”, “Paul”, “Jane”);
- // Use Stream API to filter names that start with “J”
- List<String> namesStartingWithJ = names.stream()
- .filter(name -> name.startsWith(“J”)) // Filter names starting with “J”
- .collect(Collectors.toList()); // Collect results into a list
- // Print the filtered list
- System.out.println(namesStartingWithJ);
- }
- }
Output:
[John, Jennifer, Jane]
Java Base64 Encoding and Decoding
Java provides a class Base64 to deal with encryption and decryption. You need to import java.util.Base64 class in your source file to use its methods.
This class provides three different encoders and decoders to encrypt information at each level.
For more information and examples: click here
Filename: Base64Example.java
- import java.util.Base64;
- public class Base64Example {
- public static void main(String[] args) {
- // Original String
- String originalString = “Hello, World!”;
- // Encode using basic encoder
- String encodedString = Base64.getEncoder().encodeToString(originalString.getBytes());
- System.out.println(“Encoded String (Basic) : ” + encodedString);
- // Decode the base64 encoded string
- byte[] decodedBytes = Base64.getDecoder().decode(encodedString);
- String decodedString = new String(decodedBytes);
- System.out.println(“Decoded String : ” + decodedString);
- // URL and Filename safe encoding
- String urlEncodedString = Base64.getUrlEncoder().encodeToString(originalString.getBytes());
- System.out.println(“Encoded String (URL) : ” + urlEncodedString);
- // MIME encoder example
- String mimeEncodedString = Base64.getMimeEncoder().encodeToString(originalString.getBytes());
- System.out.println(“Encoded String (MIME) : ” + mimeEncodedString);
- }
- }
Output:
Encoded String (Basic) : SGVsbG8sIFdvcmxkIQ== Decoded String : Hello, World! Encoded String (URL) : SGVsbG8sIFdvcmxkIQ== Encoded String (MIME) : SGVsbG8sIFdvcmxkIQ==
Java Parallel Array Sorting
Java provides a new additional feature in Arrays class which is used to sort array elements parallelly. The parallelSort() method has added to java.util.Arrays class that uses the JSR 166 Fork/Join parallelism common pool to provide sorting of arrays. It is an overloaded method.
For more information and examples: click here
Filename: ParallelArraySortingExample.java
- import java.util.Arrays;
- import java.util.Comparator;
- public class ParallelArraySortingExample {
- public static void main(String[] args) {
- // Parallel sorting for an array of primitives
- int[] numbers = {9, 3, 1, 5, 13, 12, 7, 4, 11, 6};
- System.out.println(“Original array: ” + Arrays.toString(numbers));
- Arrays.parallelSort(numbers);
- System.out.println(“Sorted array: ” + Arrays.toString(numbers));
- // Parallel sorting for an array of objects with a custom comparator
- String[] fruits = {“Peach”, “Apple”, “Orange”, “Banana”, “Grape”, “Pear”};
- System.out.println(“\nOriginal array: ” + Arrays.toString(fruits));
- // Using a lambda expression for the comparator to sort in reverse alphabetical order
- Arrays.parallelSort(fruits, Comparator.reverseOrder());
- System.out.println(“Sorted array in reverse order: ” + Arrays.toString(fruits));
- }
- }
Output:
Original array: [9, 3, 1, 5, 13, 12, 7, 4, 11, 6] Sorted array: [1, 3, 4, 5, 6, 7, 9, 11, 12, 13] Original array: [Peach, Apple, Orange, Banana, Grape, Pear] Sorted array in reverse order: [Pear, Peach, Orange, Grape, Banana, Apple]
Hey very nice site!! Man .. Beautiful .. Amazing .. I’ll bookmark your blog and take the feeds also厈I am happy to find a lot of useful info here in the post, we need work out more strategies in this regard, thanks for sharing. . . . . .
An fascinating dialogue is worth comment. I think that you must write extra on this matter, it may not be a taboo topic but generally persons are not enough to speak on such topics. To the next. Cheers