How do I use the Virtual Threads API Project Loom?

The Virtual Threads API is part of Project Loom in the Java platform. With Java 19, virtual threads became available as a preview feature, enabling the creation of lightweight threads that can run concurrently. They work similarly to traditional threads but are much cheaper in terms of memory and thread management because they are managed by the Java runtime, not the operating system. This makes it possible to scale the number of threads easily, even in the millions.

Here’s how you can use the Virtual Threads API:

1. Enable Virtual Threads

Virtual threads are available in Java 19+ as an incubating feature. To use them:

  • Ensure that you’re using a compatible version of Java (Java 19 or later).
  • Add the JVM flag --enable-preview to enable preview features when running your program.

2. Creating Virtual Threads

Java provides the java.lang.Thread class and the Executors utility to work with virtual threads. For example:

Creating a Virtual Thread

You can create and start a virtual thread like this:

Thread.startVirtualThread(() -> {
    System.out.println("This is a virtual thread!");
});

Using Virtual Threads with Executors

The Executors.newVirtualThreadPerTaskExecutor() method creates an ExecutorService that launches a new virtual thread for each task:

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(() -> System.out.println("Task on a virtual thread"));
    executor.submit(() -> System.out.println("Another virtual thread task"));
}

3. Advantages of Virtual Threads

  1. Lightweight: Virtual threads are less resource-intensive because they use the Java runtime scheduler rather than the OS scheduler. Millions of threads can be created.
  2. Non-blocking: Blocking operations in virtual threads don’t block OS resources, making them very efficient for I/O-intensive workloads like web servers or concurrent network communication.
  3. Easier Scaling: They simplify concurrent programming by allowing you to continue using the familiar thread-per-task model without worrying about resource limits.
  4. Works with Existing Code: Virtual threads integrate well with existing Java APIs like java.util.concurrent.

4. When to Use Virtual Threads

Virtual threads are ideal for:

  • Concurrent I/O tasks like HTTP servers or database connections.
  • High-concurrency environments where traditional threads might run out of OS resources.
  • Migrating legacy multithreaded code to take advantage of better scalability.

5. Example: HTTP Server with Virtual Threads

Here’s a minimal example showcasing how to use virtual threads for handling multiple HTTP requests:

import java.net.ServerSocket;
import java.net.Socket;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.PrintWriter;

public class VirtualThreadHttpServer {
    public static void main(String[] args) throws Exception {
        try (var serverSocket = new ServerSocket(8080)) {
            while (true) {
                Socket clientSocket = serverSocket.accept();
                Thread.startVirtualThread(() -> handleClient(clientSocket));
            }
        }
    }

