Repetition is the mother of learning. Although we already talked about Java's switch keyword earlier, today we will review the basics and dig into some new information.

Java has two types of switch constructs: the switch statement and the switch expression. The switch expression became official in Java 14, having existed in two unofficial "preview" variants in versions 12 and 13.

But let's start from the beginning. Let's recall what a good old switch looked like before version 12:

public String getProductTypeByName(String product) {
    String productType = "";

    switch (product) {
        case "Apple":
            productType = "Fruit";
            break;

        case "Peach":
            productType = "Fruit";
            break;

        case "Raspberry":
            productType = "Berry";
            break;

        case "Cherry":
            productType = "Berry";
            break;

        case "Tomato":
            productType = "Vegetable";
            break;

        default:
            productType = "other";
            break;
    }

    return productType;
}

A switch statement is a set of constructs that will be executed one after another. It does not let you return a value. The main problem with using a switch statement is that you can add an infinite number of case expressions, and this ability is often abused by programmers.

Java 12 saw the addition of an experimental feature: a new version of switch — not a switch statement, but a switch expression — which can yield a value, use functional programming internally, and merge case statements that have a common value, thereby making the construct compact.

In Java 12, you can rewrite the getProductTypeByName() method as follows:

public String getProductTypeByName(String product) {
    return switch (product) {
        case "Apple", "Peach" -> "Fruit";
        case "Raspberry", "Cherry" -> "Berry";
        case "Tomato" -> "Vegetable";
        default -> "other";

    };
}

Now the code looks cleaner. The arrow syntax from functional programming lets us return values without the break keyword, and in general, the result of executing the switch can now be saved to a variable or returned via the return keyword.

If we need to not only return a result, but also have multiple lines of code, then our switch would look like this:

public String getProductTypeByName(String product) {
    var result = switch (product) {
        case "Apple", "Peach" -> {
            System.out.println("This is a Fruit");
            break "Fruit";
        }
        case "Raspberry", "Cherry" -> {
            System.out.println("This is a Berry");
            break "Berry";
        }
        case "Tomato" -> {
            System.out.println("This is a Vegetable");
            break "Vegetable";
        }
        default -> {
            break "other";
        }

    };
     return result;
}

In Java 13, the switch expression was still an experimental feature. To make it available, just as in Java 12, you need to use the --enable-preview command when compiling and running. The main, and essentially only, "innovation" of the switch in Java 13 is the yield keyword, which replaced break.

public String getProductTypeByName(String product) {
    var result = switch (product) {
        case "Apple", "Peach" -> {
            System.out.println("This is a Fruit");
            yield "Fruit";
        }
        case "Raspberry", "Cherry" -> {
            System.out.println("This is a Berry");
            yield "Berry";
        }
        case "Tomato" -> {
            System.out.println("This is a Vegetable");
            yield "Vegetables";
        }
        default -> {
            System.out.println("Other");
            yield "other";
        }

    };
    return result;
}

The main difference between yield and break is that break returns execution control from a case statement, but yield returns the result of the entire switch, acting like an internal return statement.

In Java 14, the instanceof operator has changed and can now be used like this:

if (o instanceof String s) {
s.toLowerCase();
}

Instead of the somewhat ugly old way where you had to not only check the variable using instanceof, but also cast it to a specific type.

if(s instanceof String) {
((String) s).toLowerCase();
}

These changes are part of the Amber project, which aims to add pattern matching support to Java.

Thanks to the change in the instanceof operator in version 14 and an extension in version 16, pattern matching did make it into version 17 after all. True, it exists only as a preview for now. You can try it out with --enable-preview:

public String getObjectType(Object object) {
    return switch (object) {
        case Integer i -> "Integer";
        case Long l -> "Long";
        case String s -> "String";
        default -> object.toString();
    };
}

In general, each new version brings more and more interesting features into the language, which makes Java development even cooler.