In this blog post, let's talk about an interesting knowledge point in Java - syntax sugar
1. Introduction to grammar
So, what is grammar sugar?
Grammar sugar: also known as "sugar coated grammar". Refers to a syntax added to a computer language. This syntax has no impact on the function of the language, but only to facilitate programmer development, improve development efficiency and improve program readability (the existence of syntax sugar is mainly convenient for developers).
Syntax sugar: however, the JVM does not support syntax sugar. After the program is compiled, the syntax sugar will be restored to the original basic syntax structure. This process is the parsing sugar (the syntax sugar in Java only exists during compilation).
Therefore, in Java, the java compiler really supports syntax sugar.
After the program is compiled, it changes from a. java file to a. class file, which is binary bytecode. Its contents cannot be viewed directly. It needs to be decompiled with the help of decompiler tools before it can be viewed normally. Here is a recommender tool: JAD. For detailed usage, please refer to this blog
jad Class name.class
The above command decompiles the. class file into a. java file, and then you can view the original structure of the syntax file after it is compiled.
2. Syntax sugar in Java
Many syntax sugars are provided in Java. The following are the main and commonly used syntax sugars:
- Numeric literal
- Method variable length parameter
- Enhanced for loop
- Switch case support for String and enumeration classes
- String + sign syntax
- Automatic packing and unpacking of packaging
- Inner class
- Try with resources syntax
- enumeration
- generic paradigm
2.1 numerical literal quantity
In Java, numeric literals in the following forms are supported:
- Decimal: default
- Octal: an integer is represented by the number 0 before it
- Hexadecimal: the integer is preceded by 0X or 0X
- Binary (newly added): the integer is preceded by 0B or 0B
In addition, in JDK 1.7, it is allowed to insert any number of underscores between numbers, regardless of the numeric literal of integers or floating-point numbers. These underscores have no effect on the literal values for ease of reading. For example:
- 1000_000
- 123_456.22
The underline can only appear in the middle of the number. It must be a number before and after it. Therefore, "_100" and "0b_101" are illegal and cannot be compiled.
The motivation of this restriction is to reduce the implementation complexity. With this restriction, the Java compiler only needs to delete the underscore in the middle of the number found when scanning the source code. If this restriction is not added, the compiler needs syntax analysis to make a judgment. For example: _100, it may be an entire number, 100, or a Variable name. This requires more complex changes to the compiler's implementation.
The code is as follows:
public class TestOne { public static void main(String[] args) { // decimal system int a = 10; int aa = 10_000; // octal number system int b = 010; // hexadecimal int c = 0X10; // Binary int d = 0B10; // 10 System.out.println(a); // 10000 System.out.println(aa); // 8 System.out.println(b); // 16 System.out.println(c); // 2 System.out.println(d); } }
Decompile the above code through jad tool:
public class TestOne { public TestOne() { } public static void main(String args[]) { int a = 10; int aa = 10000; int b = 8; int c = 16; int d = 2; System.out.println(a); System.out.println(aa); System.out.println(b); System.out.println(c); System.out.println(d); } }
It is known from the decompiled code that the compiler has removed the sliding line; the compiler has converted binary, octal and hexadecimal numbers into decimal numbers
2.2 method variable length parameter (JDK1.5)
There are two conditions for using variable length parameters: one is that the variable length parameters have the same type; the other is that the variable length parameters must be at the end of the method parameter list.
Variable length parameter is also a syntax sugar in Java. Its internal implementation principle: the compiler converts the variable length parameter part into a Java array when compiling the source code.
public class TestTwo { public static void variable(String country, String... cities) { System.out.println(country); for(int i = 0; i < cities.length; i++) { System.out.print(cities[i]); } System.out.println(); } public static void main(String[] args) { variable("china", "Beijing", "ShangHai", "ShenZhen"); } }
After decompilation:
public class TestTwo { public TestTwo() { } public static transient void variable(String country, String cities[]) { System.out.println(country); for(int i = 0; i < cities.length; i++) System.out.print(cities[i]); System.out.println(); } public static void main(String args[]) { variable("china", new String[] { "Beijing", "ShangHai", "ShenZhen" }); } }
2.3 enhanced for loop
The object of the enhanced for loop is either an array or implements the iteratable interface. This syntax sugar is mainly used to traverse the array or collection, and it cannot change the size of the collection during the loop. The enhanced for loop mainly makes the code more concise. The underlying principle is that the compiler converts the enhanced for loop into an ordinary for loop or while loop.
public class TestThree { public static void main(String[] args) { String[] params = new String[]{"Java", "Python", "C++"}; for (String param : params) { System.out.println(param); } } }
After decompilation:
public class TestThree { public TestThree() { } public static void main(String args[]) { String params[] = { "Java", "Python", "C++" }; String args1[] = params; int i = args1.length; for(int j = 0; j < i; j++) { String param = args1[j]; System.out.println(param); } } }
2.4 switch case support for String and Enum classes
switch in Java originally supports basic types - integer: int, char, byte and short. In addition, the type will be (forcibly) converted to int at compile time.
However, long type is not supported. Because converting long type to int type will lose precision!!
For int, byte and short types, numerical values are directly compared; for char types, ascii codes are compared
Later, switch supported enumeration types appeared in JDK 1.5, and switch supported strings appeared in JDK 1.7.
For String, switch is implemented through hashCode() and equals() methods; for enumeration class, it is implemented through the subscript defined by enumeration
character string:
public class SwitchSugarTest { public static void main(String[] args) { String str = "Java"; switch (str) { case "Java": System.out.println("James Gosling is Java's father"); break; case "C++": System.out.println("Bjarne Stroustrup is C++'s fatherr"); break; default: break; } } }
After decompilation:
public class SwitchSugarTest { public SwitchSugarTest() { } public static void main(String args[]) { String str = "Java"; String s = str; byte byte0 = -1; switch(s.hashCode()) { case 2301506: if(s.equals("Java")) byte0 = 0; break; case 65763: if(s.equals("C++")) byte0 = 1; break; } switch(byte0) { case 0: // '\0' System.out.println("James Gosling is Java's father"); break; case 1: // '\001' System.out.println("Bjarne Stroustrup is C++'s fatherr"); break; } } }
2.5 string + sign syntax
String + number splicing principle: run time, two strings str1, str2 splicing will first new a StringBuilder object, then append operation on the string, and finally call toString() method.
public class StringSugarTest { public static void main(String[] args) { String str1 = "a"; String str2 = "b"; String s = str1 + str2; System.out.println(s); } }
After decompilation:
public class StringSugarTest { public StringSugarTest() { } public static void main(String args[]) { String str1 = "a"; String str2 = "b"; String s = (new StringBuilder()).append(str1).append(str2).toString(); System.out.println(s); } }
However, if the result of character addition can be determined at compile time, compile time optimization will be carried out.
String s = "a" + "b";
For the above expression, the compiler directly optimizes to
String s = "ab";
2.6 automatic packing and unpacking of packaging
In Java, the eight basic types and corresponding packing types can be assigned to each other (this process is called automatic packing and unpacking process).
In fact, the principle behind this is that the compiler optimizes: assigning a basic type to a wrapper class actually calls the valueOf() method of the wrapper class to create a wrapper class and assign it to the basic type; and assigning a wrapper class to a basic type is to call the xxxValue() method of the wrapper class to get the basic data type and then assign it
Automatic packing:
public class PackageSugarTest { public static void main(String[] args) { int a = 1; int b = 2; Integer c = a + b; System.out.println(c); } }
After decompilation:
public class PackageSugarTest { public PackageSugarTest() { } public static void main(String args[]) { int a = 1; int b = 2; Integer c = Integer.valueOf(a + b); System.out.println(c); } }
Automatic unpacking:
public class PackageSugarTest { public static void main(String[] args) { Integer a = 1; Integer b = 2; int c = a + b; System.out.println(c); } }
After decompilation:
public class PackageSugarTest { public PackageSugarTest() { } public static void main(String args[]) { Integer a = Integer.valueOf(1); Integer b = Integer.valueOf(2); int c = a.intValue() + b.intValue(); System.out.println(c); } }
2.7 internal class
The reason why internal classes are introduced into the Java language is that sometimes a class only wants to be useful in one class, and we don't want it to be used in another place. The reason why internal classes are syntax sugar is that they are just a compile time concept. Once the compilation is completed, the compiler will generate a separate class file for the internal classes, named outer$innter.class.
public class Outer { class Inner {} }
After compiling with javac, two class files are generated: Outer.class and Outer$Inner.class. After decompilation, the contents are as follows:
public class Outer { class Inner { final Outer this$0; Inner() { this.this$0 = Outer.this; super(); } } public Outer() { } }
class Outer$Inner { final Outer this$0; Outer$Inner() { this.this$0 = Outer.this; super(); } }
2.8 try with resources syntax
When the handle object of an external resource implements the autoclosable interface, JDK7 can use the try with resource syntax to more gracefully close resources and eliminate plate code.
Put the creation of the handle object of the external resource in parentheses after the try keyword. After the try catch code block is executed, Java will ensure that the close() method of the external resource is called
public class TrySugarTest { public static void main(String[] args) { try (FileInputStream in = new FileInputStream(new File("pom.xml"))){ System.out.println(in.read()); } catch (Exception e) { e.printStackTrace(); } } }
After decompilation:
public class TrySugarTest { public TrySugarTest() { } public static void main(String args[]) { FileInputStream in; Throwable throwable; in = new FileInputStream(new File("pom.xml")); throwable = null; try { System.out.println(in.read()); } catch(Throwable throwable2) { throwable = throwable2; throw throwable2; } if(in != null) if(throwable != null) try { in.close(); } catch(Throwable throwable1) { throwable.addSuppressed(throwable1); } else in.close(); break MISSING_BLOCK_LABEL_108; Exception exception; exception; if(in != null) if(throwable != null) try { in.close(); } catch(Throwable throwable3) { throwable.addSuppressed(throwable3); } else in.close(); throw exception; Exception e; e; e.printStackTrace(); } }
2.9 enumeration
Class is used for the definition of classes in Java, and enum is used for the definition of enumeration classes. However, in the bytecode structure of Java, there is no enumeration type. The enumeration type is just a syntax sugar. After compilation, it will be compiled into an ordinary class, which is also decorated with class. This class inherits java.lang.Enum and is decorated with the final keyword.
public enum EnumSugarTest { APPLE , ORANGE ; }
After decompilation:
public final class EnumSugarTest extends Enum { public static EnumSugarTest[] values() { return (EnumSugarTest[])$VALUES.clone(); } public static EnumSugarTest valueOf(String name) { return (EnumSugarTest)Enum.valueOf(com/tinady/sugar/EnumSugarTest, name); } private EnumSugarTest(String s, int i) { super(s, i); } public static final EnumSugarTest APPLE; public static final EnumSugarTest ORANGE; private static final EnumSugarTest $VALUES[]; static { APPLE = new EnumSugarTest("APPLE", 0); ORANGE = new EnumSugarTest("ORANGE", 1); $VALUES = (new EnumSugarTest[] { APPLE, ORANGE }); } }
2.10 generics
In JDK5, the Java language introduces a generic mechanism. However, this generic mechanism is actually implemented through type erasure, that is, generics in Java are only valid in the program source code (type checking is provided in the source code stage), and are automatically replaced by forced type conversion in the compiled bytecode. In other words, the generic mechanism in the Java language is actually a syntax sugar.
public class FanSugarTest { public static void main(String[] args) { Map<String, String> map = new HashMap<>(); map.put("name", "zzc"); map.put("age", "22"); String name = map.get("name"); System.out.println(name); } }
After decompilation:
public class FanSugarTest { public FanSugarTest() { } public static void main(String args[]) { Map map = new HashMap(); map.put("name", "zzc"); map.put("age", "22"); String name = (String)map.get("name"); System.out.println(name); } }