    private static void handleClient(Socket clientSocket) {
        try (var in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
             var out = new PrintWriter(clientSocket.getOutputStream(), true)) {
            out.println("Hello from the Virtual Thread server!");
            String input;
            while ((input = in.readLine()) != null) {
                System.out.println("Received: " + input);
                if ("exit".equalsIgnoreCase(input)) {
                    break;
                }
                out.println("You said: " + input);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

This makes use of virtual threads to handle each incoming socket connection, which scales efficiently for high-concurrency workloads.

6. Integration with Structured Concurrency

Virtual threads can be combined with structured concurrency (introduced in Java 21) for safer and more manageable multithreading. Structured concurrency allows parent threads to manage the lifecycle of child threads.

Example of Structured Concurrency:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class StructuredConcurrencyExample {
    public static void main(String[] args) throws Exception {
        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            var future1 = executor.submit(() -> {
                Thread.sleep(500);
                return "Task 1 completed";
            });

            var future2 = executor.submit(() -> {
                Thread.sleep(300);
                return "Task 2 completed";
            });

            // Wait for results
            System.out.println(future1.get());
            System.out.println(future2.get());
        }
    }
}

Keynotes:

  • Virtual threads require no changes in application logic. Code written for traditional Thread can immediately benefit from using virtual threads.
  • They simplify thread management while maintaining excellent performance for non-blocking I/O operations.

Limitations:

  • Virtual threads won’t improve performance for CPU-bound tasks; you still need to consider the number of logical CPUs in your system.
  • JVM preview features need to be enabled since virtual threads are not yet finalized in the standard Java API. Check the latest Java release notes for updates.

How do I collect stream results into an immutable collection?

In Java, you can use the Stream API’s Collectors to gather stream results into an immutable collection. Since Java 10, you can use Collectors.toUnmodifiableList(), Collectors.toUnmodifiableSet(), and other similar methods to collect the results into unmodifiable collections.

Here’s how you can collect the stream results into an immutable collection:

1. Immutable List

To collect the results of a stream into an immutable list:

package org.kodejava.util.stream;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ImmutableCollectionExample {
    public static void main(String[] args) {
        List<String> immutableList = Stream.of("apple", "banana", "cherry")
                .collect(Collectors.toUnmodifiableList());

        System.out.println(immutableList);

        // Attempting to modify the list will throw UnsupportedOperationException
        // immutableList.add("date"); // Throws UnsupportedOperationException
    }
}

2. Immutable Set

To collect the results into an immutable set:

package org.kodejava.util.stream;

import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ImmutableCollectionExample {
    public static void main(String[] args) {
        Set<String> immutableSet = Stream.of("apple", "banana", "cherry")
                .collect(Collectors.toUnmodifiableSet());

        System.out.println(immutableSet);

        // Attempting to modify the set will throw UnsupportedOperationException
        // immutableSet.add("date"); // Throws UnsupportedOperationException
    }
}

3. Immutable Map

To collect results into an immutable map:

package org.kodejava.util.stream;

import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ImmutableCollectionExample {
    public static void main(String[] args) {
        Map<String, Integer> immutableMap = Stream.of("apple", "banana", "cherry")
                .collect(Collectors.toUnmodifiableMap(
                        fruit -> fruit,         // Key mapper: the fruit itself
                        fruit -> fruit.length() // Value mapper: the length of the fruit name
                ));

        System.out.println(immutableMap);

        // Attempting to modify the map will throw UnsupportedOperationException
        // immutableMap.put("date", 4); // Throws UnsupportedOperationException
    }
}

Notes:

  • Unmodifiable vs Immutable: Collections created with Collectors.toUnmodifiableList(), Collectors.toUnmodifiableSet(), and Collectors.toUnmodifiableMap() are unmodifiable. While they cannot be changed (add, remove, replace), immutability might imply further guarantees (e.g., deeply immutable objects inside the collection, which this does not enforce).
  • Introduced in Java 10: toUnmodifiableList(), toUnmodifiableSet(), and toUnmodifiableMap() were introduced in Java 10. If you’re using Java 8 or Java 9, you’ll need a custom approach for creating immutable collections (like Collections.unmodifiableList).

In Java 8:

If you’re stuck on Java 8, you can achieve something similar using Collections.unmodifiableList() or other Collections.unmodifiableXxx methods:

package org.kodejava.util.stream;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ImmutableCollectionExample {
    public static void main(String[] args) {
        List<String> immutableList = Collections.unmodifiableList(
                Stream.of("apple", "banana", "cherry").collect(Collectors.toList())
        );

        System.out.println(immutableList);
        // immutableList.add("date"); // Throws UnsupportedOperationException
    }
}

This approach, however, wraps an existing modifiable collection, so try to update your project to take advantage of Java 10+ features.

How do I use LinkedHashMap for predictable iteration order?

In Java, a LinkedHashMap is a subtype of HashMap that maintains a predictable iteration order. It uses a doubly linked list to store the entries in insertion order (or, optionally, access order). Here’s how you can use LinkedHashMap for predictable iteration order:

1. Maintaining Insertion Order

By default, a LinkedHashMap iterates its entries in the order they were inserted. This is useful when you want to retrieve elements in the same order you added them.

Here’s an example:

package org.kodejava.util;

import java.util.LinkedHashMap;
import java.util.Map;

public class LinkedHashMapExample {
    public static void main(String[] args) {
        // Creating LinkedHashMap
        Map<String, Integer> map = new LinkedHashMap<>();

        // Adding elements (insertion order)
        map.put("One", 1);
        map.put("Two", 2);
        map.put("Three", 3);
        map.put("Four", 4);

        // Iterating through the map
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + " => " + entry.getValue());
        }
    }
}

Output:

One => 1
Two => 2
Three => 3
Four => 4

In this example, the elements are iterated in the same order they were inserted.


2. Maintaining Access Order

You can configure a LinkedHashMap to maintain access order, which means it reorders entries based on the most recent access. To enable access order, you must use the constructor that takes a boolean parameter for accessOrder.

Here’s an example:

package org.kodejava.util;

import java.util.LinkedHashMap;
import java.util.Map;

public class AccessOrderExample {
    public static void main(String[] args) {
        // Creating LinkedHashMap with access-order
        Map<String, Integer> map = new LinkedHashMap<>(16, 0.75f, true);

        // Adding elements
        map.put("One", 1);
        map.put("Two", 2);
        map.put("Three", 3);

        // Accessing some elements
        map.get("One");  // Access "One"
        map.get("Three"); // Access "Three"

        // Iterating through the map
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + " => " + entry.getValue());
        }
    }
}

