Unnamed Variables & Patterns

Quick summary:

  • Allows the use of underscore (_) to denote unnamed variables and patterns, simplifying the handling of unneeded variables in patterns and enhancing code readability.
  • Developers migrating from Java 7 or lower might face issues, as warnings against using “_” only started appearing from Java 8.
  • Released in Java 22 (2024), but introduced in Java 21 (2023).

Key Features

  • Use of Underscore for Unnamed Variables: Enables developers to use an underscore to represent variables that are initialized but not subsequently used, reducing unnecessary code verbosity.
  • Enhanced Readability: By omitting unnecessary names in patterns, code becomes cleaner and easier to maintain, focusing only on relevant components.
  • Efficiency in Pattern Matching: Improves pattern matching by allowing the omission of unused components, which is particularly useful in complex conditional logic.

Example

Before Java 25:

Handling multiple catch blocks required naming exceptions even when they were not used, leading to cluttered code.

try {
    // risky operations
} catch (IOException ex) {
    System.out.println("Something went wrong with the IO");
    // handle exception
} catch (SQLException ex) {
    System.out.println("Something went wrong with the database connection");
    // handle exception
}

After Java 25:

Using underscores, you can simplify exception handling when the exception details are irrelevant.

try {
    // risky operations
} catch (IOException _) {
    System.out.println("Something went wrong with the IO");
    // handle exception
} catch (SQLException _) {
    System.out.println("Something went wrong with the database connection");
    // handle exception
}

Also fun with switch case pattern matching:

    public void processEmployee(Employee emp) {
        switch (emp) {
            case SalariedEmployee e -> System.out.println("Processed salaried employee with salary: " + e.getSalary());
            case HourlyEmployee e -> System.out.println("Processed hourly employee with rate: " + e.getHourlyRate());
            case InternEmployee _ -> System.out.println("Processed intern employee");
            default -> throw new IllegalStateException("Unexpected value: " + emp);
        }
    }

Even more fun with switch case:

    public static void processEmployee(Employee emp) {
        switch (emp) {
            case SalariedEmployee(double salary) -> System.out.println("Processed salaried employee with salary: " + salary);
            case HourlyEmployee(double hourlyRate) -> System.out.println("Processed hourly employee with rate: " + hourlyRate);
            case InternEmployee(_, _) -> System.out.println("Processed intern employee");
            default -> throw new IllegalStateException("Unexpected value: " + emp);
        }
    }
  • Notice how with unnamed record pattern matching, you wanna use an underscore for every component you don’t wanna use (or for all of them if you don’t wanna use any).

👩‍💻 Hands-on Demo: Unnamed Variables & Patterns

  1. Given the following code samples, refactor them by using unnamed variables and patterns where possible:
public class BankAppBefore {
    public static void main(String[] args) {
        Scanner keyboard = new Scanner(System.in);
        System.out.print("Enter the type of account you want to create: ");
        var account = switch (keyboard.nextLine()) {
            case "checking" -> new CheckingAccount();
            case "savings" -> new SavingsAccount();
            default -> new BankAccount();
        };
        String response = switch (account) {
            case CheckingAccount c -> "You have a checking account";
            case SavingsAccount s -> "You have a savings account";
            default -> "You have a bank account";
        };
        System.out.println(response);
    }
}
public class ExpenseAppBefore {
    public static void main(String[] args) {
        double totalExpenses = Set.of(
                new Mortgage("My house", 1000, 0.05, 5),
                new OnlineSubscription("Netflix", "https://www.netflix.com", 10),
                new Product("iPhone", 1000),
                new Product("MacBook", 1000)
        ).stream().mapToDouble(ExpenseAppBefore::calcExpense).sum();
        System.out.printf("Total expenses: $%,.2f\n", totalExpenses);
    }

    private static double calcExpense(Expense expense){
        return switch (expense) {
            case Mortgage(String propertyName, double monthlyPayment, double interestRate, int numberOfYears) -> monthlyPayment * numberOfYears * (1 + interestRate);
            case OnlineSubscription(String name, String url, double monthlyPrice) -> monthlyPrice;
            case Product(String name, double price) -> price;
            default -> 0;
        };
    }
}
public class StreamsBefore {
    public static void main(String[] args) {
        RandomGenerator.getDefault().ints(10, 1, 101)
                .forEach(n -> System.out.println("And another random number!"));
    }
}

Solutions

🕵️‍♂️ Click here to reveal the solutions
public class BankAppAfterRefactor {
    public static void main(String[] args) {
        Scanner keyboard = new Scanner(System.in);
        System.out.print("Enter the type of account you want to create: ");
        var account = switch (keyboard.nextLine()) {
            case "checking" -> new CheckingAccount();
            case "savings" -> new SavingsAccount();
            default -> new BankAccount();
        };
        String response = switch (account) {
            case CheckingAccount _ -> "You have a checking account";
            case SavingsAccount _ -> "You have a savings account";
            default -> "You have a bank account";
        };
        System.out.println(response);
    }
}
public class ExpenseAppAfterRefactor {
    public static void main(String[] args) {
        double totalExpenses = Set.of(
                new Mortgage("My house", 1000, 0.05, 5),
                new OnlineSubscription("Netflix", "https://www.netflix.com", 10),
                new Product("iPhone", 1000),
                new Product("MacBook", 1000)
        ).stream().mapToDouble(ExpenseAppAfterRefactor::calcExpense).sum();
        System.out.printf("Total expenses: $%,.2f\n", totalExpenses);
    }

    private static double calcExpense(Expense expense){
        return switch (expense) {
            case Mortgage(_, double monthlyPayment, double interestRate, int numberOfYears) -> monthlyPayment * numberOfYears * (1 + interestRate);
            case OnlineSubscription(_, _, double monthlyPrice) -> monthlyPrice;
            case Product(_, double price) -> price;
            default -> 0;
        };
    }
}
public class StreamsAfterRefactor {
    public static void main(String[] args) {
        RandomGenerator.getDefault().ints(10, 1, 101)
                .forEach(_ -> System.out.println("And another random number!"));
    }
}
public class Hamster extends Pet {
    public Hamster(String name) {
        super(name);
    }

    @Override
    public void makeSound() {
        System.out.println("Ssssssssssssssssss!");
    }

    @Override
    public void feed() {
        System.out.printf("Feeding %s with a hamster food diet...\n", getName());
    }
}

Summary

  • Simplicity and Readability: By allowing unnamed variables (_), the language helps in avoiding clutter and reducing the cognitive load on developers. This is particularly useful in large codebases where reading and understanding code quickly becomes crucial. Simplified code with unnamed variables makes it easier to understand what each part of the code is supposed to do without getting distracted by unnecessary details.
  • Reduction of Boilerplate: Java has often been criticized for its verbose syntax compared to other modern languages. The introduction of unnamed variables helps reduce this verbosity, particularly in control structures like switch statements or pattern matching expressions where not all variables are needed.
  • Error Reduction: When developers are forced to declare variables that they don’t actually need, it increases the surface area for errors—either through shadowing (accidentally overriding a variable from a higher scope) or simply through unused variables that clutter the codebase. By using unnamed variables, these potential sources of errors are eliminated.