_
) to denote unnamed variables and patterns, simplifying the handling of unneeded variables in patterns and enhancing code readability._
” only started appearing from Java 8.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); } }
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!")); } }
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()); } }
_
), 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.