Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JUGNsk Meetup#8. Тагир Валеев: "Трансляция switch в Java-байткод"

JUGNsk Meetup#8. Тагир Валеев: "Трансляция switch в Java-байткод"

Мы посмотрим каким образом компилятор javac транслирует в Java-байткод различные версии оператора switch начиная с Java 1 и вплоть до Java 12, какие нетривиальные проблемы при этом возникают и как они отважно решаются. Доклад предназначен для тех, кому интересно внутреннее устройство Java, кто занимается или хочет заниматься манипуляциями с байткодом.

jugnsk

May 16, 2019
Tweet

More Decks by jugnsk

Other Decks in Programming

Transcript

  1. 0: iconst_1 1: istore_1 2: iload_1 3: bipush 100 5:

    if_icmpgt 73 8: iload_1 9: bipush 15 11: irem 12: ifne 26 15: getstatic #2 // System.out 18: ldc #3 20: invokevirtual #4 // PrintStream.println 23: goto 67 26: iload_1 27: iconst_3 28: irem 29: ifne 43 32: getstatic #2 // System.out 35: ldc #5 37: invokevirtual #4 // PrintStream.println 40: goto 67 43: iload_1 44: iconst_5 45: irem 46: ifne 60 49: getstatic #2 // System.out 52: ldc #6 54: invokevirtual #4 // PrintStream.println 57: goto 67 60: getstatic #2 // System.out 63: iload_1 64: invokevirtual #7 // PrintStream.println 67: iinc 1, 1 70: goto 2 73: return 2
  2. 0: iconst_1 1: istore_1 2: iload_1 3: bipush 100 5:

    if_icmpgt 73 8: iload_1 9: bipush 15 11: irem 12: ifne 26 15: getstatic #2 // System.out 18: ldc #3 20: invokevirtual #4 // PrintStream.println 23: goto 67 26: iload_1 27: iconst_3 28: irem 29: ifne 43 32: getstatic #2 // System.out 35: ldc #5 37: invokevirtual #4 // PrintStream.println 40: goto 67 43: iload_1 44: iconst_5 45: irem 46: ifne 60 49: getstatic #2 // System.out 52: ldc #6 54: invokevirtual #4 // PrintStream.println 57: goto 67 60: getstatic #2 // System.out 63: iload_1 64: invokevirtual #7 // PrintStream.println 67: iinc 1, 1 70: goto 2 73: return 3
  3. 0: iconst_1 1: istore_1 2: iload_1 3: bipush 100 5:

    if_icmpgt 73 8: iload_1 9: bipush 15 11: irem 12: ifne 26 15: getstatic #2 // System.out 18: ldc #3 // "FizzBuzz" 20: invokevirtual #4 // PrintStream.println 23: goto 67 26: iload_1 27: iconst_3 28: irem 29: ifne 43 32: getstatic #2 // System.out 35: ldc #5 // "Fizz" 37: invokevirtual #4 // PrintStream.println 40: goto 67 43: iload_1 44: iconst_5 45: irem 46: ifne 60 49: getstatic #2 // System.out 52: ldc #6 // "Buzz" 54: invokevirtual #4 // PrintStream.println 57: goto 67 60: getstatic #2 // System.out 63: iload_1 64: invokevirtual #7 // PrintStream.println 67: iinc 1, 1 70: goto 2 73: return 4
  4. public static void main(String[] args) { for (int i =

    1; i <= 100; i++) { if (i % 15 == 0) { System.out.println("FizzBuzz"); } else if (i % 3 == 0) { System.out.println("Fizz"); } else if (i % 5 == 0) { System.out.println("Buzz"); } else { System.out.println(i); } } } 5
  5. Разбираться в байткоде нужно если вы ✓ …разрабатываете виртуальную машину

    Java ✓ …пишете java-agent для инструментации кода ✓ …пишете анализатор покрытия кода ✓ …пишете или используете кодогенератор ✓ …создаёте альтернативный JVM-язык 6
  6. 7 Разбираться в байткоде нужно если вы ✓ …разрабатываете виртуальную

    машину Java ✓ …пишете java-agent для инструментации кода ✓ …пишете анализатор покрытия кода ✓ …пишете или используете кодогенератор ✓ …создаёте альтернативный JVM-язык ✓ …любопытный человек ☺
  7. static String bookWithNumber(int x) { switch (x) { case 1:

    return "Толстой. Пётр Первый"; case 2: return "Каверин. Два капитана"; case 3: return "Олеша. Три толстяка"; …; } } 9
  8. static String bookWithNumber(int x) { switch (x) { case 1:

    return "Толстой. Пётр Первый"; case 2: return "Каверин. Два капитана"; case 3: return "Олеша. Три толстяка"; default:return "Кащеев. Неправильное число"; } } 10
  9. public class Books { static String bookWithNumber(int x) { switch

    (x) { case 1: return "Толстой. Пётр Первый"; case 2: return "Каверин. Два капитана"; case 3: return "Олеша. Три толстяка"; default:return "Кащеев. Неправильное число"; } } public static void main(String[] args) { for (int i = 1; i <= 4; i++) { System.out.println(bookWithNumber(i)); } } } 11
  10. 12 $ javac Books.java $ java Books Толстой. Пётр Первый

    Каверин. Два капитана Олеша. Три толстяка Кащеев. Неправильное число
  11. 13 $ javac Books.java $ java Books Толстой. Пётр Первый

    Каверин. Два капитана Олеша. Три толстяка Кащеев. Неправильное число $ javap –c –p Books
  12. static String bookWithNumber(int); Code: 0: iload_0 1: tableswitch { //

    1 to 3 1: 28 2: 31 3: 34 default: 37 } 28: ldc #2 // Толстой. Пётр… 30: areturn 31: ldc #3 // Каверин. Два… 33: areturn 34: ldc #4 // Олеша. Три… 36: areturn 37: ldc #5 // Кащеев. Непра… 39: areturn 14 static String bookWithNumber(int x) { switch (x) { case 1: return "Толстой. Пётр Первый"; case 2: return "Каверин. Два капитана"; case 3: return "Олеша. Три толстяка"; default: return "Кащеев. Неправильное число"; } }
  13. static String bookWithNumber(int); Code: 0: iload_0 1: tableswitch { //

    1 to 3 1: 28 2: 31 3: 34 default: 37 } 28: ldc #2 // Толстой. Пётр… 30: areturn 31: ldc #3 // Каверин. Два… 33: areturn 34: ldc #4 // Олеша. Три… 36: areturn 37: ldc #5 // Кащеев. Непра… 39: areturn 15 Benedict Tableswitch
  14. struct tableswitch { int defaultOffset; int low; int high; int

    offsets[high-low+1]; } 1: tableswitch { // 1 to 3 1: 28 2: 31 3: 34 default: 37 } 16 JVM Specification: 6.5. tableswitch
  15. struct tableswitch { int defaultOffset; // = 37 int low;

    // = 1 int high; // = 3 int offsets[high-low+1]; // = {28,31,34} } 1: tableswitch { // 1 to 3 1: 28 2: 31 3: 34 default: 37 } 17
  16. struct tableswitch { int defaultOffset; // = 37 int low;

    // = 1 int high; // = 3 int offsets[high-low+1]; // = {28,31,34} } 1: tableswitch { // 1 to 3 1: 28 2: 31 3: 34 default: 37 } 18 — Где мои два байта?!
  17. static String bookWithNumber(int); Code: 0: iload_0 1: tableswitch { ???

    } 19 static String bookWithNumber(int x) { switch (x) { case 1: return "Толстой. Пётр Первый"; case 2: return "Каверин. Два капитана"; case 4: return "Конан Дойл. Знак четырёх"; default: return "Кащеев. Неправильное число"; } }
  18. static String bookWithNumber(int); Code: 0: iload_0 1: tableswitch { //

    1 to 4 1: 32 2: 35 3: 41 4: 38 default: 41 } 32: ldc #2 // Толстой. Пётр… 34: areturn 35: ldc #3 // Каверин. Два… 37: areturn 38: ldc #4 // Конан Дойл… 40: areturn 41: ldc #5 // Кащеев. Непра… 43: areturn 20 static String bookWithNumber(int x) { switch (x) { case 1: return "Толстой. Пётр Первый"; case 2: return "Каверин. Два капитана"; case 4: return "Конан Дойл. Знак четырёх"; case 3: default: return "Кащеев. Неправильное число"; } }
  19. static String bookWithNumber(int); Code: 0: iload_0 1: tableswitch { //

    1 to 5 1: 36 2: 39 3: 45 4: 45 5: 42 default: 45 } 36: ldc #2 // Толстой. Пётр… 38: areturn 39: ldc #3 // Каверин. Два… 41: areturn 42: ldc #4 // Дэвис. Пятый… 44: areturn 45: ldc #5 // Кащеев. Непра… 47: areturn 21 static String bookWithNumber(int x) { switch (x) { case 1: return "Толстой. Пётр Первый"; case 2: return "Каверин. Два капитана"; case 5: return "Дэвис. Пятый персонаж"; default: return "Кащеев. Неправильное число"; } }
  20. static String bookWithNumber(int); Code: 0: iload_0 1: lookupswitch { //

    3 1: 36 2: 39 6: 42 default: 45 } 36: ldc #2 // Толстой. Пётр… 38: areturn 39: ldc #3 // Каверин. Два… 41: areturn 42: ldc #4 // Чехов. Палата… 44: areturn 45: ldc #5 // Кащеев. Непра… 47: areturn 22 static String bookWithNumber(int x) { switch (x) { case 1: return "Толстой. Пётр Первый"; case 2: return "Каверин. Два капитана"; case 6: return "Чехов. Палата №6"; default: return "Кащеев. Неправильное число"; } }
  21. struct tableswitch { int defaultOffset; int low; int high; int

    offsets[high-low+1]; } 1: tableswitch { // 1 to 3 1: 28 2: 31 3: 34 default: 37 } 1: lookupswitch { // 3 1: 36 2: 39 6: 42 default: 45 } struct lookupswitch { int defaultOffset; int npairs; struct { int match; int offset; } pairs[npairs]; } 23 JVM Specification: 6.5. lookupswitch
  22. 24 // Determine whether to issue a tableswitch or a

    lookupswitch // instruction. long table_space_cost = 4 + ((long) hi - lo + 1); // words long table_time_cost = 3; // comparisons long lookup_space_cost = 3 + 2 * (long) nlabels; long lookup_time_cost = nlabels; int opcode = nlabels > 0 && table_space_cost + 3 * table_time_cost <= lookup_space_cost + 3 * lookup_time_cost ? tableswitch : lookupswitch; http://hg.openjdk.java.net/jdk/jdk/file/d230a0406623/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java#l1280
  23. enum Mousquetaire { Athos, Porthos, Aramis } 26 public class

    Dumas { static String title(Mousquetaire m) { switch (m) { case Athos: return "Три"; case Porthos: return " мушке"; case Aramis: return "тёра"; default: ??? } } }
  24. enum Mousquetaire { Athos, Porthos, Aramis } public class Dumas

    { static String title(Mousquetaire m) { switch (m) { case Athos: return "Три"; case Porthos: return " мушке"; case Aramis: return "тёра"; default: return "Бред"; } } public static void main(String[] args) { for (Mousquetaire m : Mousquetaire.values()) { System.out.print(title(m)); } } } 27
  25. enum Mousquetaire { Athos, Porthos, Aramis } public class Dumas

    { static String title(Mousquetaire m) { switch (m) { case Athos: return "Три"; case Porthos: return " мушке"; case Aramis: return "тёра"; default: return "Бред"; } } public static void main(String[] args) { for (Mousquetaire m : Mousquetaire.values()) { System.out.print(title(m)); } } } 28
  26. 29 enum Mousquetaire { Athos, Porthos, Aramis } public class

    Dumas { static String title(Mousquetaire m) { if (m == Mousquetaire.Athos) { return "Три"; } else if (m == Mousquetaire.Porthos) { return " мушке"; } else if (m == Mousquetaire.Aramis) { return "тёра"; } return "Бред"; } public static void main(String[] args) { for (Mousquetaire m : Mousquetaire.values()) { System.out.print(title(m)); } } }
  27. enum Mousquetaire { Richelieu, Porthos, Aramis } 30 if (m

    == Mousquetaire.Athos) { return "Три"; } else if (m == Mousquetaire.Porthos) { return " мушке"; } else if (m == Mousquetaire.Aramis) { return "тёра"; } return "Бред";
  28. Exception in thread "main" java.lang.NoSuchFieldError: Athos at Switched.swatch(Switched.java:3) at Switched.main(Switched.java:15)

    if-версия 31 enum Mousquetaire { Richelieu, Porthos, Aramis } if (m == Mousquetaire.Athos) { return "Три"; } else if (m == Mousquetaire.Porthos) { return " мушке"; } else if (m == Mousquetaire.Aramis) { return "тёра"; } return "Бред";
  29. 32 Exception in thread "main" java.lang.NoSuchFieldError: Athos at Switched.swatch(Switched.java:3) at

    Switched.main(Switched.java:15) if-версия enum Mousquetaire { Richelieu, Porthos, Aramis } switch (m) { case Athos: return "Три"; case Porthos: return " мушке"; case Aramis: return "тёра"; default: return "Бред"; }
  30. switch-версия 33 Exception in thread "main" java.lang.NoSuchFieldError: Athos at Switched.swatch(Switched.java:3)

    at Switched.main(Switched.java:15) Бред мушкетёра if-версия enum Mousquetaire { Richelieu, Porthos, Aramis } switch (m) { case Athos: return "Три"; case Porthos: return " мушке"; case Aramis: return "тёра"; default: return "Бред"; }
  31. String name = m.name(); if ("Athos".equals(name)) { return "Три"; }

    else if ("Porthos".equals(name)) { return " мушке"; } else if ("Aramis".equals(name)) { return "тёра"; } return "Бред"; 34 enum Mousquetaire { Richelieu, Porthos, Aramis } switch-версия Exception in thread "main" java.lang.NoSuchFieldError: Athos at Switched.swatch(Switched.java:3) at Switched.main(Switched.java:15) Бред мушкетёра if-версия
  32. int ord = m.ordinal(); switch (ord) { case 0: return

    "Три"; case 1: return " мушке"; case 2: return "тёра"; default: return "Бред"; } 35 enum Mousquetaire { Richelieu, Porthos, Aramis } switch-версия Exception in thread "main" java.lang.NoSuchFieldError: Athos at Switched.swatch(Switched.java:3) at Switched.main(Switched.java:15) Бред мушкетёра if-версия
  33. 36

  34. switch (m) { case Athos: return "Три"; case Porthos: return

    " мушке"; case Aramis: return "тёра"; default: return "Бред"; } 0: getstatic #2 // Dumas$1.$SwitchMap$Mousquetaire:[I 3: aload_0 4: invokevirtual #3 // Method Mousquetaire.ordinal:()I 7: iaload 8: tableswitch { // 1 to 3 1: 36 2: 39 3: 42 default: 45 } 36: ldc #4 // String Три 38: areturn 39: ldc #5 // String мушке 41: areturn 42: ldc #6 // String тёра 44: areturn 45: ldc #7 // String Бред 47: areturn 37
  35. switch (m) { case Athos: return "Три"; case Porthos: return

    " мушке"; case Aramis: return "тёра"; default: return "Бред"; } switch (Dumas$1.$SwitchMap$Mousquetaire[m.ordinal()]) { case 1: return "Три"; case 2: return " мушке"; case 3: return "тёра"; default: return "Бред"; } 38
  36. class Dumas$1 { static final int[] $SwitchMap$Mousquetaire; static {}; Code:

    0: invokestatic #1 // Method Mousquetaire.values:()[LMousquetaire; 3: arraylength 4: newarray int 6: putstatic #2 // Field $SwitchMap$Mousquetaire:[I 9: getstatic #2 // Field $SwitchMap$Mousquetaire:[I 12: getstatic #3 // Field Mousquetaire.Athos:LMousquetaire; 15: invokevirtual #4 // Method Mousquetaire.ordinal:()I 18: iconst_1 … (что-то много байткода) 54: return Exception table: from to target type 9 20 23 Class java/lang/NoSuchFieldError 24 35 38 Class java/lang/NoSuchFieldError 39 50 53 Class java/lang/NoSuchFieldError } 39
  37. class Dumas$1 { static final int[] $SwitchMap$Mousquetaire; static { $SwitchMap$Mousquetaire

    = new int[Mousquetaire.values().length]; $SwitchMap$Mousquetaire[Mousquetaire.Athos.ordinal()] = 1; $SwitchMap$Mousquetaire[Mousquetaire.Porthos.ordinal()] = 2; $SwitchMap$Mousquetaire[Mousquetaire.Aramis.ordinal()] = 3; } } 40
  38. class Dumas$1 { static final int[] $SwitchMap$Mousquetaire; static { $SwitchMap$Mousquetaire

    = new int[Mousquetaire.values().length]; try { $SwitchMap$Mousquetaire[Mousquetaire.Athos.ordinal()] = 1; } catch (NoSuchFieldError ignored) { } try { $SwitchMap$Mousquetaire[Mousquetaire.Porthos.ordinal()] = 2; } catch (NoSuchFieldError ignored) { } try { $SwitchMap$Mousquetaire[Mousquetaire.Aramis.ordinal()] = 3; } catch (NoSuchFieldError ignored) { } } } 41
  39. class Dumas$1 { static final int[] $SwitchMap$Mousquetaire; static { $SwitchMap$Mousquetaire

    = new int[Mousquetaire.values().length]; try { $SwitchMap$Mousquetaire[Mousquetaire.Athos.ordinal()] = 1; } catch (NoSuchFieldError ignored) { } try { $SwitchMap$Mousquetaire[Mousquetaire.Porthos.ordinal()] = 2; } catch (NoSuchFieldError ignored) { } try { $SwitchMap$Mousquetaire[Mousquetaire.Aramis.ordinal()] = 3; } catch (NoSuchFieldError ignored) { } } } 42 Initialization-on-demand holder idiom
  40. 44 public class Dumas { private static int[] $SWITCH_TABLE$Mousquetaire; …

    (наши методы) static int[] $SWITCH_TABLE$Mousquetaire(); Code: 0: getstatic #51 // Field $SWITCH_TABLE$Mousquetaire:[I 3: dup 4: ifnull 8 7: areturn 8: pop 9: invokestatic #33 // Method Mousquetaire.values:()[LMousquetaire; 12: arraylength 13: newarray int 15: astore_0 16: aload_0 … (что-то много байткода) 60: areturn Exception table: from to target type 16 25 28 Class java/lang/NoSuchFieldError 29 38 41 Class java/lang/NoSuchFieldError 42 51 54 Class java/lang/NoSuchFieldError }
  41. 45 private static int[] $SWITCH_TABLE$Mousquetaire; static int[] $SWITCH_TABLE$Mousquetaire() { int[]

    tmp = $SWITCH_TABLE$Mousquetaire; if (tmp != null) { return tmp; } tmp = new int[Mousquetaire.values().length]; try { tmp[Mousquetaire.Aramis.ordinal()] = 3; } catch (NoSuchFieldError ignored) { } try { tmp[Mousquetaire.Athos.ordinal()] = 1; } catch (NoSuchFieldError ignored) { } try { tmp[Mousquetaire. Porthos.ordinal()] = 2; } catch (NoSuchFieldError ignored) { } return $SWITCH_TABLE$Mousquetaire = tmp; } static String title(Mousquetaire m) { switch ($SWITCH_TABLE$Mousquetaire() [m.ordinal()]) { … } }
  42. 46 private static int[] $SWITCH_TABLE$Mousquetaire; static int[] $SWITCH_TABLE$Mousquetaire() { int[]

    tmp = $SWITCH_TABLE$Mousquetaire; if (tmp != null) { return tmp; } tmp = new int[Mousquetaire.values().length]; try { tmp[Mousquetaire.Aramis.ordinal()] = 3; } catch (NoSuchFieldError ignored) { } try { tmp[Mousquetaire.Athos.ordinal()] = 1; } catch (NoSuchFieldError ignored) { } try { tmp[Mousquetaire. Porthos.ordinal()] = 2; } catch (NoSuchFieldError ignored) { } return $SWITCH_TABLE$Mousquetaire = tmp; } Порядок видимости в другом потоке не гарантирован!
  43. 47 private static int[] $SWITCH_TABLE$Mousquetaire; static int[] $SWITCH_TABLE$Mousquetaire() { int[]

    tmp = $SWITCH_TABLE$Mousquetaire; if (tmp != null) { return tmp; } tmp = new int[Mousquetaire.values().length]; try { tmp[Mousquetaire.Aramis.ordinal()] = 3; } catch (NoSuchFieldError ignored) { } try { tmp[Mousquetaire.Athos.ordinal()] = 1; } catch (NoSuchFieldError ignored) { } try { tmp[Mousquetaire. Porthos.ordinal()] = 2; } catch (NoSuchFieldError ignored) { } return $SWITCH_TABLE$Mousquetaire = tmp; } Порядок видимости в другом потоке не гарантирован! Unsafe publication
  44. 48 Bug 544521 - Enum switch lookup table is not

    safely published, according to Java Memory Model
  45. 49 class Test { enum A{ X; } enum B{

    Y; } static void testA(A a) { switch(a) {} } static void testB(B b) { switch(b) {} } }
  46. 50 class Test { enum A{ X; } enum B{

    Y; } static void testA(A a) { switch(a) {} } static void testB(B b) { switch(b) {} } }
  47. 51 class Test$1 { static final int[] $SwitchMap$Test$A; static final

    int[] $SwitchMap$Test$B; static {}; Code: 0: invokestatic #1 // Method Test$B.values:()[LTest$B; 3: arraylength 4: newarray int 6: putstatic #2 // Field $SwitchMap$Test$B:[I 9: invokestatic #3 // Method Test$A.values:()[LTest$A; 12: arraylength 13: newarray int 15: putstatic #4 // Field $SwitchMap$Test$A:[I 18: return }
  48. 52 class Test$1 { static final int[] $SwitchMap$Test$A; static final

    int[] $SwitchMap$Test$B; static { $SwitchMap$Test$B = new int[Test.B.values().length]; $SwitchMap$Test$A = new int[Test.A.values().length]; } }
  49. 53 class Test { enum A{ X; static { System.out.println("A

    is initialized!"); } } enum B{ Y; static { System.out.println("B is initialized!"); } } static void testA(A a) { switch(a) {} } static void testB(B b) { switch(b) {} } public static void main(String[] args) { testA(A.X); } }
  50. 54 class Test { enum A{ X; static { System.out.println("A

    is initialized!"); } } enum B{ Y; static { System.out.println("B is initialized!"); } } static void testA(A a) { switch(a) {} } static void testB(B b) { switch(b) {} } public static void main(String[] args) { testA(A.X); } } $ java Test A is initialized! B is initialized!
  51. 55 12.4.1. When Initialization Occurs A class or interface type

    T will be initialized immediately before the first occurrence of any one of the following: • T is a class and an instance of T is created. • A static method declared by T is invoked. • A static field declared by T is assigned. • A static field declared by T is used and the field is not a constant variable (§4.12.4). When a class is initialized, its superclasses are initialized (if they have not been previously initialized), as well as any superinterfaces (§8.1.5) that declare any default methods (§9.4.3) (if they have not been previously initialized). Initialization of an interface does not, of itself, cause initialization of any of its superinterfaces. A reference to a static field (§8.3.1.1) causes initialization of only the class or interface that actually declares it, even though it might be referred to through the name of a subclass, a subinterface, or a class that implements an interface. Invocation of certain reflective methods in class Class and in package java.lang.reflect also causes class or interface initialization. A class or interface will not be initialized under any other circumstance.
  52. 56 12.4.1. When Initialization Occurs A class or interface type

    T will be initialized immediately before the first occurrence of any one of the following: • T is a class and an instance of T is created. • A static method declared by T is invoked. • A static field declared by T is assigned. • A static field declared by T is used and the field is not a constant variable (§4.12.4). When a class is initialized, its superclasses are initialized (if they have not been previously initialized), as well as any superinterfaces (§8.1.5) that declare any default methods (§9.4.3) (if they have not been previously initialized). Initialization of an interface does not, of itself, cause initialization of any of its superinterfaces. A reference to a static field (§8.3.1.1) causes initialization of only the class or interface that actually declares it, even though it might be referred to through the name of a subclass, a subinterface, or a class that implements an interface. Invocation of certain reflective methods in class Class and in package java.lang.reflect also causes class or interface initialization. A class or interface will not be initialized under any other circumstance.
  53. 61 enum DeathCause {AXE, TRAIN} enum Story { ANNA_KARENINA(DeathCause.TRAIN), CRIME_AND_PUNISHMENT(DeathCause.AXE);

    Story(DeathCause cause) { this.cause = cause; switch (cause) { case AXE: this.quote = "Раскольников проснулся и сладко потянулся за топором"; break; case TRAIN: this.quote = "Анна бросилась под поезд, и он долго влачил ее жалкое существование"; break; } } final DeathCause cause; String quote; }
  54. 62 enum DeathCause {AXE, TRAIN} enum Story { ANNA_KARENINA(DeathCause.TRAIN), CRIME_AND_PUNISHMENT(DeathCause.AXE);

    Story(DeathCause cause) { this.cause = cause; switch (cause) {…} } final DeathCause cause; String quote; } public static void main(String[] args) { System.out.println(Story.ANNA_KARENINA.quote); }
  55. 63 enum DeathCause {AXE, TRAIN} enum Story { ANNA_KARENINA(DeathCause.TRAIN), CRIME_AND_PUNISHMENT(DeathCause.AXE);

    Story(DeathCause cause) { this.cause = cause; switch (cause) {…} } final DeathCause cause; String quote; } public static void main(String[] args) { System.out.println(Story.ANNA_KARENINA.quote); }
  56. 64 enum DeathCause {AXE, TRAIN} enum Story { ANNA_KARENINA(DeathCause.TRAIN), CRIME_AND_PUNISHMENT(DeathCause.AXE);

    Story(DeathCause cause) { this.cause = cause; switch (cause) {…} } final DeathCause cause; String quote; boolean isSadStory() { switch (this) { case ANNA_KARENINA: ??? } } }
  57. 65 enum DeathCause {AXE, TRAIN} enum Story { ANNA_KARENINA(DeathCause.TRAIN), CRIME_AND_PUNISHMENT(DeathCause.AXE);

    Story(DeathCause cause) { this.cause = cause; switch (cause) {…} } final DeathCause cause; String quote; boolean isSadStory() { switch (this) { case ANNA_KARENINA: return true; case CRIME_AND_PUNISHMENT: ??? } } }
  58. 66 enum DeathCause {AXE, TRAIN} enum Story { ANNA_KARENINA(DeathCause.TRAIN), CRIME_AND_PUNISHMENT(DeathCause.AXE);

    Story(DeathCause cause) { this.cause = cause; switch (cause) {…} } final DeathCause cause; String quote; boolean isSadStory() { switch (this) { case ANNA_KARENINA: return true; case CRIME_AND_PUNISHMENT: return true; default: return true; } } }
  59. 67 enum DeathCause {AXE, TRAIN} enum Story { ANNA_KARENINA(DeathCause.TRAIN), CRIME_AND_PUNISHMENT(DeathCause.AXE);

    Story(DeathCause cause) { this.cause = cause; switch (cause) {…} } final DeathCause cause; String quote; boolean isSadStory() { switch (this) { case ANNA_KARENINA: return true; case CRIME_AND_PUNISHMENT: return true; default: return true; } } }
  60. 68 enum DeathCause {AXE, TRAIN} enum Story { ANNA_KARENINA(DeathCause.TRAIN), CRIME_AND_PUNISHMENT(DeathCause.AXE);

    Story(DeathCause cause) { this.cause = cause; switch (cause) {…} } final DeathCause cause; String quote; boolean isSadStory() { switch (this) { case ANNA_KARENINA: return true; case CRIME_AND_PUNISHMENT: return true; default: return true; } } }
  61. 71 static String quote(String author) { switch (author) { case

    "Некрасов": return "Однажды в студёную зимнюю пору"; case "Пушкин": return "Сижу за решёткой в темнице сырой"; default: throw new IllegalArgumentException("Неизвестный автор"); } }
  62. 72 public int hashCode() Returns a hash code for this

    string. The hash code for a String object is computed as s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] using int arithmetic, where s[i] is the ith character of the string, n is the length of the string, and ^ indicates exponentiation. (The hash value of the empty string is zero.) Overrides: hashCode in class Object Returns: a hash code value for this object. https://docs.oracle.com/javase/10/docs/api/java/lang/String.html#hashCode()
  63. 73 switch (author.hashCode()) { case 1241783171: // "Некрасов".hashCode() return "Однажды

    в студёную зимнюю пору"; case 1180269947: // "Пушкин".hashCode() return "Сижу за решёткой в темнице сырой"; default: throw new IllegalArgumentException("Неизвестный автор"); }
  64. 74 switch (author.hashCode()) { case 1241783171: // "Некрасов".hashCode() if ("Некрасов".equals(author))

    { return "Однажды в студёную зимнюю пору"; } break; case 1180269947: // "Пушкин".hashCode() if ("Пушкин".equals(author)) { return "Сижу за решёткой в темнице сырой"; } break; } throw new IllegalArgumentException("Неизвестный автор");
  65. 75 static int getBookRating(String bookName) { switch (bookName) { case

    "Куртки на управителя": return 1; case "Лепетали и снилась": return 2; case "Обменялась на неустойчивые": return 3; case "Тупогубый валил": return 4; default: return -1; } }
  66. 76 static int getBookRating(String bookName) { switch (bookName.hashCode()) { case

    0xDEAD_BEEF: if ("Куртки на управителя".equals(bookName)) { return 1; } else if ("Лепетали и снилась".equals(bookName)) { return 2; } else if ("Обменялась на неустойчивые".equals(bookName)) { return 3; } else if ("Тупогубый валил".equals(bookName)) { return 4; } break; } return -1; }
  67. 77 static int getBookRating(String bookName) { switch (bookName) { case

    "Куртки на управителя": case "Наука под меткими": return 1; case "Лепетали и снилась": case "Славянофил усугублял": return 2; case "Обменялась на неустойчивые": case "Увитые или уголовные": return 3; case "Тупогубый валил": case "Хоромы у посещения": return 4; default: return -1; } }
  68. 78 static int getBookRating(String bookName) { switch (bookName.hashCode()) { case

    0xDEAD_BEEF: if ("Куртки на управителя".equals(bookName)) return 1; else if ("Лепетали и снилась".equals(bookName)) return 2; else if ("Обменялась на неустойчивые".equals(bookName)) return 3; else if ("Тупогубый валил".equals(bookName)) return 4; break; case 0xCAFE_BABE: if ("Наука под меткими".equals(bookName)) return 1; else if ("Славянофил усугублял".equals(bookName)) return 2; else if ("Увитые или уголовные".equals(bookName)) return 3; else if ("Хоромы у посещения".equals(bookName)) return 4; break; } return -1; }
  69. 79 static int getBookRating(String bookName) { int $tmp = -1;

    switch (bookName.hashCode()) { case 0xDEAD_BEEF: if ("Куртки на управителя".equals(bookName)) $tmp = 6; else if ("Лепетали и снилась".equals(bookName)) $tmp = 4; else if ("Обменялась на неустойчивые".equals(bookName)) $tmp = 2; else if ("Тупогубый валил".equals(bookName)) $tmp = 0; break; case 0xCAFE_BABE: if ("Наука под меткими".equals(bookName)) $tmp = 7; else if ("Славянофил усугублял".equals(bookName)) $tmp = 5; else if ("Увитые или уголовные".equals(bookName)) $tmp = 3; else if ("Хоромы у посещения".equals(bookName)) $tmp = 1; break; } switch ($tmp) { case 0: case 1: return 1; case 2: case 3: return 2; case 4: case 5: return 3; case 6: case 7: return 4; default: return -1; } }
  70. 80 * With some additional effort, it would be possible

    to * use a single switch statement on the hash code of the * argument, but care would need to be taken to preserve * the proper control flow in the presence of hash * collisions and other complications, such as * fallthroughs. Switch statements with one or two * alternatives could also be specially translated into * if-then statements to omit the computation of the hash * code. http://hg.openjdk.java.net/jdk/jdk/file/d230a0406623/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java#l3505
  71. 81 static int getBookRating(String bookName) { switch (bookName.hashCode()) { case

    0xDEAD_BEEF: if ("Куртки на управителя".equals(bookName)) goto L1; else if ("Лепетали и снилась".equals(bookName)) goto L2; else if ("Обменялась на неустойчивые".equals(bookName)) goto L3; else if ("Тупогубый валил".equals(bookName)) goto L4; goto L5; case 0xCAFE_BABE: if ("Наука под меткими".equals(bookName)) goto L1; else if ("Славянофил усугублял".equals(bookName)) goto L2; else if ("Увитые или уголовные".equals(bookName)) goto L3; else if ("Хоромы у посещения".equals(bookName)) goto L4; default: goto L5; } L1: return 1; L2: return 2; L3: return 3; L4: return 4; L5: return -1; } Eclipse Compiler for Java (ecj-4.10.jar)
  72. 84 $ javac --release 12 --enable-preview *.java $ java MyClass

    Error: LinkageError occurred while loading main class MyClass java.lang.UnsupportedClassVersionError: Preview features are not enabled for MyClass (class file version 56.65535). Try running with '--enable-preview’ $ java --enable-preview MyClass
  73. 85 static void bookOfTheDay(DayOfWeek day) { switch (day) { case

    TUESDAY -> System.out.println("Вторники в замке"); case WEDNESDAY -> System.out.println("Фиалки по средам"); case THURSDAY -> System.out.println("Человек, который был Четвергом"); case FRIDAY -> System.out.println("Пятница, или Тихоокеанский лимб"); case SATURDAY, MONDAY -> { System.out.println("Понедельник"); System.out.println("начинается"); System.out.println("в субботу"); } case SUNDAY -> System.out.println("Воскресная месса в Толедо"); } }
  74. 86 static void bookOfTheDay(DayOfWeek day) { System.out.println(switch (day) { case

    TUESDAY -> "Вторники в замке"; case WEDNESDAY -> "Фиалки по средам"; case THURSDAY -> "Человек, который был Четвергом"; case FRIDAY -> "Пятница, или Тихоокеанский лимб"; case SATURDAY, MONDAY -> "Понедельник начинается в субботу"; case SUNDAY -> "Воскресная месса в Толедо"; }); }
  75. 87 static void bookOfTheDay(DayOfWeek day) { System.out.println(switch (day) { case

    TUESDAY -> "Вторники в замке"; case WEDNESDAY -> "Фиалки по средам"; case THURSDAY -> "Человек, который был Четвергом"; case FRIDAY -> "Пятница, или Тихоокеанский лимб"; case SATURDAY, MONDAY -> "Понедельник начинается в субботу"; case SUNDAY -> "Воскресная месса в Толедо"; default -> throw new IncompatibleClassChangeError(); }); } 88: new #11 // class IncompatibleClassChangeError 91: dup 92: invokespecial #12 // Method IncompatibleClassChangeError."<init>" 95: athrow
  76. 88 static void bookOfTheDay(DayOfWeek day) throws InterruptedException { System.out.println(switch (day)

    { case TUESDAY -> "Вторники в замке"; case WEDNESDAY -> "Фиалки по средам"; case THURSDAY -> "Человек, который был Четвергом"; case FRIDAY -> "Пятница, или Тихоокеанский лимб"; case SATURDAY, MONDAY -> { Thread.sleep(TimeUnit.HOURS.toMillis(8)); break "Понедельник начинается в субботу"; } case SUNDAY -> "Воскресная месса в Толедо"; }); }
  77. 89 static void bookOfTheDay(DayOfWeek day) { System.out.println(switch (day) { case

    TUESDAY -> "Вторники в замке"; case WEDNESDAY -> "Фиалки по средам"; case THURSDAY -> "Человек, который был Четвергом"; case FRIDAY -> "Пятница, или Тихоокеанский лимб"; case SATURDAY, MONDAY -> { try { Thread.sleep(TimeUnit.HOURS.toMillis(8)); } catch (InterruptedException e) { break "Подушку верните и убирайтесь вон. Сигареты на столе."; } break "Понедельник начинается в субботу"; } case SUNDAY -> "Воскресная месса в Толедо"; }); }
  78. 90 case SATURDAY: case MONDAY: try { Thread.sleep(TimeUnit.HOURS.toMillis(8)); } catch

    (InterruptedException e) { System.out.println("Подушку верните и убирайтесь вон. Сигареты на столе."); break; } System.out.println("Понедельник начинается в субботу"); break;
  79. case SATURDAY: case MONDAY: try { Thread.sleep(TimeUnit.HOURS.toMillis(8)); } catch (InterruptedException

    e) { System.out.println("Подушку верните и убирайтесь вон. Сигареты на столе."); break; } System.out.println("Понедельник начинается в субботу"); break; 91 try { 96: getstatic #10 // Field TimeUnit.HOURS 99: ldc2_w #11 // long 8l 102: invokevirtual #13 // Method TimeUnit.toMillis 105: invokestatic #14 // Method Thread.sleep 108: goto 123 } catch {111: astore_1 // сохранили исключение 112: getstatic #4 // Field System.out 115: ldc #16 // String Подушку верните и… 117: invokevirtual #6 // Method PrintStream.println 120: goto 153 // break; } 123: getstatic #4 // Field System.out 126: ldc #17 // String Понедельник начинается… 128: invokevirtual #6 // Method PrintStream.println 131: return Exception table: from to target type 96 108 111 Class java/lang/InterruptedException
  80. 92 static void bookOfTheDay(DayOfWeek day) { System.out.println(switch (day) { case

    TUESDAY -> "Вторники в замке"; case WEDNESDAY -> "Фиалки по средам"; case THURSDAY -> "Человек, который был Четвергом"; case FRIDAY -> "Пятница, или Тихоокеанский лимб"; case SATURDAY, MONDAY -> { try { Thread.sleep(TimeUnit.HOURS.toMillis(8)); } catch (InterruptedException e) { break "Подушку верните и убирайтесь вон. Сигареты на столе."; } break "Понедельник начинается в субботу"; } case SUNDAY -> "Воскресная месса в Толедо"; }); }
  81. 93 Error: Unable to initialize main class SwTest Caused by:

    java.lang.VerifyError: Stack map does not match the one at exception handler 95 Exception Details: Location: SwTest.bookOfTheDay(Ljava/time/DayOfWeek;)V @82: getstatic Reason: Current frame's stack size doesn't match stackmap. Current Frame: bci: @82 flags: { } locals: { 'java/time/DayOfWeek' } stack: { 'java/lang/InterruptedException' } Stackmap Frame: bci: @95 flags: { } locals: { 'java/time/DayOfWeek' } stack: { 'java/io/PrintStream', 'java/lang/InterruptedException' } Bytecode: 0000000: b200 02b2 0003 2ab6 0004 2eaa 0000 0061 0000010: 0000 0001 0000 0007 0000 0029 0000 002f 0000020: 0000 0035 0000 003b 0000 0041 0000 0047 0000030: 0000 0047 1205 4ca7 003d 1206 4ca7 0037 0000040: 1207 4ca7 0031 1208 4ca7 002b 1209 4ca7 0000050: 0025 b200 0a0a b600 0bb8 000c a700 0a4d 0000060: 120e 4ca7 0011 120f 4ca7 000b bb00 1059 0000070: b700 11bf 2bb6 0012 b1
  82. 95 0: getstatic #2 // Field System.out 3: astore_1 4:

    getstatic #3 // Field SwTest$1.$SwitchMap$java$time$DayOfWeek 7: aload_0 8: invokevirtual #4 // Method DayOfWeek.ordinal:()I 11: iaload 12: tableswitch { 1: 56; 2: 62; 3: 68; 4: 74; 5: 80; 6: 80; 7: 108; default: 114 } 56: aload_1 57: ldc #5 // String Вторники в замке 59: goto 122 … 80: getstatic #9 // Field TimeUnit.HOURS 83: ldc2_w #10 // long 8l 86: invokevirtual #12 // Method TimeUnit.toMillis 89: invokestatic #13 // Method Thread.sleep 96: goto 102 95: astore_2 96: aload_1 97: ldc #15 // String Подушку верните и убирайтесь вон. Сигареты на столе. 99: goto 122 102: aload_1 103: ldc #16 // String Понедельник начинается в субботу 105: goto 122 … 114: new #18 // class IncompatibleClassChangeError
  83. 96 0: getstatic #2 // Field System.out 3: astore_1 4:

    getstatic #3 // Field SwTest$1.$SwitchMap$java$time$DayOfWeek 7: aload_0 8: invokevirtual #4 // Method DayOfWeek.ordinal:()I 11: iaload 12: tableswitch { 1: 56; 2: 62; 3: 68; 4: 74; 5: 80; 6: 80; 7: 108; default: 114 } 56: aload_1 57: ldc #5 // String Вторники в замке 59: goto 122 … 80: getstatic #9 // Field TimeUnit.HOURS 83: ldc2_w #10 // long 8l 86: invokevirtual #12 // Method TimeUnit.toMillis 89: invokestatic #13 // Method Thread.sleep 96: goto 102 95: astore_2 96: aload_1 97: ldc #15 // String Подушку верните и убирайтесь вон. Сигареты на столе. 99: goto 122 102: aload_1 103: ldc #16 // String Понедельник начинается в субботу 105: goto 122 … 114: new #18 // class IncompatibleClassChangeError
  84. 97 0: getstatic #2 // Field System.out 3: astore_1 4:

    getstatic #3 // Field SwTest$1.$SwitchMap$java$time$DayOfWeek 7: aload_0 8: invokevirtual #4 // Method DayOfWeek.ordinal:()I 11: iaload 12: tableswitch { 1: 56; 2: 62; 3: 68; 4: 74; 5: 80; 6: 80; 7: 108; default: 114 } 56: aload_1 57: ldc #5 // String Вторники в замке 59: goto 122 … 80: getstatic #9 // Field TimeUnit.HOURS 83: ldc2_w #10 // long 8l 86: invokevirtual #12 // Method TimeUnit.toMillis 89: invokestatic #13 // Method Thread.sleep 96: goto 102 95: astore_2 96: aload_1 97: ldc #15 // String Подушку верните и убирайтесь вон. Сигареты на столе. 99: goto 122 102: aload_1 103: ldc #16 // String Понедельник начинается в субботу 105: goto 122 … 114: new #18 // class IncompatibleClassChangeError
  85. 98 0: getstatic #2 // Field System.out 3: astore_1 4:

    getstatic #3 // Field SwTest$1.$SwitchMap$java$time$DayOfWeek 7: aload_0 8: invokevirtual #4 // Method DayOfWeek.ordinal:()I 11: iaload 12: tableswitch { 1: 56; 2: 62; 3: 68; 4: 74; 5: 80; 6: 80; 7: 108; default: 114 } 56: aload_1 57: ldc #5 // String Вторники в замке 59: goto 122 … 80: getstatic #9 // Field TimeUnit.HOURS 83: ldc2_w #10 // long 8l 86: invokevirtual #12 // Method TimeUnit.toMillis 89: invokestatic #13 // Method Thread.sleep 96: goto 102 95: astore_2 96: aload_1 97: ldc #15 // String Подушку верните и убирайтесь вон. Сигареты на столе. 99: goto 122 102: aload_1 103: ldc #16 // String Понедельник начинается в субботу 105: goto 122 … 114: new #18 // class IncompatibleClassChangeError
  86. 99 0: getstatic #2 // Field System.out 3: astore_1 4:

    getstatic #3 // Field SwTest$1.$SwitchMap$java$time$DayOfWeek 7: aload_0 8: invokevirtual #4 // Method DayOfWeek.ordinal:()I 11: iaload 12: tableswitch { 1: 56; 2: 62; 3: 68; 4: 74; 5: 80; 6: 80; 7: 108; default: 114 } 56: aload_1 57: ldc #5 // String Вторники в замке 59: goto 122 … 80: getstatic #9 // Field TimeUnit.HOURS 83: ldc2_w #10 // long 8l 86: invokevirtual #12 // Method TimeUnit.toMillis 89: invokestatic #13 // Method Thread.sleep 96: goto 102 95: astore_2 96: aload_1 97: ldc #15 // String Подушку верните и убирайтесь вон. Сигареты на столе. 99: goto 122 102: aload_1 103: ldc #16 // String Понедельник начинается в субботу 105: goto 122 … 114: new #18 // class IncompatibleClassChangeError
  87. 100 public static void main(String[] args) { System.out.println(switch (0) {

    default -> { try { break "Старый пруд"; } catch (Exception ex) { break "Прыгнула в воду лягушка"; } finally { break "Всплеск в тишине"; } } }); }
  88. 101 An exception has occurred in the compiler (12). Please

    file a bug against the Java compiler via the Java bug reporting page (http://bugreport.java.com) after checking the Bug Database (http://bugs.java.com) for duplicates. Include your program and the following diagnostic in your report. Thank you. java.lang.AssertionError at jdk.compiler/com.sun.tools.javac.util.Assert.error(Assert.java:155) at jdk.compiler/com.sun.tools.javac.util.Assert.check(Assert.java:46) at jdk.compiler/com.sun.tools.javac.jvm.Gen.visitBreak(Gen.java:1707) at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCBreak.accept(JCTree.java:1561) at jdk.compiler/com.sun.tools.javac.jvm.Gen.genDef(Gen.java:595) at jdk.compiler/com.sun.tools.javac.jvm.Gen.genStat(Gen.java:630) at jdk.compiler/com.sun.tools.javac.jvm.Gen.genStat(Gen.java:616) at jdk.compiler/com.sun.tools.javac.jvm.Gen.genStats(Gen.java:667) at jdk.compiler/com.sun.tools.javac.jvm.Gen.visitBlock(Gen.java:1067) at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCBlock.accept(JCTree.java:1026) at jdk.compiler/com.sun.tools.javac.jvm.Gen.genDef(Gen.java:595) at jdk.compiler/com.sun.tools.javac.jvm.Gen.genStat(Gen.java:630) at jdk.compiler/com.sun.tools.javac.jvm.Gen.genStat(Gen.java:616) at jdk.compiler/com.sun.tools.javac.jvm.Gen$3.genLast(Gen.java:1450) at jdk.compiler/com.sun.tools.javac.jvm.Gen$3.gen(Gen.java:1446) at jdk.compiler/com.sun.tools.javac.jvm.Gen.genFinalizer(Gen.java:341) at jdk.compiler/com.sun.tools.javac.jvm.Gen.unwind(Gen.java:353) at jdk.compiler/com.sun.tools.javac.jvm.Gen.unwindBreak(Gen.java:1747) at jdk.compiler/com.sun.tools.javac.jvm.Gen.visitBreak(Gen.java:1735) at jdk.compiler/com.sun.tools.javac.tree.JCTree$JCBreak.accept(JCTree.java:1561)
  89. 103 Error: Unable to initialize main class SwTest Caused by:

    java.lang.VerifyError: Stack map does not match the one at exception handler 95 Exception Details: Location: SwTest.bookOfTheDay(Ljava/time/DayOfWeek;)V @82: getstatic Reason: Current frame's stack size doesn't match stackmap. Current Frame: bci: @82 flags: { } locals: { 'java/time/DayOfWeek' } stack: { 'java/lang/InterruptedException' } Stackmap Frame: bci: @95 flags: { } locals: { 'java/time/DayOfWeek' } stack: { 'java/io/PrintStream', 'java/lang/InterruptedException' } Bytecode: 0000000: b200 02b2 0003 2ab6 0004 2eaa 0000 0061 0000010: 0000 0001 0000 0007 0000 0029 0000 002f 0000020: 0000 0035 0000 003b 0000 0041 0000 0047 0000030: 0000 0047 1205 4ca7 003d 1206 4ca7 0037 0000040: 1207 4ca7 0031 1208 4ca7 002b 1209 4ca7 0000050: 0025 b200 0a0a b600 0bb8 000c a700 0a4d 0000060: 120e 4ca7 0011 120f 4ca7 000b bb00 1059 0000070: b700 11bf 2bb6 0012 b1