Output:

Two => 2
One => 1
Three => 3

In this case:

  • Initially, the insertion order was One, Two, Three.
  • After accessing One and Three, they were moved to the end, making Two the first in the iteration order.

3. Removing the Oldest Entry with Access Order

If needed, you can use a LinkedHashMap in combination with its removeEldestEntry method to automatically remove the oldest entry (e.g., implementing a cache).

Here’s how:

package org.kodejava.util;

import java.util.LinkedHashMap;
import java.util.Map;

public class RemoveEldestExample {
    public static void main(String[] args) {
        // Create LinkedHashMap with override for removeEldestEntry
        LinkedHashMap<String, Integer> map = new LinkedHashMap<>(3, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry<String, Integer> eldest) {
                return size() > 3; // Remove oldest if size > 3
            }
        };

        // Adding elements
        map.put("One", 1);
        map.put("Two", 2);
        map.put("Three", 3);
        map.put("Four", 4); // "One" will be removed here

        // Accessing some elements
        map.get("Two");
        map.put("Five", 5); // "Three" will be removed here

        // Iterating through the map
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + " => " + entry.getValue());
        }
    }
}

Output:

Four => 4
Two => 2
Five => 5

Explanation:

  1. The map was set to remove the eldest (first) entry when its size exceeds 3.
  2. When "Four" was added, "One" was removed because the size limit was exceeded.
  3. When "Five" was added, "Three" was removed, as it was now the eldest entry after accessing "Two".

Summary of Key Points:

  1. Insertion Order: By default, the iteration order matches the insertion order.
  2. Access Order: Can be enabled using the LinkedHashMap constructor with accessOrder = true.
  3. Custom Behavior: Override the removeEldestEntry method to create a fixed-size cache or similar functionality.

LinkedHashMap is handy when you need consistent iteration order (e.g., for caches, ordering-sensitive collections).

How to Use System.currentTimeMillis() for Performance Timing

In Java, System.currentTimeMillis() is commonly used as a simple way to measure the execution time of a block of code or a specific operation in terms of milliseconds. Here’s how you can effectively use it for performance timing:

Example Usage

package org.kodejava.lang;

public class PerformanceTimingExample {
    public static void main(String[] args) {
        // Record the start time
        long startTime = System.currentTimeMillis();

        // The code you want to measure
        performOperation();

        // Record the end time
        long endTime = System.currentTimeMillis();

        // Calculate the elapsed time
        long elapsedTime = endTime - startTime;

        // Print the result
        System.out.println("Execution time: " + elapsedTime + " milliseconds");
    }

