Friday, May 20, 2022

Bruce Eckel on switch expressions, arrow syntax, and case null

Oracle Java, Core Java, Oracle Java Certification, Java Preparation, Oracle Java Skill

The switch statement is constantly evolving. Here are three of the most significant recent improvements: the arrow syntax, the case null option, and switch expressions.

The switch statement is constantly evolving. Here are three of the most significant recent improvements: the arrow syntax, the case null option, and switch expressions.

Arrow syntax in switch

JDK 14 added the ability to use the new arrow syntax (->) for the case clauses in a switch. Below, colons() shows the old way, and arrows() shows the new way.

// enumerations/ArrowInSwitch.java

// {NewFeature} Since JDK 14

import static java.util.stream.IntStream.range;

public class ArrowInSwitch {

  static void colons(int i) {

    switch(i) {

      case 1: System.out.println("one");

              break;

      case 2: System.out.println("two");

              break;

      case 3: System.out.println("three");

              break;

      default: System.out.println("default");

    }

  }

  static void arrows(int i) {

    switch(i) {

      case 1 -> System.out.println("one");

      case 2 -> System.out.println("two");

      case 3 -> System.out.println("three");

      default -> System.out.println("default");

    }

  }

  public static void main(String[] args) {

    range(0, 4).forEach(i -> colons(i));

    range(0, 4).forEach(i -> arrows(i));

  }

}

/* Output:

default

one

two

three

default

one

two

three

*/

(Note: The {NewFeature} comment tag excludes this example from the Gradle build that uses JDK 8.)

In colons(), you see the need to add a break after each case (except the last one, default) to prevent fall-through. When you replace the colons with arrows in arrows(), the break statements are no longer needed. This is only part of what the arrow accomplishes, however.

You cannot mix colons and arrows within the same switch.

The case null clause in switch

JDK 17 adds the (preview) ability to include the previously illegal case null clause in a switch. Historically, you had to check for null cases outside the switch, as shown by old() in the code below.

In checkNull() you see that null is now a legitimate case for a switch using both arrow and colon syntax.

You might wonder whether default includes the null case. Well, defaultOnly() shows that default does not capture null, and without a case null you’ll get a NullPointerException. Why? Java says that a switch must cover all possible values even if it does not cover null. This is a backward-compatibility issue: If Java suddenly enforced null checking, a lot of existing code wouldn’t compile.

In general, multiple patterns can be combined into one case using commas. You can even combine null with another pattern, as shown below in combineNullAndCase(). Conveniently, you can combine case null with default, as seen in combineNullAndDefault().

// enumerations/CaseNull.java

// {NewFeature} Preview in JDK 17

// Compile with javac flags:

//   --enable-preview --source 17

// Run with java flag: --enable-preview

import java.util.*;

import java.util.function.*;

public class CaseNull {

  static void old(String s) {

    if(s == null) {

      System.out.println("null");

      return;

    }

    switch(s) {

      case "XX" -> System.out.println("XX");

      default   -> System.out.println("default");

    }

  }

  static void checkNull(String s) {

    switch(s) {

      case "XX" -> System.out.println("XX");

      case null -> System.out.println("null");

      default   -> System.out.println("default");

    }

    // Works with colon syntax, too:

    switch(s) {

      case "XX": System.out.println("XX");

                 break;

      case null: System.out.println("null");

                 break;

      default  : System.out.println("default");

    }

  }

  static void defaultOnly(String s) {

    switch(s) {

      case "XX" -> System.out.println("XX");

      default   -> System.out.println("default");

    }

  }

  static void combineNullAndCase(String s) {

    switch(s) {

      case "XX", null -> System.out.println("XX|null");

      default -> System.out.println("default");

    }

  }

  static void combineNullAndDefault(String s) {

    switch(s) {

      case "XX" -> System.out.println("XX");

      case null, default -> System.out.println("both");

    }

  }

  static void test(Consumer<String> cs) {

    cs.accept("XX");

    cs.accept("YY");

    try {

      cs.accept(null);

    } catch(NullPointerException e) {

      System.out.println(e.getMessage());

    }

  }

  public static void main(String[] args) {

    test(CaseNull::old);

    test(CaseNull::checkNull);

    test(CaseNull::defaultOnly);

    test(CaseNull::combineNullAndCase);

    test(CaseNull::combineNullAndDefault);

  }

}

/* Output:

XX

default

null

XX

XX

default

default

null

null

XX

default

Cannot invoke "String.hashCode()" because "<local1>" is null

XX|null

default

XX|null

XX

both

both

*/

Using switch as an expression

Historically, switch has always been a statement, and statements don’t generate a result. JDK 14 allows switch to also be an expression, and when used in this way, switch produces a result.

// enumerations/SwitchExpression.java

// {NewFeature} Since JDK 14

import java.util.*;

public class SwitchExpression {

  static int colon(String s) {

    var result = switch(s) {

      case "i": yield 1;

      case "j": yield 2;

      case "k": yield 3;

      default:  yield 0;

    };

    return result;

  }

  static int arrow(String s) {

    var result = switch(s) {

      case "i" -> 1;

      case "j" -> 2;

      case "k" -> 3;

      default  -> 0;

    };

    return result;

  }

  public static void main(String[] args) {

    for(var s: new String[]{"i", "j", "k", "z"})

      System.out.format(

        "%s %d %d%n", s, colon(s), arrow(s));

  }

}

/* Output:

i 1 1

j 2 2

k 3 3

z 0 0

*/

With the old-style colons, as seen above in colon(), you use the new yield keyword to return results from the switch. Notice that when you use yield, a break is not necessary. In fact, if you add a break you’ll get an error at compile time, flagging the attempt to break out of a switch expression.

If you try to use yield inside a switch statement, the compiler produces an error message that says you used yield outside of a switch expression.

In switch expressions, arrow() has the same effect as colon(), but the syntax can be cleaner, more compact, and more readable. For example, consider the following class that simulates a traffic light:

// enumerations/EnumSwitch.java

// {NewFeature} Since JDK 14

public class EnumSwitch {

  enum Signal { GREEN, YELLOW, RED, }

  Signal color = Signal.RED;

  public void change() {

    color = switch(color) {

      case RED -> Signal.GREEN;

      case GREEN -> Signal.YELLOW;

      case YELLOW -> Signal.RED;

    };

  }

}

If you add BLUE to enum Signal without adding a case BLUE, Java complains that the switch expression does not cover all possible input values. In that way, the compiler ensures that you don’t miss a case when you modify the code.

If a case requires multiple statements or expressions, put them inside a curly brace enclosed block, as in the following:

// enumerations/Planets.java

// {NewFeature} Since JDK 14

enum CelestialBody {

  MERCURY, VENUS, EARTH, MARS, JUPITER,

  SATURN, URANUS, NEPTUNE, PLUTO

}

public class Planets {

  public static String classify(CelestialBody b) {

    var result = switch(b) {

      case  MERCURY, VENUS, EARTH,

            MARS, JUPITER,

            SATURN, URANUS, NEPTUNE -> {

              System.out.print("A planet: ");

              yield b.toString();

            }

      case  PLUTO -> {

              System.out.print("Not a planet: ");

              yield b.toString();

            }

    };

    return result;

  }

  public static void main(String[] args) {

    System.out.println(classify(CelestialBody.MARS));

    System.out.println(classify(CelestialBody.PLUTO));

  }

}

/* Output:

A planet: MARS

Not a planet: PLUTO

*/

Note that when you’re producing a result from a multiline case expression, you need to use the yield keyword even though the arrow syntax is involved.

Source: oracle.com

Related Posts

0 comments:

Post a Comment