8 Unnamed Variables and Patterns

Unnamed variables are variables that can be initialized but not used. Unnamed patterns can appear in a pattern list of a record pattern, and always match the corresponding record component. You can use them instead of a type pattern. They remove the burden of having to write a type and name of a pattern variable that's not needed in subsequent code. You denote both with the underscore character (_).

For background information about unnamed variables and patterns, see JEP 456.

Unnamed Variables

You can use the underscore keyword (_) as the name of a local variable, exception, or lambda parameter in a declaration when the value of the declaration isn't needed. This is called an unnamed variable, which represents a variable that’s being declared but it has no usable name.

Unnamed variables are useful when the side effect of a statement is more important than its result.

Consider the following example that iterates through the elements of the array orderIDs with a for loop. The side effect of this for loop is that it calculates the number of elements in orderIDs without ever using the loop variable id:

        int[] orderIDs = {34, 45, 23, 27, 15};
        int total = 0;
        for (int id : orderIDs) {
            total++;
        }
        System.out.println("Total: " + total);

You can use an unnamed variable to omit or elide the unused variable id:

        int[] orderIDs = {34, 45, 23, 27, 15};
        int total = 0;
        for (int _ : orderIDs) {
            total++;
        }
        System.out.println("Total: " + total);

The following table describes where you can declare an unnamed variable:

Table 8-1 Valid Unnamed Variable Declarations

Declaration Type Example with Unnamed Variable
A local variable declaration statement in a block
record Caller(String phoneNumber) { }

static List everyFifthCaller(Queue<Caller> q, int prizes) {
    var winners = new ArrayList<Caller>();
    try {
        while (prizes > 0) {
            Caller _ = q.remove();
            Caller _ = q.remove();
            Caller _ = q.remove();
            Caller _ = q.remove();
            winners.add(q.remove());
            prizes--;
        }
    } catch (NoSuchElementException _) {
        // Do nothing
    }
    return winners;
}

Note that you don't have to assign the value returned by Queue::remove to a variable, named or unnamed. You might want to do so to signify that a lesser-known API returns a value that your application doesn't use.

A resource specification of a try-with-resources statement
static void doesFileExist(String path) {
    try (var _ = new FileReader(path)) {
        // Do nothing
    } catch (IOException e) {
        e.printStackTrace();
    }
}	
The header of a basic for statement
Function<String,Integer> sideEffect =
    s -> {
        System.out.println(s);
        return 0;
    };
    
for (int i = 0, _ = sideEffect.apply("Starting for-loop");
    i < 10; i++) {
    System.out.println(i);
}
The header of an enhanced for loop
static void stringLength(String s) {
    int len = 0;
    for (char _ : s.toCharArray()) {
        len++;
    }
    System.out.println("Length of " + s + ": " + len);
}
An exception parameter of a catch block
static void validateNumber(String s) {
    try {
        int i = Integer.parseInt(s);
        System.out.println(i + " is valid");
    } catch (NumberFormatException _) {
        System.out.println(s + " isn't valid");
    }
} 
A formal parameter of a lambda expression
record Point(double x, double y) {}
record UniqueRectangle(String id,
    Point upperLeft, Point lowerRight) {}       
    
static Map getIDs(List<UniqueRectangle> r) {
    return r.stream()
            .collect(
                Collectors.toMap(
                    UniqueRectangle::id,
                    _ -> "NODATA"));
}

Unnamed Patterns

Consider the following example that calculates the distance between two instances of ColoredPoint:

    record Point(double x, double y) {}
    enum Color { RED, GREEN, BLUE }
    record ColoredPoint(Point p, Color c) {}

    double getDistance(Object obj1, Object obj2) {
        if (obj1 instanceof ColoredPoint(Point p1, Color c1) &&
            obj2 instanceof ColoredPoint(Point p2, Color c2)) {
        return java.lang.Math.sqrt(
            java.lang.Math.pow(p2.x - p1.x, 2) +
            java.lang.Math.pow(p2.y - p1.y, 2));
        } else {
            return -1;
        }
    }

The example doesn't use the Color component of the ColoredPoint record. To simplify the code and improve readability, you can elide the type patterns Color c1 and Color c2 with the unnamed pattern (_):

    double getDistance(Object obj1, Object obj2) {
        if (obj1 instanceof ColoredPoint(Point p1, _) &&
            obj2 instanceof ColoredPoint(Point p2, _)) {
        return java.lang.Math.sqrt(
            java.lang.Math.pow(p2.x - p1.x, 2) +
            java.lang.Math.pow(p2.y - p1.y, 2));
        } else {
            return -1;
        }
    }

Alternatively, you can keep the type pattern's type and elide just its name:

        if (obj1 instanceof ColoredPoint(Point p1, Color _) &&
            obj2 instanceof ColoredPoint(Point p2, Color _))

No value is bound to the unnamed pattern variable. Consequently, the highlighted statement in the following example is invalid:

        if (obj1 instanceof ColoredPoint(Point p1, Color _) &&
            obj2 instanceof ColoredPoint(Point p2, Color _)) {
            // Compiler error: the underscore keyword '_" is only allowed to
            // declare unnamed patterns, local variables, exception parameters or
            // lambda parameters
            System.out.println("Color: " + _); 
            // ...
        }   

Also, you can't use an unnamed pattern as a top-level pattern:

        // Compiler error: the underscore keyword '_' is only allowed to
        // declare unnamed patterns, local variables, exception parameters or
        // lambda parameters        
        if (obj1 instanceof _) {
            // ...
        }

You can use unnamed patterns in switch expressions and statements:

    sealed interface Employee permits Salaried, Freelancer, Intern { }
    record Salaried(String name, long salary) implements Employee { }
    record Freelancer(String name) implements Employee { }
    record Intern(String name) implements Employee { }
        
    void printSalary(Employee b) {
        switch (b) {
            case Salaried r   -> System.out.println("Salary: " + r.salary());
            case Freelancer _ -> System.out.println("Other");
            case Intern _     -> System.out.println("Other");
        }
    }

You may use multiple patterns in a case label provided that they don't declare any pattern variables. For example, you can rewrite the previous switch statement as follows:

        switch (b) {
            case Salaried r              -> System.out.println("Salary: " + r.salary());
            case Freelancer _, Intern _  -> System.out.println("Other");
        }