How to inspect and use the enhanced Optional.orElseThrow() in Java 10

In Java 10, the Optional.orElseThrow() method was enhanced to become the preferred method for retrieving a value from an Optional when the value is present, and throwing an exception otherwise. Let’s explore how this works.


Enhanced Optional.orElseThrow()

Prior to Java 10, the Optional class provided:

  • orElse() – Retrieves the value if present or returns a default value.
  • orElseGet() – Retrieves the value or calculates one using a supplier.
  • orElseThrow(Supplier<? extends X> exceptionSupplier) – Retrieves the value or throws the exception provided by the supplier.

With Java 10, the Optional.orElseThrow() now acts as a shorthand for orElseThrow(NoSuchElementException::new) when you need to retrieve a value, and throw an exception if the value is absent, without providing a custom exception supplier.


Usage

Key Behavior:

  • If the Optional contains a value, orElseThrow() will return the value.
  • If the Optional is empty, it will throw a NoSuchElementException.

Example Code:

package org.kodejava.util;

import java.util.NoSuchElementException;
import java.util.Optional;

public class EnhancedOptionalExample {

    public static void main(String[] args) {
        // An Optional with a value
        Optional<String> optionalWithValue = Optional.of("Hello, Java 10!");

        // Retrieve the value using orElseThrow()
        String value = optionalWithValue.orElseThrow();
        System.out.println("Value: " + value); // Output: Hello, Java 10!

        // An empty Optional
        Optional<String> emptyOptional = Optional.empty();

        try {
            // Attempt to retrieve the value from an empty Optional
            emptyOptional.orElseThrow();
        } catch (NoSuchElementException e) {
            System.err.println("Caught Exception: " + e.getMessage()); // Output: No value present
        }
    }
}

Comparison with Other Optional Methods

Method Behavior
orElse(value) Returns the value if present; otherwise, returns the provided default value.
orElseGet(supplier) Returns the value if present; otherwise, computes a value using the supplier.
orElseThrow(supplier) Returns the value if present; otherwise, throws an exception provided by the supplier.
orElseThrow() (Java 10) Returns the value if present; otherwise, throws a NoSuchElementException (default).

Advantages of Enhanced orElseThrow()

  1. Simplicity: Eliminates the need to write orElseThrow(NoSuchElementException::new) explicitly.
  2. Readability: Makes the code concise and expressive.
  3. Standardized Exception: Default exception (NoSuchElementException) aligns with the semantics of an empty Optional.

Real-World Use Case

A common scenario is when processing data that is expected to be present:

Example:

Optional<String> username = fetchUsernameFromDatabase();

String verifiedUsername = username.orElseThrow();
System.out.println("Verified Username: " + verifiedUsername);

Here, if the username is absent, the application will throw a runtime exception (NoSuchElementException), indicating data inconsistency.


The enhanced Optional.orElseThrow() introduced in Java 10 simplifies handling Optional objects by providing a default exception mechanism without needing a custom supplier.

How to use the Collectors.toUnmodifiableList() and other new Collectors in Java 10

In Java 10, a significant enhancement was introduced to the java.util.stream.Collectors class: new utility methods to create unmodifiable collections such as lists and sets. One notable method is Collectors.toUnmodifiableList(). This method allows you to efficiently create immutable lists during stream processing, adding to the immutability features provided by Java 9 and earlier versions.

Here’s how you can use Collectors.toUnmodifiableList() and other similar methods introduced in Java 10:


1. Using Collectors.toUnmodifiableList()

The Collectors.toUnmodifiableList() collector creates an unmodifiable list from a stream of elements. This means the resulting list cannot be modified (no adding, removing, or updating elements). If you attempt to modify it, a runtime exception (UnsupportedOperationException) will be thrown.

Example:

package org.kodejava.util.stream;

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

