Modern Java Puzzlers Simon Ritter, Deputy CTO | Azul
2 Disclaimer • This is intended to be a fun session to make you think about how Java works and some of the unexpected things that can happen with modern Java features • It is not intended to be a critique of language feature design • I think the OpenJDK architects have done an unbelievable job of adding complex language features without breaking compatibility (or the feel of Java)
3 Modern Java • Project Amber has introduced a selection of new language features to Java • For this session, we'll be focusing on these: • Patterns • Switch expressions • Sealed classes
4 Puzzlers 1 Exhausted by Exhaustive
5 Exhausted By Exhaustive java: the switch expression does not cover all possible input values int x = getValue(); String d = switch (x) { case 0 -> "zero"; case 2, 4, 6 -> "even"; };
6 Exhausted By Exhaustive Yes, the switch expression is now exhaustive int x = getValue(); String d = switch (x) { case 0 -> "zero"; case 2, 4, 6 -> "even"; default -> "other"; };
7 Exhausted By Exhaustive Yes, a switch statement does not need to be exhaustive Imagine the code that would break if that was enforced retrospectively int x = getValue(); String d = null; switch (x) { case 0 -> d = "zero"; case 2, 4, 6 -> d = "even"; }
8 Exhausted By Exhaustive This is an enhanced switch statement (it includes a null) so must be exhaustive java: the switch statement does not cover all possible input values Integer x = getValue(); String d = null; switch (x) { case 0 -> d = "zero"; case 2, 4, 6 -> d = "even"; case null -> d ="null"; }
9 Exhausted By Exhaustive Yes, the switch expression is exhaustive public enum AB {A, B}; private String testAB(AB ab) { return switch(ab) { case A -> "A"; case B -> "B"; }; }
10 Exhausted By Exhaustive public enum AB {A,B}; private String testAB(AB ab) { switch(ab) { case A -> { return "A"; } case B -> { return "B"; } } } "Adding or reordering constants from an enum type will not break compatibility with pre-existing binaries." JLS 13.4.26 java: missing return statement
11 Exhausted By Exhaustive As with sealed classes, although adding an enum constant to an enum class is considered a binary compatible change, it may cause the execution of an exhaustive switch to fail if the switch encounters the new enum constant that was not known at compile time. public enum AB {A, B}; private String testAB(AB ab) { return switch(ab) { case A -> "A"; case B -> "B"; }; }
12 Puzzlers 2 Primitives In Patterns
13 Java Primitives v Objects • Primitives are not objects in Java (but arrays of primitives are) o Primitive variables store values, not references o Hence why we have wrapper classes o And autoboxing and unboxing • Since JDK 23, we can now include primitives in patterns (as a preview feature) o We can also mix and match primitives and wrapper types o How (and when) exhaustiveness is required can sometimes require careful consideration o Similarly for pattern dominance
14 Primitives In Switch Yes, the switch expression is exhaustive public void testInt(int n) { String r = switch(n) { case byte b -> "Byte"; case int i -> "Integer"; }; System.out.println(r); }
15 Primitives In Switch java: switch has both an unconditional pattern and a default label public void testInt(int n) { String r = switch(n) { case byte b -> "Byte"; case int i -> "Integer"; default -> "Other"; }; System.out.println(r); }
16 Primitives In Switch What is the result of testInt(1024); ? Integer public void testInt(int n) { String r = switch(n) { case byte b -> "Byte"; case int i -> "Integer"; }; System.out.println(r); }
17 Primitives In Switch What is the result of testInt(42); ? Byte public void testInt(int n) { String r = switch(n) { case byte b -> "Byte"; case int i -> "Integer"; }; System.out.println(r); }
18 Primitives In Switch What is the result of testInt((int)42); ? Byte public void testInt(int n) { String r = switch(n) { case byte b -> "Byte"; case int i -> "Integer"; }; System.out.println(r); }
19 Primitives In Switch What is the result of testInt(Integer.valueOf(42)); ? Byte public void testInt(int n) { String r = switch(n) { case byte b -> "Byte"; case int i -> "Integer"; }; System.out.println(r); }
20 Primitives In Switch java: this case label is dominated by a preceding case label public void testInt(int n) { String r = switch(n) { case int i -> "Integer"; case byte b -> "Byte"; }; System.out.println(r); } testInt(42);
21 Primitives With instanceof int x = 42; if (x instanceof int) System.out.println("int"); // prints int (quite logically) if (x instanceof byte) System.out.println("byte"); // prints byte (42 fits in a byte) // prints byte (42 still fits in a byte) if (x instanceof byte) System.out.println("byte"); else if (x instanceof int) System.out.println("int"); if (x instanceof int) System.out.println("int"); else if (x instanceof byte) System.out.println("byte");// prints int (first match, not best match)
22 Primitive And Reference Types In Switch public void testObj(Object o) { switch(o) { case byte b -> System.out.println("Byte"); case Integer i -> System.out.println("Integer"); default -> System.out.println("Something else"); }; } What is the result of testObj(Integer.valueOf(42)); ? Integer
23 Primitive And Reference Types In Switch public void testObj(Object o) { switch(o) { case byte b -> System.out.println("Byte"); case Integer i -> System.out.println("Integer"); default -> System.out.println("Something else"); }; } What is the result of testObj(42); ? Integer
24 Primitive And Reference Types In Switch public void testObj(Object o) { switch(o) { case byte b -> System.out.println("Byte"); case Integer i -> System.out.println("Integer"); default -> System.out.println("Something else"); }; } What is the result of: byte b = 42; testObj(b); ? Byte
25 Primitive And Reference Types In Switch public void testObj(Object o) { switch(o) { case Integer i -> System.out.println("Integer"); case byte b -> System.out.println("Byte"); default -> System.out.println("Something else"); }; } testObj(42): Integer
26 Primitive And Reference Types In Switch public void testObj(Object o) { switch(o) { case Integer i -> System.out.println("Integer"); case byte b -> System.out.println("Byte"); default -> System.out.println("Something else"); }; } byte bb = 42; testObj(bb): Byte
27 Primitive And Reference Types In Switch public void testObj(Object o) { switch(o) { case Integer i -> System.out.println("Integer"); case byte b -> System.out.println("Byte"); case int i -> System.out.println("int"); default -> System.out.println("Something else"); }; } java: this case label is dominated by a preceding case label
28 Primitive And Reference Types In Switch public void testObj(Object o) { switch(o) { case Integer i -> System.out.println("Integer"); case short s -> System.out.println("short"); case byte b -> System.out.println("Byte"); default -> System.out.println("Something else"); }; } java: this case label is dominated by a preceding case label
29 Primitive And Reference Types In Switch Compiles fine, all values print, Integer public void testInt() { int n = getValue(); switch(n) { case Integer i -> System.out.println("Integer"); case byte b -> System.out.println("Byte"); } }
30 Puzzlers 3 Guarded Patterns
31 Guards And Exhaustive The compiler does not evaluate the guard to determine exhaustiveness java: the switch expression does not cover all possible input values int x = getValue(); String d = switch (x) { case 0 -> "zero"; case int i when i < 0 -> d = "negative"; case int i when i > 0 -> d = "positive"; };
32 Guards And Pattern Dominance private void positiveOrNegative(int x) { String d = switch (x) { case Integer i when i > 0 -> "positive"; default -> "negative"; case 0 -> "zero"; }; System.out.println("Result is: " + d); } positiveOrNegative(42); positive
33 Guards And Pattern Dominance private void positiveOrNegative(int x) { String d = switch (x) { case Integer i when i > 0 -> "positive"; default -> "negative"; case 0 -> "zero"; }; System.out.println("Result is: " + d); } positiveOrNegative(-1); negative
34 Guards And Pattern Dominance private void positiveOrNegative(int x) { String d = switch (x) { case Integer i when i > 0 -> "positive"; default -> "negative"; case 0 -> "zero"; }; System.out.println("Result is: " + d); } positiveOrNegative(0); zero
35 Guards And Pattern Dominance private void positiveOrNegative(int x) { String d = switch (x) { case Integer i when i > 0 -> "positive"; default -> "negative"; case Integer i when i == 0 -> "zero"; }; System.out.println("Result is: " + d); } positiveOrNegative(0); java: this case label is dominated by a preceding case label
36 Guards And Pattern Dominance private void positiveOrNegative(int x) { String d = switch (x) { case Integer i when i > 0 -> "positive"; default -> "negative"; case 0 -> "zero"; }; System.out.println("Result is: " + d); } positiveOrNegative(0); zero Guarded pattern labels don't dominate constant labels What about changing when i > 0 when i == 0
37 Guards And Pattern Dominance private void positiveOrNegative(int x) { String d = switch (x) { case Integer i -> "positive"; default -> "negative"; case 0 -> "zero"; }; System.out.println("Result is: " + d); } positiveOrNegative(0); java: this case label is dominated by a preceding case label
38 Multiple Patterns With Guards enum Type { ISOSCELES, EQUILATERAL, RIGHT_ANGLE } record Triangle(Type t) {} record Square(double root) {} private void tryTwo(Object o) { String n = switch (o) { case Triangle(Type t) when t == Type.ISOSCELES -> "Found"; case Square(double r) when r > 20 -> "Found"; default -> "Lost"; }; }
39 Multiple Patterns With Guards private void tryTwo(Object o) { String n = switch (o) { case Triangle(Type t) when t == Type.ISOSCELES, Square(double r) when r > 20 -> "Found"; default -> "Lost"; }; } error: : or -> expected Guards are attached to cases, not patterns
40 Multiple Patterns private void tryTwo(Object o) { String n = switch (o) { case Triangle t -> "Found"; case Square s -> "Found"; default -> "Lost"; }; }
41 Multiple Patterns private void tryTwo(Object o) { String n = switch (o) { case Triangle t, Square s -> "Found"; default -> "Lost"; }; } error: illegal fall-through from a pattern (the current case label is missing a break) Invalid case label combination: multiple patterns are allowed only if none of them declare any pattern variables
42 Multiple Patterns private void tryTwo(Object o) { String n = switch (o) { case Triangle _, Square _ -> "Found"; default -> "Lost"; }; }
43 Puzzler 4 Sealed And Non-Sealed
44 Java Restricted Identifiers From JDK 16, these are now referred to as Reserved keywords abstract case continue else float import long private short synchronized transient true boolean catch default extends for instanceof native protected static this try false break char do final if int new public super throw void null byte class double finally implements interface package return switch throws volatile assert enum goto const strictfp _
45 Java Restricted Keywords requires requires; module { } exports exports to to; module opens opens; module-info.java
46 Java Reserved Type • JDK 10 introduced local variable type inference (var) • This is a reserved type (and restricted identifier) class var { ... } Possible (but not advised) until JDK 9 class Var { ... } var var = new Var();
47 Java Contextual Keywords (JDK 16) • Reclassified from restricted keywords • Only work as keywords when used in specific places o They can still be used as identifiers module requires opens exports opens to transitive uses provides with open var record yield when permits sealed non-sealed
48 When Is non-sealed, sealed? public sealed class Sealed permits NonSealed { } non-sealed class NonSealed extends Sealed { public NonSealed(){ int non = 4; int sealed = 2; System.out.printf("Class is " + (non-sealed)); // Prints "Class is 2" } } public void confused() { int non­ -sealed; non-sealed = 2; // No compiler error non-sealed = 3; // Compiler error }
49 When Is non-sealed, sealed? public sealed class Sealed permits NonSealed { } non-sealed class NonSealed extends Sealed { public NonSealed(){ int non = 4; int sealed = 2; System.out.printf("Class is " + (non-sealed)); // Prints "Class is 2" } } Unicode character u001d is a soft hyphen and classified as a identifier-ignorable character public void confused() { int non­ -sealed; non-sealed = 2; // No compiler error non-sealed = 3; // Cannot find symbol, symbol: variable non }
50 When Is non-sealed, sealed? public sealed class Sealed permits NonSealed { } non-sealed class NonSealed extends Sealed { public NonSealed(){ int non = 4; int sealed = 2; System.out.printf("Class is " + (non-sealed)); // Prints "Class is 2" } } public void confused() { int non­ -sealed; non-sealed = 2; // No compiler error int nonsealed = 3; // variable nonsealed is already defined in method confused() }
51 Puzzler 5 Comment without comment
52 Comment Without Comment public class EndOfFile { public EndOfFile() { ... } } interface EOFInterface { public int getValue(); } /* Enum */ enum EOFEnum { START, MIDDLE, END }; /* Record */ record EOFRecord(int i) {} /* Annotation */ @interface EOFAnnotation {} How to get the compiler to ignore EOFEnum, EOFRecord and EOFAnnotation by adding the minimum number of characters? (We cannot remove any characters)
53 Comment Without Comment public class EndOfFile { public EndOfFile() { ... } } interface EOFInterface { public int getValue(); } /* Enum */ /*enum EOFEnum { START, MIDDLE, END };*/ /* Record */ /*record EOFRecord(int i) {}*/ /* Annotation */ /*@interface EOFAnnotation {}*/ The block comment syntax requires 12 characters
54 Comment Without Comment public class EndOfFile { public EndOfFile() { ... } } interface EOFInterface { public int getValue(); } /* Enum */ //enum EOFEnum { // START, MIDDLE, END //}; /* Record */ //record EOFRecord(int i) {} /* Annotation */ //@interface EOFAnnotation {} Single line comments require 10 characters
55 Comment Without Comment public class EndOfFile { public EndOfFile() { ... } } interface EOFInterface { public int getValue(); } u001a/* Enum */ enum EOFEnum { START, MIDDLE, END }; /* Record */ record EOFRecord(int i) {} /* Annotation */ @interface EOFAnnotation {} u001a is the Unicode end-of-file character Prior to JDK 16, this would cause a compiler error Now, everything after u001a is ignored
Summary
57 Conclusions • Modern Java features add more power and less boilerplate to the language • Sometimes things don't behave quite how you would expect • Almost always, this is logical and well thought out • Your IDE will help you a lot
Thank you! @speakjava

Java Modern Puzzlers, a guide to new programming features

  • 1.
    Modern Java Puzzlers SimonRitter, Deputy CTO | Azul
  • 2.
    2 Disclaimer • This isintended to be a fun session to make you think about how Java works and some of the unexpected things that can happen with modern Java features • It is not intended to be a critique of language feature design • I think the OpenJDK architects have done an unbelievable job of adding complex language features without breaking compatibility (or the feel of Java)
  • 3.
    3 Modern Java • ProjectAmber has introduced a selection of new language features to Java • For this session, we'll be focusing on these: • Patterns • Switch expressions • Sealed classes
  • 4.
  • 5.
    5 Exhausted By Exhaustive java:the switch expression does not cover all possible input values int x = getValue(); String d = switch (x) { case 0 -> "zero"; case 2, 4, 6 -> "even"; };
  • 6.
    6 Exhausted By Exhaustive Yes,the switch expression is now exhaustive int x = getValue(); String d = switch (x) { case 0 -> "zero"; case 2, 4, 6 -> "even"; default -> "other"; };
  • 7.
    7 Exhausted By Exhaustive Yes,a switch statement does not need to be exhaustive Imagine the code that would break if that was enforced retrospectively int x = getValue(); String d = null; switch (x) { case 0 -> d = "zero"; case 2, 4, 6 -> d = "even"; }
  • 8.
    8 Exhausted By Exhaustive Thisis an enhanced switch statement (it includes a null) so must be exhaustive java: the switch statement does not cover all possible input values Integer x = getValue(); String d = null; switch (x) { case 0 -> d = "zero"; case 2, 4, 6 -> d = "even"; case null -> d ="null"; }
  • 9.
    9 Exhausted By Exhaustive Yes,the switch expression is exhaustive public enum AB {A, B}; private String testAB(AB ab) { return switch(ab) { case A -> "A"; case B -> "B"; }; }
  • 10.
    10 Exhausted By Exhaustive publicenum AB {A,B}; private String testAB(AB ab) { switch(ab) { case A -> { return "A"; } case B -> { return "B"; } } } "Adding or reordering constants from an enum type will not break compatibility with pre-existing binaries." JLS 13.4.26 java: missing return statement
  • 11.
    11 Exhausted By Exhaustive Aswith sealed classes, although adding an enum constant to an enum class is considered a binary compatible change, it may cause the execution of an exhaustive switch to fail if the switch encounters the new enum constant that was not known at compile time. public enum AB {A, B}; private String testAB(AB ab) { return switch(ab) { case A -> "A"; case B -> "B"; }; }
  • 12.
  • 13.
    13 Java Primitives vObjects • Primitives are not objects in Java (but arrays of primitives are) o Primitive variables store values, not references o Hence why we have wrapper classes o And autoboxing and unboxing • Since JDK 23, we can now include primitives in patterns (as a preview feature) o We can also mix and match primitives and wrapper types o How (and when) exhaustiveness is required can sometimes require careful consideration o Similarly for pattern dominance
  • 14.
    14 Primitives In Switch Yes,the switch expression is exhaustive public void testInt(int n) { String r = switch(n) { case byte b -> "Byte"; case int i -> "Integer"; }; System.out.println(r); }
  • 15.
    15 Primitives In Switch java:switch has both an unconditional pattern and a default label public void testInt(int n) { String r = switch(n) { case byte b -> "Byte"; case int i -> "Integer"; default -> "Other"; }; System.out.println(r); }
  • 16.
    16 Primitives In Switch Whatis the result of testInt(1024); ? Integer public void testInt(int n) { String r = switch(n) { case byte b -> "Byte"; case int i -> "Integer"; }; System.out.println(r); }
  • 17.
    17 Primitives In Switch Whatis the result of testInt(42); ? Byte public void testInt(int n) { String r = switch(n) { case byte b -> "Byte"; case int i -> "Integer"; }; System.out.println(r); }
  • 18.
    18 Primitives In Switch Whatis the result of testInt((int)42); ? Byte public void testInt(int n) { String r = switch(n) { case byte b -> "Byte"; case int i -> "Integer"; }; System.out.println(r); }
  • 19.
    19 Primitives In Switch Whatis the result of testInt(Integer.valueOf(42)); ? Byte public void testInt(int n) { String r = switch(n) { case byte b -> "Byte"; case int i -> "Integer"; }; System.out.println(r); }
  • 20.
    20 Primitives In Switch java:this case label is dominated by a preceding case label public void testInt(int n) { String r = switch(n) { case int i -> "Integer"; case byte b -> "Byte"; }; System.out.println(r); } testInt(42);
  • 21.
    21 Primitives With instanceof intx = 42; if (x instanceof int) System.out.println("int"); // prints int (quite logically) if (x instanceof byte) System.out.println("byte"); // prints byte (42 fits in a byte) // prints byte (42 still fits in a byte) if (x instanceof byte) System.out.println("byte"); else if (x instanceof int) System.out.println("int"); if (x instanceof int) System.out.println("int"); else if (x instanceof byte) System.out.println("byte");// prints int (first match, not best match)
  • 22.
    22 Primitive And ReferenceTypes In Switch public void testObj(Object o) { switch(o) { case byte b -> System.out.println("Byte"); case Integer i -> System.out.println("Integer"); default -> System.out.println("Something else"); }; } What is the result of testObj(Integer.valueOf(42)); ? Integer
  • 23.
    23 Primitive And ReferenceTypes In Switch public void testObj(Object o) { switch(o) { case byte b -> System.out.println("Byte"); case Integer i -> System.out.println("Integer"); default -> System.out.println("Something else"); }; } What is the result of testObj(42); ? Integer
  • 24.
    24 Primitive And ReferenceTypes In Switch public void testObj(Object o) { switch(o) { case byte b -> System.out.println("Byte"); case Integer i -> System.out.println("Integer"); default -> System.out.println("Something else"); }; } What is the result of: byte b = 42; testObj(b); ? Byte
  • 25.
    25 Primitive And ReferenceTypes In Switch public void testObj(Object o) { switch(o) { case Integer i -> System.out.println("Integer"); case byte b -> System.out.println("Byte"); default -> System.out.println("Something else"); }; } testObj(42): Integer
  • 26.
    26 Primitive And ReferenceTypes In Switch public void testObj(Object o) { switch(o) { case Integer i -> System.out.println("Integer"); case byte b -> System.out.println("Byte"); default -> System.out.println("Something else"); }; } byte bb = 42; testObj(bb): Byte
  • 27.
    27 Primitive And ReferenceTypes In Switch public void testObj(Object o) { switch(o) { case Integer i -> System.out.println("Integer"); case byte b -> System.out.println("Byte"); case int i -> System.out.println("int"); default -> System.out.println("Something else"); }; } java: this case label is dominated by a preceding case label
  • 28.
    28 Primitive And ReferenceTypes In Switch public void testObj(Object o) { switch(o) { case Integer i -> System.out.println("Integer"); case short s -> System.out.println("short"); case byte b -> System.out.println("Byte"); default -> System.out.println("Something else"); }; } java: this case label is dominated by a preceding case label
  • 29.
    29 Primitive And ReferenceTypes In Switch Compiles fine, all values print, Integer public void testInt() { int n = getValue(); switch(n) { case Integer i -> System.out.println("Integer"); case byte b -> System.out.println("Byte"); } }
  • 30.
  • 31.
    31 Guards And Exhaustive Thecompiler does not evaluate the guard to determine exhaustiveness java: the switch expression does not cover all possible input values int x = getValue(); String d = switch (x) { case 0 -> "zero"; case int i when i < 0 -> d = "negative"; case int i when i > 0 -> d = "positive"; };
  • 32.
    32 Guards And PatternDominance private void positiveOrNegative(int x) { String d = switch (x) { case Integer i when i > 0 -> "positive"; default -> "negative"; case 0 -> "zero"; }; System.out.println("Result is: " + d); } positiveOrNegative(42); positive
  • 33.
    33 Guards And PatternDominance private void positiveOrNegative(int x) { String d = switch (x) { case Integer i when i > 0 -> "positive"; default -> "negative"; case 0 -> "zero"; }; System.out.println("Result is: " + d); } positiveOrNegative(-1); negative
  • 34.
    34 Guards And PatternDominance private void positiveOrNegative(int x) { String d = switch (x) { case Integer i when i > 0 -> "positive"; default -> "negative"; case 0 -> "zero"; }; System.out.println("Result is: " + d); } positiveOrNegative(0); zero
  • 35.
    35 Guards And PatternDominance private void positiveOrNegative(int x) { String d = switch (x) { case Integer i when i > 0 -> "positive"; default -> "negative"; case Integer i when i == 0 -> "zero"; }; System.out.println("Result is: " + d); } positiveOrNegative(0); java: this case label is dominated by a preceding case label
  • 36.
    36 Guards And PatternDominance private void positiveOrNegative(int x) { String d = switch (x) { case Integer i when i > 0 -> "positive"; default -> "negative"; case 0 -> "zero"; }; System.out.println("Result is: " + d); } positiveOrNegative(0); zero Guarded pattern labels don't dominate constant labels What about changing when i > 0 when i == 0
  • 37.
    37 Guards And PatternDominance private void positiveOrNegative(int x) { String d = switch (x) { case Integer i -> "positive"; default -> "negative"; case 0 -> "zero"; }; System.out.println("Result is: " + d); } positiveOrNegative(0); java: this case label is dominated by a preceding case label
  • 38.
    38 Multiple Patterns WithGuards enum Type { ISOSCELES, EQUILATERAL, RIGHT_ANGLE } record Triangle(Type t) {} record Square(double root) {} private void tryTwo(Object o) { String n = switch (o) { case Triangle(Type t) when t == Type.ISOSCELES -> "Found"; case Square(double r) when r > 20 -> "Found"; default -> "Lost"; }; }
  • 39.
    39 Multiple Patterns WithGuards private void tryTwo(Object o) { String n = switch (o) { case Triangle(Type t) when t == Type.ISOSCELES, Square(double r) when r > 20 -> "Found"; default -> "Lost"; }; } error: : or -> expected Guards are attached to cases, not patterns
  • 40.
    40 Multiple Patterns private voidtryTwo(Object o) { String n = switch (o) { case Triangle t -> "Found"; case Square s -> "Found"; default -> "Lost"; }; }
  • 41.
    41 Multiple Patterns private voidtryTwo(Object o) { String n = switch (o) { case Triangle t, Square s -> "Found"; default -> "Lost"; }; } error: illegal fall-through from a pattern (the current case label is missing a break) Invalid case label combination: multiple patterns are allowed only if none of them declare any pattern variables
  • 42.
    42 Multiple Patterns private voidtryTwo(Object o) { String n = switch (o) { case Triangle _, Square _ -> "Found"; default -> "Lost"; }; }
  • 43.
  • 44.
    44 Java Restricted Identifiers FromJDK 16, these are now referred to as Reserved keywords abstract case continue else float import long private short synchronized transient true boolean catch default extends for instanceof native protected static this try false break char do final if int new public super throw void null byte class double finally implements interface package return switch throws volatile assert enum goto const strictfp _
  • 45.
    45 Java Restricted Keywords requires requires; module{ } exports exports to to; module opens opens; module-info.java
  • 46.
    46 Java Reserved Type •JDK 10 introduced local variable type inference (var) • This is a reserved type (and restricted identifier) class var { ... } Possible (but not advised) until JDK 9 class Var { ... } var var = new Var();
  • 47.
    47 Java Contextual Keywords(JDK 16) • Reclassified from restricted keywords • Only work as keywords when used in specific places o They can still be used as identifiers module requires opens exports opens to transitive uses provides with open var record yield when permits sealed non-sealed
  • 48.
    48 When Is non-sealed,sealed? public sealed class Sealed permits NonSealed { } non-sealed class NonSealed extends Sealed { public NonSealed(){ int non = 4; int sealed = 2; System.out.printf("Class is " + (non-sealed)); // Prints "Class is 2" } } public void confused() { int non­ -sealed; non-sealed = 2; // No compiler error non-sealed = 3; // Compiler error }
  • 49.
    49 When Is non-sealed,sealed? public sealed class Sealed permits NonSealed { } non-sealed class NonSealed extends Sealed { public NonSealed(){ int non = 4; int sealed = 2; System.out.printf("Class is " + (non-sealed)); // Prints "Class is 2" } } Unicode character u001d is a soft hyphen and classified as a identifier-ignorable character public void confused() { int non­ -sealed; non-sealed = 2; // No compiler error non-sealed = 3; // Cannot find symbol, symbol: variable non }
  • 50.
    50 When Is non-sealed,sealed? public sealed class Sealed permits NonSealed { } non-sealed class NonSealed extends Sealed { public NonSealed(){ int non = 4; int sealed = 2; System.out.printf("Class is " + (non-sealed)); // Prints "Class is 2" } } public void confused() { int non­ -sealed; non-sealed = 2; // No compiler error int nonsealed = 3; // variable nonsealed is already defined in method confused() }
  • 51.
  • 52.
    52 Comment Without Comment publicclass EndOfFile { public EndOfFile() { ... } } interface EOFInterface { public int getValue(); } /* Enum */ enum EOFEnum { START, MIDDLE, END }; /* Record */ record EOFRecord(int i) {} /* Annotation */ @interface EOFAnnotation {} How to get the compiler to ignore EOFEnum, EOFRecord and EOFAnnotation by adding the minimum number of characters? (We cannot remove any characters)
  • 53.
    53 Comment Without Comment publicclass EndOfFile { public EndOfFile() { ... } } interface EOFInterface { public int getValue(); } /* Enum */ /*enum EOFEnum { START, MIDDLE, END };*/ /* Record */ /*record EOFRecord(int i) {}*/ /* Annotation */ /*@interface EOFAnnotation {}*/ The block comment syntax requires 12 characters
  • 54.
    54 Comment Without Comment publicclass EndOfFile { public EndOfFile() { ... } } interface EOFInterface { public int getValue(); } /* Enum */ //enum EOFEnum { // START, MIDDLE, END //}; /* Record */ //record EOFRecord(int i) {} /* Annotation */ //@interface EOFAnnotation {} Single line comments require 10 characters
  • 55.
    55 Comment Without Comment publicclass EndOfFile { public EndOfFile() { ... } } interface EOFInterface { public int getValue(); } u001a/* Enum */ enum EOFEnum { START, MIDDLE, END }; /* Record */ record EOFRecord(int i) {} /* Annotation */ @interface EOFAnnotation {} u001a is the Unicode end-of-file character Prior to JDK 16, this would cause a compiler error Now, everything after u001a is ignored
  • 56.
  • 57.
    57 Conclusions • Modern Javafeatures add more power and less boilerplate to the language • Sometimes things don't behave quite how you would expect • Almost always, this is logical and well thought out • Your IDE will help you a lot
  • 58.