Java provides many different flavors of grammar sugar, each flavor of sugar has a different way to eat and magic, which flavors of sugar do you know? Let’s try it together
Generics and type erasure
-
Generics in Java exist only at compile time, because only at compile time are the types of generics checked. At run time, there is no concept of generics. After javac is compiled, generics are erased.
-
Example 1 — Determine bytecode objects
public class Test { // public static void main(String[] args) { ArrayList<String> list1 = new ArrayList<String>(); list1.add("abc"); ArrayList<Integer> list2 = new ArrayList<Integer>(); list2.add(123); // true Different generics, but the class objects are the sameSystem.out.println(list1.getClass() == list2.getClass()); }}Copy the code
-
Example 2 — Reflection gets run-time objects
public static void main(String[] args) throws Exception { List<String> list = new ArrayList<>(); list.add("一"); Method method = list.getClass().getMethod("add", Object.class); method.invoke(list, 2); System.out.println(list); // [一, 2] } Copy the code
From the example above, you can see that different types can be set directly into a collection at run time. Although the collection’s generics specify other types, generics only restrict types at compile time
Automatic packing and unpacking
-
When using Integer, int and other wrapped classes and base types to judge, we can directly compare the reference type with the base type without converting the reference type. As we know, the reference type can be compared directly, then the memory address can be compared.
-
The principle of
// Integer and int are directly compared public static void main(String[] args) throws Exception { Integer a = new Integer(1); int b = 1; System.out.println(a == b); // true } // Integer automatic unpacking principle public static void main(String[] args) throws Exception { Integer a = new Integer(1); int b = 1; / / split open a case int i = a.intValue(); } // Automatic boxing of Integer public static void main(String[] args) throws Exception { Integer c = 1; // c is boxed as an Integer object Integer d = Integer.valueOf(1); } Copy the code
In fact, the automatic boxing and unboxing is done by calling Integer’s intValue() and valueOf() methods. The JVM automatically calls methods when a wrapper class needs to be converted to a base type or when the base type needs to be converted to a wrapper class.
Traversal cycle
-
ForEach enhances for loops the most common way we iterate over sets of numbers or collections, so how does forEach support clean code implementation loops? ForEach is also a syntactic sugar
-
Example:
- Original traversal mode:
public static void main(String[] args) throws Exception { List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); for(String s : list) { System.out.println(s); }}Copy the code
- Compiled code:
public static void main(String[] args) throws Exception { List<String> list = new ArrayList(); list.add("1"); list.add("2"); list.add("3"); Iterator var2 = list.iterator(); while(var2.hasNext()) { String s = (String)var2.next(); System.out.println(s); }}Copy the code
As can be seen from the decompiled code, forEach actually uses iterators to loop, but the tedious operations of iterators are left to the JVM. We only need to use concise forEach to complete the loop
Conditional compilation
-
The JVM evaluates the conditions of an if statement, eliminating the code for branches that won’t hold, and reducing code instructions
-
Example:
- The source code:
public static void main(String[] args) throws Exception { if(true) { System.out.println("true"); } else { System.out.println(false); }}Copy the code
- Decompiled code:
public static void main(String[] args) throws Exception { System.out.println("true"); } Copy the code
This is how the JVM optimizes code, and conditional compilation can only occur when the condition is considered constant
Swtich Supports strings
-
Prior to jdk1.7, switch supported basic data types: byte, short, char, int, and corresponding wrapper classes. Byte, short, and char can be converted to ints. Long, double, and float must be cast to ints. However, string is not allowed, and an error will be reported, so how is string supported after jdk1.8?
-
Principle:
Jdk1.8 does not add directives to handle strings. Instead, string#hashcode calculates the hash value for each string and matches cases in the switch
Now you might be wondering, what if there’s a hash conflict? The JVM development team also figured out that, like the HashMap solution, only the hashCode and equals methods would guarantee equality in case of hash conflicts
-
Example:
- The source code:
public static void main(String[] args) { String str = "a"; switch (str) { case "a": System.out.println("a"); break; case "b": System.out.println("b"); break; }}Copy the code
- Decompiled code:
public static void main(String[] args) { String str = "a"; byte var3 = -1; // Determine whether hashcode is equal to equals, and if so, assign int switch(str.hashCode()) { case 97: // The hashcode of 'a' calculates to be 97 if (str.equals("a")) { var3 = 0; } break; case 98: if (str.equals("b")) { var3 = 1; }}// Use the switch of int switch(var3) { case 0: System.out.println("a"); break; case 1: System.out.println("b"); }}Copy the code
try-with-resource
-
Let’s take a look at the code for a try-catch-finally operation on a stream
-
Example: The operation of reading a file and closing the stream correctly
public static void main(String[] args) { InputStream in = null; try { in = new FileInputStream("D://file.txt"); int read = in.read(); } catch (Exception e) { e.printStackTrace(); } finally { if(in ! =null) { try { in.close(); } catch(IOException e) { e.printStackTrace(); }}}}Copy the code
Ah, there is only one line of code to read the file, but there is so much code to secure the stream
-
The try – with – the resource example
public static void main(String[] args) { try(InputStream in = new FileInputStream("D://file.txt")) { int read = in.read(); } catch(Exception e) { e.printStackTrace(); }}Copy the code
Putting the read code operations in the try block automatically helps us safely close the stream. How? This is basically the same as the original try-catch-finally, except that the JVM does all the work
- Decompiled code
public static void main(String[] args) { try { InputStream in = new FileInputStream("D://file.txt"); Throwable var2 = null; try { int var3 = in.read(); } catch (Throwable var12) { var2 = var12; throw var12; } finally { if(in ! =null) { if(var2 ! =null) { try { in.close(); } catch(Throwable var11) { var2.addSuppressed(var11); }}else{ in.close(); }}}}catch(Exception var14) { var14.printStackTrace(); }}Copy the code
This is basically the same as the self-handling exception above, but the JVM handles it more fully and provides a sweet icing to use
Variable-length argument
-
When you’re passing parameters to a method, and you’re not sure how many you’re passing, you can use variable-length parameters as the parameters of the method, but variable-length parameters are essentially arrays, so you can take multiple parameters, and when you’re processing them in a method, you need to treat them as arrays
-
The sample
- Types of variable length arguments:
public static void main(String[] args) { int a = 1; int b = 2; int c = 3; handle(a, b, c); } private static void handle(int a, int. arr) { System.out.println(arr.getClass().getSimpleName()); // int[] } Copy the code
The variable length parameter is of type int[], which indicates that multiple parameters are passed but stored in the array. Note that variable length parameters can only be placed at the end of the parameter list, and only one variable length parameter can be found in a method
The enumeration
-
Do you know that enumerations are essentially classes? Enumerations are essentially implemented by ordinary classes, but the compiler handles them for us. Each enumerated type inherits from java.lang.Enum and automatically adds values and valueOf methods
Let’s take a look at what enumeration looks like after decompilation
-
The source code:
public enum Skip { ADD, SUBTRACT, MULTIPLY, DIVIDE } Copy the code
-
Decompiled code:
- The first stepcompile:
javac Skip.java
- The second stepdecompiling:
javap -c -v Skip.class
Compiled from "Skip.java" public final class Skip extends java.lang.Enum<Skip> minor version: 0 major version: 52 flags: ACC_PUBLIC.ACC_FINAL.ACC_SUPER.ACC_ENUM Constant pool# 1:= Fieldref #4.#38 // Skip.$VALUES:[LSkip; #2 = Methodref #39.#40 // "[LSkip;".clone:()Ljava/lang/Object; #3 = Class #23 // "[LSkip;" #4 = Class #41 // Skip #5 = Methodref #16.#42 // java/lang/Enum.valueOf:(Ljava/lang/Class; Ljava/lang/String;) Ljava/lang/Enum; #6 = Methodref #16.#43 // java/lang/Enum."
":(Ljava/lang/String; I)V #7 = String #17 // ADD #8 = Methodref #4.#43 // Skip."":(Ljava/lang/String; I)V #9 = Fieldref #4.#44 // Skip.ADD:LSkip; #10 = String #19 // SUBTRACT #11 = Fieldref #4.#45 // Skip.SUBTRACT:LSkip; #12 = String #20 // MULTIPLY #13 = Fieldref #4.#46 // Skip.MULTIPLY:LSkip; #14 = String #21 // DIVIDE #15 = Fieldref #4.#47 // Skip.DIVIDE:LSkip; #16 = Class #48 // java/lang/Enum #17 = Utf8 ADD #18 = Utf8 LSkip; #19 = Utf8 SUBTRACT #20 = Utf8 MULTIPLY #21 = Utf8 DIVIDE #22 = Utf8 $VALUES #23 = Utf8 [LSkip; #24 = Utf8 values #25 = Utf8 ()[LSkip; #26 = Utf8 Code #27 = Utf8 LineNumberTable #28 = Utf8 valueOf #29= Utf8 (Ljava/lang/String;) LSkip; #30 = Utf8 <init> #31= Utf8 (Ljava/lang/String; I)V #32 = Utf8 Signature #33 = Utf8 ()V #34 = Utf8 <clinit> #35= Utf8 Ljava/lang/Enum<LSkip; >; #36 = Utf8 SourceFile #37 = Utf8 Skip.java #38 = NameAndType #22: #23 // $VALUES:[LSkip; #39 = Class #23 // "[LSkip;" #40 = NameAndType #49: #50 // clone:()Ljava/lang/Object; #41 = Utf8 Skip #42 = NameAndType #28: #51 // valueOf:(Ljava/lang/Class; Ljava/lang/String;) Ljava/lang/Enum; #43 = NameAndType #30: #31 // "":(Ljava/lang/String; I)V #44 = NameAndType #17: #18 // ADD:LSkip; #45 = NameAndType #19: #18 // SUBTRACT:LSkip; #46 = NameAndType #20: #18 // MULTIPLY:LSkip; #47 = NameAndType #21: #18 // DIVIDE:LSkip; #48 = Utf8 java/lang/Enum #49 = Utf8 clone #50 = Utf8 ()Ljava/lang/Object; #51= Utf8 (Ljava/lang/Class; Ljava/lang/String;) Ljava/lang/Enum; {public static final Skip ADD; descriptor: LSkip; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static final Skip SUBTRACT; descriptor: LSkip; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static final Skip MULTIPLY; descriptor: LSkip; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static final Skip DIVIDE; descriptor: LSkip; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static Skip[] values(); descriptor: ()[LSkip; flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 0: getstatic #1 // Field $VALUES:[LSkip; 3: invokevirtual #2 // Method "[LSkip;".clone:()Ljava/lang/Object; 6: checkcast #3 // class "[LSkip;" 9: areturn LineNumberTable: line 5: 0 public static Skip valueOf(java.lang.String); descriptor: (Ljava/lang/String;) LSkip; flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: ldc #4 // class Skip 2: aload_0 3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class; Ljava/lang/String;) Ljava/lang/Enum; 6: checkcast #4 // class Skip 9: areturn LineNumberTable: line 5: 0 static {}; descriptor: ()V flags: ACC_STATIC Code: stack=4, locals=0, args_size=0 0: new #4 // class Skip 3: dup 4: ldc #7 // String ADD 6: iconst_0 7: invokespecial #8 // Method "":(Ljava/lang/String; I)V 10: putstatic #9 // Field ADD:LSkip; 13: new #4 // class Skip 16: dup 17: ldc #10 // String SUBTRACT 19: iconst_1 20: invokespecial #8 // Method "":(Ljava/lang/String; I)V 23: putstatic #11 // Field SUBTRACT:LSkip; 26: new #4 // class Skip 29: dup 30: ldc #12 // String MULTIPLY 32: iconst_2 33: invokespecial #8 // Method "":(Ljava/lang/String; I)V 36: putstatic #13 // Field MULTIPLY:LSkip; 39: new #4 // class Skip 42: dup 43: ldc #14 // String DIVIDE 45: iconst_3 46: invokespecial #8 // Method "":(Ljava/lang/String; I)V 49: putstatic #15 // Field DIVIDE:LSkip; 52: iconst_4 53: anewarray #4 // class Skip 56: dup 57: iconst_0 58: getstatic #9 // Field ADD:LSkip; 61: aastore 62: dup 63: iconst_1 64: getstatic #11 // Field SUBTRACT:LSkip; 67: aastore 68: dup 69: iconst_2 70: getstatic #13 // Field MULTIPLY:LSkip; 73: aastore 74: dup 75: iconst_3 76: getstatic #15 // Field DIVIDE:LSkip; 79: aastore 80: putstatic #1 // Field $VALUES:[LSkip; 83: return LineNumberTable: line 6: 0 line 7: 13 line 8: 26 line 9: 39 line 5: 52 } Signature: #35 // Ljava/lang/Enum; ;> SourceFile: "Skip.java" Copy the codeAs you can see from the decomcompiled code, the enumeration Class defined by Skip extends java.lang.Enum is a Class object and inherits from the java.lang.Enum Class, automatically generating values and valueOf methods.
Each enumerated constant is a static constant field, implemented using an inner class that inherits from the enumerated class. All enumerated constants are initialized by static code blocks, that is, during class loading. In addition, the clone, readObject and writeObject methods are defined as final, and corresponding exceptions are thrown. This ensures that each enumeration type and enumeration constant is immutable. These two features of enumeration can be leveraged to implement thread-safe singletons.
- The first stepcompile:
With all that said about syntactic sugar, do you know at what stage in javac compilation the syntactic sugar is unsugar-coated? That’s right, in the analysis and bytecode generation phase, where the Javac compiler specifically unsugarcoats the syntax, returning it to the original code
Next time you’re eating grammar candy, don’t forget the source code below each sugar coating
Welcome to the wechat public account “code on the finger tip”
Original is not easy, click a praise and then go ~ welcome to pay attention to, to bring you more wonderful articles!
Your likes and attention are the biggest motivation for writing articles