    private static void performOperation() {
        try {
            // Simulate time-consuming task
            Thread.sleep(2000); // Sleep for 2 seconds
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

Steps Explained

  1. Record Start Time: Use System.currentTimeMillis() before the block of code you want to measure.
  2. Execute Operation: Run the code or process whose performance you need to measure.
  3. Record End Time: Capture the time after the code execution using System.currentTimeMillis().
  4. Calculate Elapsed Time: Subtract the start time from the end time to get the elapsed time in milliseconds.
  5. Output Results: Display or log the elapsed time for performance analysis.

Things to Keep in Mind

  • Resolution: System.currentTimeMillis() measures the current time in milliseconds since the Unix epoch (January 1, 1970). However, its granularity may vary depending on the system, and it is not as precise as System.nanoTime() for very fine-grained measurements.
  • Avoid Garbage Collection Interference: When measuring performance, ensure that garbage collection has minimal impact by warming up the JVM and avoiding memory-intensive operations.
  • Use System.nanoTime() for Better Precision: If you need higher precision or want to avoid timer granularity issues, consider using System.nanoTime() instead. This measures elapsed time in nanoseconds and is suitable for shorter durations.

Example with System.nanoTime()

package org.kodejava.lang;

public class NanoTimingExample {
    public static void main(String[] args) {
        // Record the start time
        long startTime = System.nanoTime();

        // The code you want to measure
        performOperation();

        // Record the end time
        long endTime = System.nanoTime();

        // Calculate the elapsed time in milliseconds
        long elapsedTime = (endTime - startTime) / 1_000_000;

        // Print the result
        System.out.println("Execution time: " + elapsedTime + " milliseconds");
    }

    private static void performOperation() {
        try {
            // Simulate time-consuming task
            Thread.sleep(2000); // Sleep for 2 seconds
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

Conclusion

System.currentTimeMillis() is a simple and effective method to time operations, especially those involving multiple seconds or milliseconds. However, for finer-grained timing or benchmarking (e.g., sub-millisecond accuracy), prefer System.nanoTime(). Always ensure that your measurements are consistent and unaffected by other system activities, such as garbage collection or OS-level processes.

How to Use StringBuilder for Efficient String Concatenation

In Java, using StringBuilder is a common way to handle efficient string concatenation, especially when working with loops or when you need to concatenate a large number of strings. Unlike String, which is immutable, StringBuilder is mutable and modifies its internal character array without creating new objects, hence improving performance.

Here’s how you can use StringBuilder for efficient string concatenation:

1. Creating a StringBuilder instance

You can create a new instance of StringBuilder using its constructor:

StringBuilder sb = new StringBuilder();

You can also initialize it with an existing string:

StringBuilder sb = new StringBuilder("Hello");

2. Appending Strings

Use the .append() method to concatenate strings:

StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("World");
System.out.println(sb.toString()); // Output: "Hello World"

Here, the append() method modifies the existing StringBuilder instance.


3. Inserting Strings

To insert a string at a specific position, use the .insert() method:

StringBuilder sb = new StringBuilder("Hello World");
sb.insert(6, "Beautiful ");
System.out.println(sb.toString()); // Output: "Hello Beautiful World"

4. Replacing Part of the String

You can replace part of the string using .replace():

StringBuilder sb = new StringBuilder("Hello Java");
sb.replace(6, 10, "World");
System.out.println(sb.toString()); // Output: "Hello World"

5. Reversing the String

You can reverse the string using .reverse():

StringBuilder sb = new StringBuilder("abcd");
sb.reverse();
System.out.println(sb.toString()); // Output: "dcba"

6. Deleting Characters or Substrings

You can use .delete() or .deleteCharAt() to remove parts of the string:

StringBuilder sb = new StringBuilder("Hello World");
sb.delete(5, 11); // Remove characters from index 5 to 10
System.out.println(sb.toString()); // Output: "Hello"

sb.deleteCharAt(0); // Remove the character at index 0
System.out.println(sb.toString()); // Output: "ello"

7. Converting Back to a String

Once you are done building the string, convert it back to a String using .toString():

StringBuilder sb = new StringBuilder("Hello");
String result = sb.toString();
System.out.println(result); // Output: "Hello"

8. StringBuilder in Loops

It is particularly useful when appending strings in loops to avoid the overhead of creating multiple String instances:

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5; i++) {
    sb.append("Number ").append(i).append(", ");
}
System.out.println(sb.toString());
// Output: "Number 0, Number 1, Number 2, Number 3, Number 4, "

Example: Complete Code

Here’s a complete example that combines multiple methods:

public class StringBuilderExample {
    public static void main(String[] args) {
        // Create a StringBuilder
        StringBuilder sb = new StringBuilder("Example");

        // Append strings
        sb.append(" of").append(" StringBuilder");

        // Insert a string
        sb.insert(8, " java");

        // Replace a substring
        sb.replace(0, 7, "Demo");

        // Delete part of the string
        sb.delete(5, 10);

        // Reverse the string
        sb.reverse();

        // Convert back to String
        System.out.println(sb.toString());
    }
}

Output:

redliuBgnirtS fo omeD

Performance Comparison: String vs StringBuilder

Here’s a quick comparison of the performance:

  • String: Creates a new object for each concatenation, which is inefficient in loops.
  • StringBuilder: Reuses the same object and modifies its internal buffer, which is much faster.

So, whenever you’re performing a lot of string manipulations, especially in loops, it’s highly recommended to use StringBuilder.

How to Format Strings Using String.format()

In Java, the String.format() method is a convenient way to create formatted strings using placeholders. It allows you to include values such as numbers or strings at specific positions in a string by using format specifiers. Here’s how you can use it:

Syntax

String.format(String format, Object... args)
  • format: The format string with placeholders.
  • args: The arguments to replace the placeholders.

Common Format Specifiers

  • %s: String.
  • %d: Decimal integer.
  • %f: Floating-point number.
  • %c: Character.
  • %%: Literal % character.

You can combine these with width, precision, alignment, and other formatting options.


Examples

1. String Formatting

String name = "John";
int age = 30;
String formattedString = String.format("My name is %s and I am %d years old.", name, age);
System.out.println(formattedString);
// Output: My name is John and I am 30 years old.

2. Formatting Numbers

double price = 123.456789;
String formattedPrice = String.format("The price is %.2f.", price);
System.out.println(formattedPrice);
// Output: The price is 123.46.
  • %.2f: Limits the floating-point value to 2 decimal places.

3. Padding and Alignment

  • Right-aligned text:
String formattedString = String.format("%10s", "Java");
System.out.println(formattedString);
// Output: "      Java" (padded with spaces to the left, 10 characters in total)
  • Left-aligned text:
String formattedString = String.format("%-10s", "Java");
System.out.println(formattedString);
// Output: "Java      " (padded with spaces to the right, 10 characters in total)

4. Adding Leading Zeros

int number = 42;
String formattedNumber = String.format("%05d", number);
System.out.println(formattedNumber);
// Output: 00042

5. Formatting Multiple Values

String result = String.format("%s scored %d out of %d in the exam.", "Alice", 90, 100);
System.out.println(result);
// Output: Alice scored 90 out of 100 in the exam.

6. Escaping %

To include a literal % in the string, use %%.

String formattedString = String.format("Progress: %.2f%%", 85.123);
System.out.println(formattedString);
// Output: Progress: 85.12%

Notes

  1. Null Values: If a value in args is null, %s outputs the string "null".
  2. Exceptions: Make sure the placeholders match the number and type of arguments; otherwise, it may throw an exception (e.g., IllegalFormatException).

Formatted strings are especially useful when generating user-friendly messages or handling precise output formatting, such as in reporting systems or logs.

How do I get operating system process information using ProcessHandle?

Java 9 introduced the ProcessHandle API, which allows us to interact with and retrieve information about native processes. Here’s how we can use ProcessHandle to get information about operating system processes:

We can list all the processes currently running on the system and print their details:

package org.kodejava.example.lang;

import java.time.Duration;
import java.time.Instant;

public class ProcessHandleExample {
    public static void main(String[] params) {
        ProcessHandle.allProcesses()
                .forEach(process -> {
                    long pid = process.pid();
                    ProcessHandle.Info info = process.info();
                    String cmd = info.command().orElse("");
                    String[] args = info.arguments().orElse(new String[0]);
                    Instant startTime = info.startInstant().orElse(null);
                    Duration cpuUsage = info.totalCpuDuration().orElse(Duration.ZERO);

                    System.out.println("PID        = " + pid);
                    System.out.println("Command    = " + cmd);
                    System.out.println("Args       = " + String.join(" ", args));
                    System.out.println("Start Time = " + startTime);
                    System.out.println("CPU Usage  = " + cpuUsage);
                    System.out.println("------------");
                });
    }
}

If we want to get information about a specific process, we can use their process ID (PID):

package org.kodejava.example.lang;

import java.time.Duration;
import java.time.Instant;
import java.util.Optional;

public class SpecificProcessInfo {
    public static void main(String[] params) {
        // Replace with the PID of the process you want to query
        long pid = 33656;

        // Get the ProcessHandle of the specific process
        Optional<ProcessHandle> processHandle = ProcessHandle.of(pid);
        if (processHandle.isPresent()) {
            ProcessHandle process = processHandle.get();
            pid = process.pid();
            ProcessHandle.Info info = process.info();
            String cmd = info.command().orElse("");
            String[] args = info.arguments().orElse(new String[0]);
            Instant startTime = info.startInstant().orElse(null);
            Duration cpuUsage = info.totalCpuDuration().orElse(Duration.ZERO);

            System.out.println("PID        = " + pid);
            System.out.println("Command    = " + cmd);
            System.out.println("Args       = " + String.join(" ", args));
            System.out.println("Start Time = " + startTime);
            System.out.println("CPU Usage  = " + cpuUsage);
            System.out.println("------------");
        } else {
            System.out.println("No process found with PID: " + pid);
        }
    }
}

Output:

PID        = 33656
Command    = C:\Users\wayan\AppData\Local\Programs\IntelliJ IDEA Ultimate\bin\idea64.exe
Args       = 
Start Time = 2024-07-22T03:14:07.825Z
CPU Usage  = PT46M27.484375S
------------

Explanation

  • ProcessHandle.allProcesses(): returns a stream of all processes currently running on the system.
  • ProcessHandle.of(pid): returns an Optional<ProcessHandle> for the process with the given PID.
  • ProcessHandle.Info: contains information about a process, such as its command, arguments, start time, and CPU usage.
  • info.command(): returns an Optional<String> with the command used to start the process.
  • info.arguments(): returns an Optional<String[]> with the arguments passed to the process.
  • info.startInstant(): returns an Optional<Instant> with the start time of the process.
  • info.totalCpuDuration(): returns an Optional<Duration> with the total CPU time used by the process.

Using the ProcessHandle API in Java 9 and later makes it straightforward to get detailed information about operating system processes.

How do I check if a character is a whitespace in Java?

Whitespace characters in Java (or programming in general) aren’t just the space ' ' character. It also includes other characters that create some form of space or break in the text. The most common ones include:

  • space ' '
  • tab '\t'
  • newline '\n'
  • carriage return '\r'
  • form feed '\f'.

All these characters fall into the category of whitespace characters.

Now, if we want to check if a character in Java is one of these whitespace characters, we can make use of the built-in method Character.isWhitespace(char ch). Character is a class in Java that provides a number of useful class (i.e., static) methods for working with characters. And the isWhitespace() method is one of them which checks if the provided character is a whitespace character.

Here is a simple code snippet:

package org.kodejava.lang;

public class CharacterIsWhitespace {
    public static void main(String[] args) {
        char ch = ' ';

        if (Character.isWhitespace(ch)) {
            System.out.println(ch + " is a whitespace character.");
        } else {
            System.out.println(ch + " is not a whitespace character.");
        }
    }
}

This code first defines a character ch and then uses Character.isWhitespace(ch) to check if it is a whitespace character. The isWhitespace() method returns true if the given character is a space, new line, tab, or other whitespace characters, false otherwise.

Here’s a little more expansive example:

package org.kodejava.lang;

import java.util.Arrays;
import java.util.List;

public class CharacterIsWhitespaceDemo {
    public static void main(String[] args) {
        List<Character> characters = Arrays.asList(' ', '\t', '\n', '\r', '\f', 'a', '1');
        for (char ch : characters) {
            if (Character.isWhitespace(ch)) {
                System.out.println("'" + ch + "' is a whitespace character.");
            } else {
                System.out.println("'" + ch + "' is not a whitespace character.");
            }
        }
    }
}

Output:

' ' is a whitespace character.
'   ' is a whitespace character.
'
' is a whitespace character.
' is a whitespace character.
'' is a whitespace character.
'a' is not a whitespace character.
'1' is not a whitespace character.

In this code snippet, we are checking and outputting whether each character in a list of characters is a whitespace character or not. The list includes a space, a tab, newline, carriage return, form feed, an alphabetic character, and a digit. The isWhitespace() method identifies correctly which ones are the whitespace characters.

The Character.isWhitespace(char ch) method in Java also considers Unicode whitespace. It checks for whitespace according to the Unicode standard. The method considers a character as a whitespace if and only if it is a Unicode space separator (category “Zs”), or if it is one of the following explicit characters:

  • U+0009, HORIZONTAL TABULATION (‘\t’)
  • U+000A, LINE FEED (‘\n’)
  • U+000B, VERTICAL TABULATION
  • U+000C, FORM FEED (‘\f’)
  • U+000D, CARRIAGE RETURN (‘\r’)

Here is an example of checking Unicode whitespace:

package org.kodejava.lang;

public class CharacterIsWhitespaceUnicode {
    public static void main(String[] args) {
        char ch = '\u2003';  // EM SPACE

        if (Character.isWhitespace(ch)) {
            System.out.println("Character '" + ch + "' (\\u2003) is a whitespace character.");
        } else {
            System.out.println("Character '" + ch + "' (\\u2003) is not a whitespace character.");
        }
    }
}

Output:

Character ' ' (\u2003) is a whitespace character.

In this example, \u2003 is a Unicode representation of the “EM SPACE” character, which is a type of space character in the Unicode standard. The isWhitespace() method correctly identifies it as a whitespace character.

What is the purpose of String.strip() method of Java 11?

The purpose of the String.strip() method in Java 11 is to remove whitespaces from both the beginning and end of a string. This is very similar to the String.trim() method available in earlier versions of Java, but there is a key difference between them.

Here’s the difference:

  • String.strip(): Introduced in Java 11, strip() uses the unicode definition of whitespace. It removes not only space characters but also all other types of unicode-defined spaces, such as the thin space \u2009, etc.
  • String.trim(): Available from Java 1.0, trim() is more limited. It considers a whitespace to be any character whose ASCII value is less than or equal to 32 (a space, tab, newline, and a few other control characters).

Here are examples of how they work:

package org.kodejava.lang;

public class StringStripExample {
    public static void main(String[] args) {
        // String.strip()
        String first = " \u2009Hello  ";
        System.out.println(first.strip()); // Outputs "Hello"

        // String.trim()
        String second = " \u2009Hello  ";
        System.out.println(second.trim()); // Outputs "\u2009Hello"
    }
}

Output:

Hello
 Hello

Thus, strip() method is more comprehensive in removing different types of whitespace defined in Unicode, while trim() only removes ASCII control characters and spaces.

There are also String.stripLeading() and String.stripTrailing() methods that were introduced in Java 11, and they are similar to the strip() method, but they only remove the whitespace characters from either the beginning or the end of the string, respectively.

Here is what they do:

  • String.stripLeading(): This method removes any leading whitespace from the string. “Leading” in this context means any whitespace characters at the beginning of the string.
  • String.stripTrailing(): This method removes any trailing whitespace from the string. “Trailing” in this context means any whitespace characters at the end of the string.

Both stripLeading() and stripTrailing() use the Unicode definition of whitespace, the same as strip() method.

Here are examples of how they work:

package org.kodejava.lang;

public class StringStripLeadingTrailingExample {
    public static void main(String[] args) {
        // Strip leading whitespace
        String first = " \u2009Hello World  ";
        System.out.println(first.stripLeading());  // Outputs "Hello World  "

        // Strip trailing whitespace
        String second = " \u2009Hello World  ";
        System.out.println(second.stripTrailing()); // Outputs " \u2009Hello World"
    }
}

Output:

Hello World  
  Hello World

As demonstrated, stripLeading() removed the whitespace characters from the front of the string, and stripTrailing() removed the whitespace characters from the end of the string.

While \u00A0 is technically a type of whitespace (specifically, a non-breaking space or NBSP), it isn’t considered as such by the strip(), stripLeading(), and stripTrailing() methods, which follow the Character.isWhitespace(char) method’s definition of what constitutes a whitespace character.

According to the Java documentation, the Character.isWhitespace(char) method, which the strip() methods use, considers the following characters as whitespace:

  • ‘\t’ U+0009 HORIZONTAL TABULATION
  • ‘\n’ U+000A LINE FEED
  • ‘\u000B’ U+000B VERTICAL TABULATION
  • ‘\f’ U+000C FORM FEED
  • ‘\r’ U+000D CARRIAGE RETURN
  • ‘\u001C’ U+001C FILE SEPARATOR
  • ‘\u001D’ U+001D GROUP SEPARATOR
  • ‘\u001E’ U+001E RECORD SEPARATOR
  • ‘\u001F’ U+001F UNIT SEPARATOR
  • SPACE_SEPARATOR category types

The \u2009 (thin space) and \u0020 (space) are part of SPACE_SEPARATOR category according to Unicode standard and will be correctly stripped.

The \u00A0 (non-breaking space) is actually part of a different category called the NO-BREAK_SPACE and is not considered whitespace by Character.isWhitespace(char), so it won’t be stripped.

How do I use String.join() method in Java?

The String.join() method in Java is a static method added in Java 8 to the java.lang.String class. The String.join() is a static utility method used to concatenate multiple strings, arrays or collections (like lists and sets) of strings. This method makes it easier to join multiple strings with a specific delimiter. A delimiter is a sequence of characters used to separate strings.

This method returns a new String composed of copies of the CharSequence elements joined together with a copy of the specified delimiter. This method saves us from writing boilerplate loop code just for concatenating strings with a delimiter.

Here is an example of how you can use it:

package org.kodejava.lang;

import java.util.Arrays;
import java.util.List;

public class StringJoinList {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("Java", "is", "cool");
        String result = String.join(" ", list);
        System.out.println(result);
    }
}

Output:

Java is cool

In this example, String.join() takes two parameters:

  1. A delimiter that is a CharSequence (like a String) that is placed between each joined String.
  2. An Iterable object like a List or a Set, over which the method iterates and joins all elements into a single String.

You can also use String.join() with an array of elements:

package org.kodejava.lang;

public class StringJoinArray {
    public static void main(String[] args) {
        String[] array = new String[]{"Java", "is", "cool"};
        String result = String.join(" ", array);
        System.out.println(result);
    }
}

Output:

Java is cool

In this case, String.join() still takes a delimiter as the first argument, but the second argument is an Array of elements to be joined.