public class UnmodifiableList {
    public static void main(String[] args) {
        // Example list using Collectors.toUnmodifiableList
        List<String> unmodifiableList = Stream.of("A", "B", "C")
                .collect(Collectors.toUnmodifiableList());

        System.out.println("Unmodifiable List: " + unmodifiableList);

        // Attempt to modify the list will throw UnsupportedOperationException
        unmodifiableList.add("D"); // This will throw a runtime exception!
    }
}

Output:

Unmodifiable List: [A, B, C]
Exception in thread "main" java.lang.UnsupportedOperationException

2. Other Collectors Introduced in Java 10

Java 10 introduced two other collectors similar to toUnmodifiableList():

  • Collectors.toUnmodifiableSet()
    • Creates an unmodifiable set from a stream of elements.
    • Duplicate elements will be removed since it’s a set.

Example:

package org.kodejava.util.stream;

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

public class UnmodifiableSet {
    public static void main(String[] args) {
        Set<String> unmodifiableSet = Stream.of("A", "B", "C", "A") // "A" will appear only once
                .collect(Collectors.toUnmodifiableSet());
        System.out.println("Unmodifiable Set: " + unmodifiableSet);

        unmodifiableSet.add("D"); // Throws UnsupportedOperationException
    }
}
  • Collectors.toUnmodifiableMap()
    • Creates an unmodifiable map using key-value pairs from a stream.
    • Requires a way to specify the key and value in the collector.
    • If duplicate keys are produced, it will throw an IllegalStateException.

Example:

package org.kodejava.util.stream;

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

public class UnmodifiableMap {
    public static void main(String[] args) {
        Map<Integer, String> unmodifiableMap = Stream.of("A", "B", "C")
                .collect(Collectors.toUnmodifiableMap(
                        String::length,  // Key mapper
                        v -> v           // Value mapper
                ));

        System.out.println("Unmodifiable Map: " + unmodifiableMap);

        // Attempting to modify will throw an UnsupportedOperationException
        unmodifiableMap.put(2, "D"); // Throws UnsupportedOperationException
    }
}

3. Behavior of Unmodifiable Collections

  • These collectors guarantee that:
    • The collection cannot be modified (no add, remove, put, etc.).
    • Any attempt to modify them results in an UnsupportedOperationException.
    • They are safe to use for read-only purposes.
  • If the stream itself contains null values, a NullPointerException will be thrown.

4. Best Uses of toUnmodifiable*() Collectors

  • Ensuring immutability for collections to prevent accidental modifications.
  • Useful in multi-threaded or concurrent applications where immutability eliminates thread-safety issues.
  • Perfect for cases where only read access is required.

5. Comparison with Java 9 List.of()

Java 9 introduced factory methods like List.of(), Set.of(), and Map.of() for creating immutable collections. While those methods are concise, the new collectors offer more flexibility when working with streams.

Java 9 Example:

List<String> immutableList = List.of("A", "B", "C");

Java 10 Stream Example:

List<String> immutableList = Stream.of("A", "B", "C")
                                   .collect(Collectors.toUnmodifiableList());

Summary Table:

Collector Description Introduced in
Collectors.toUnmodifiableList() Creates an unmodifiable List Java 10
Collectors.toUnmodifiableSet() Creates an unmodifiable Set Java 10
Collectors.toUnmodifiableMap() Creates an unmodifiable Map Java 10

Conclusion

The Collectors.toUnmodifiableList() and related methods introduced in Java 10 are powerful tools for creating immutable collections directly from streams. They ensure immutability, improve code safety, and fit well into functional programming paradigms introduced with Java Streams.

How to create cleaner code with type inference in Java 10

Type inference was introduced in Java 10 with the new var keyword, enabling developers to declare local variables without explicitly specifying their type. This feature can help create cleaner, more concise code by reducing boilerplate, though it should be used judiciously to maintain code readability.

Here’s a guide on how to use type inference effectively and write cleaner code in Java 10 and later:


1. Use var for Local Variables

The var keyword allows you to declare local variables without explicitly stating their type. The compiler infers the type based on the expression assigned to the variable. Here’s how it works:

Example:

var message = "Hello, World!"; // Compiler infers this as String
var count = 42;                // Compiler infers this as int
var list = new ArrayList<String>(); // Compiler infers this as ArrayList<String>

System.out.println(message);  // Hello, World!
System.out.println(count);    // 42

Benefits:

  • Eliminates redundancy. For instance:
List<String> list = new ArrayList<>();

becomes:

var list = new ArrayList<String>();

2. Use var in Loops

In for-each loops and traditional for-loops, var can simplify the code:

Example:

var numbers = List.of(1, 2, 3, 4, 5);
for (var num : numbers) {
    System.out.println(num); // Iterates through the numbers
}

Benefits:

  • Avoids unnecessary type declarations while maintaining readability.

3. Use var with Streams and Lambdas

var integrates well with Java Streams and Lambda expressions to reduce verbosity:

Example:

var numbers = List.of(1, 2, 3, 4, 5);
var result = numbers.stream()
                    .filter(n -> n % 2 == 0)
                    .map(n -> n * 2)
                    .toList();

System.out.println(result); // [4, 8]

When working with complex streams, var can make code shorter and easier to follow.


4. Restrictions on var

While var is versatile, there are some limitations and rules:

  • Only for Local Variables: var can only be used for local variables, loop variables, and indexes, not for class fields, method parameters, or return types.
  • Compiler Must Infer Type: You must assign a value to a var. For example, the following won’t work:
var uninitialized; // Error: cannot use 'var' without initializer
  • Anonymous Classes: Avoid overuse with anonymous classes to maintain clarity.

5. Maintain Readability

While var can simplify code, readability should always be a priority. Overusing var can obscure the code’s intent, especially when dealing with complex types:

Example of Overuse:

var map = new HashMap<List<String>, Set<Integer>>(); // Hard to understand

In such cases, it’s better to use explicit types.


6. Good Practices

  • Use var for Obvious Types:
var name = "John Doe"; // Obviously String
  • Avoid var for Ambiguous Types:
// Original:
var data = performOperation(); // What is the return type?
// Better:
List<String> data = performOperation();
  • Avoid Excessive Chaining:

    Using var with complex chains can make debugging harder. Be explicit when needed.


7. Refactoring Example

Here’s how you can refactor code for better clarity using var:

Before Refactoring:

ArrayList<String> names = new ArrayList<>();
HashMap<String, Integer> nameAgeMap = new HashMap<>();

After Refactoring:

var names = new ArrayList<String>();
var nameAgeMap = new HashMap<String, Integer>();

This is concise without sacrificing clarity.


Conclusion

Type inference with var in Java 10 improves code conciseness and readability when used appropriately. To ensure cleaner code:

  • Use var for obvious and readable scenarios.
  • Avoid using var when the inferred type is unclear or ambiguous.
  • Focus on balancing conciseness with the need for maintainable and self-explanatory code.

How to Use the Java 10 JDK Command Line Tools

The Java 10 JDK offers several command-line tools for developers to use. Here’s an overview of some useful tools and related features:

1. JShell (Interactive Java REPL)

The jshell tool allows developers to experiment interactively by evaluating Java expressions, statements, and code snippets without the need to set up a complete program. It was introduced as part of JDK 9 but is also available in Java 10.

You can use JShell via:

  • Command line: Just type jshell in your terminal/command prompt.
  • IntelliJ IDEA: Open the JShell console through Tools > JShell Console in the IDE. This allows trying smaller snippets of code and experimenting interactively [1].

2. Java Compiler (javac)

The javac tool is the standard way to compile Java source code into bytecode. In Java 10, the --release flag can be used to ensure compatibility with earlier JDK releases.

Command:

javac --release <version> FileName.java

To compile your code:

  1. Open a terminal.
  2. Navigate to the directory containing the .java file.
  3. Run the javac command followed by the file’s name.

3. Java Runner (java)

The java tool is used to execute compiled Java applications or scripts. Java 10 also supports temporary files and improved APIs for startup optimizations.

Command example:

java FileName

4. Java Flight Recorder and Other Tools

Java Flight Recorder is useful for profiling and analyzing runtime performance. In JDK 10, you need to enable UnlockCommercialFeatures if using the Oracle JDK.

For example:

java -XX:+UnlockCommercialFeatures -XX:StartFlightRecording=duration=60s,filename=myrecording.jfr MyApplication

This is useful for monitoring or debugging [4].


5. JLink

The jlink tool lets you create runtime images that include all the modules your application requires (introduced in JDK 9). With Java 10, improvements were made for better custom image creation.

Command example:

jlink --module-path <modules-path> --add-modules <module-name> --output <destination-folder>

This tool is handy when distributing lightweight application bundles. IDEs like IntelliJ IDEA also provide options to integrate it into Maven or Gradle builds [5].


6. Managing Executable Scripts

Create and run Java commands or files directly as scripts without needing to compile them. This concept started gaining traction with JDK 11’s “shebang” support but can also apply lightly to Java 10 for executable bundling purposes [6].


7. General IntelliJ IDEA Features for Java 10

IntelliJ IDEA helps users by automatically configuring and detecting Java 10 features, including modular programming. Several integrations for JDK tools like javac, java, and jlink make development smoother [7].


How to Set Up Java 10 and Compile Your First Program

To set up Java 10 and compile your first program, follow these steps. Additionally, you’ll learn about Java 10’s var keyword feature once your setup is complete.


Steps to Set Up Java 10

  1. Download Java 10 JDK:
  2. Install Java 10:
    • Follow the installation wizard to install the JDK.
    • Don’t forget to note the installation path (e.g., C:\Program Files\Java\jdk-10).
  3. Set up the Environment Variables:
    • Add the JDK bin directory to the PATH variable:
      1. Go to System PropertiesAdvancedEnvironment Variables.
      2. Under System Variables, find the Path variable and add the JDK’s bin directory (e.g., C:\Program Files\Java\jdk-10\bin).
    • Verify the setup:
      • Open the command prompt or terminal and type:
        text
        java -version

        You should see the version as Java 10.
  4. Install an IDE or Use a Text Editor:
    • Download and install an IDE like IntelliJ IDEA, Eclipse, or Visual Studio Code (or use a simple text editor).

Compile and Run Your First Java Program

  1. Write the Java Program:
    Create a file named HelloWorld.java with the following content:

    public class HelloWorld {
       public static void main(String[] args) {
           System.out.println("Hello, World! Welcome to Java 10!");
       }
    }
    
  2. Compile Your Program:
    From the command prompt, navigate to the directory containing HelloWorld.java, and run:

    javac HelloWorld.java
    

    This will create a compiled HelloWorld.class file.

  3. Run Your Program:
    Execute the compiled program using:

    java HelloWorld
    

    You should see the output:

    Hello, World! Welcome to Java 10!
    

Using the var Keyword in Java 10

To explore Java 10’s var keyword for local variable type inference, you can enhance your program. Update the HelloWorld class as follows:

package org.kodejava.basic;

import java.util.List;

public class HelloWorld {
   public static void main(String[] args) {
      var message = "Hello, Java 10!";
      var numbers = List.of(1, 2, 3);

      System.out.println(message);
      for (var number : numbers) {
         System.out.println("Number: " + number); // Using 'var' in loop
      }
   }
}
  • Save the changes as HelloWorld.java.
  • Recompile and run using the steps above.

You’ll see the output:

Hello, Java 10!
Number: 1
Number: 2
Number: 3

The var keyword simplifies variable declarations without compromising type safety, ensuring better code readability and brevity.


Conclusion

You’ve successfully set up Java 10, compiled, and executed your first program. Additionally, you explored how to use Java 10’s var keyword for type inference. Keep experimenting with these features to leverage Java 10’s capabilities!