Syntactic sugar

Syntactic Sugar, also known as sugar-coated grammar, is a term coined by British computer scientist Peter.J.Landin to describe a grammar added to a computer language that has no effect on how the language functions, but is more convenient for programmers to use. In short, syntactic sugar makes programs simpler and more readable.

Almost every programming language we’re familiar with has syntactic sugar. According to the author, the amount of grammatical sugar is one of the criteria to judge whether a language is awesome or not. Many people say that Java is a “low sugar language”. In fact, since Java 7, various sugars have been added to the Java language level, mainly under the “Project Coin” Project. Even though Java is low in sugar, it will continue to be high in sugar.

Title, method of sugar

As mentioned earlier, syntactic sugar exists primarily for the convenience of developers. However, the Java virtual machine does not support these syntactic sugars. These syntactic sugars are reduced to simple basic syntactic structures at compile time. This process is called syntactic sugars.

Speaking of compilation, you probably know that in the Java language, the javac command compiles. Java source files into. Class bytecode that can run on the Java virtual machine. If you go to the com. Sun. View javac. Main. JavaCompiler source code, you will find in the compile () is one of the steps is called desugar (), this method is responsible for the title, the realization of the method of sugar.

The most common syntactic sugar in Java is generics, various-length parameters, conditional compilation, auto-unboxing, inner classes, etc. This article will analyze the principles behind these grammatical sugars. Step by step, peel off the icing and see the essence.

Syntax sugar -switch supports strings and enumerations

As mentioned earlier, the syntactic sugar in the Java language has been growing since Java 7. One of the most important is the switch’s support for Strings in Java 7.

Before we start coding, swith in Java itself already supports basic types. Such as int, char, etc. For int types, the comparison is done directly. For char, compare its ASCII code. So, for the compiler, you can only really use integers in switch, and any type of comparison is converted to integers. For example, byte. Short, char(ackii is an integer), and int.

Switch support for String

public class switchDemoString {
    public static void main(String[] args) {
        String str = "world";
        switch (str) {
        case "hello":
            System.out.println("hello");
            break;
        case "world":
            System.out.println("world");
            break;
        default:
            break; }}}Copy the code

The decompiled content is as follows:

public class switchDemoString
{
    public switchDemoString(a)
    {}public static void main(String args[])
    {
        String str = "world";
        String s;
        switch((s = str).hashCode())
        {
        default:
            break;
        case 99162322:
            if(s.equals("hello"))
                System.out.println("hello");
            break;
        case 113318802:
            if(s.equals("world"))
                System.out.println("world");
            break; }}}Copy the code

Looking at this code, you know that the original string switch is implemented via equals() and hashCode() methods. Fortunately, the hashCode() method returns an int instead of a long.

A closer look reveals that it is actually the hash values that are being switched, and then a security check is performed by comparing them using the Equals method, which is necessary because hashes can collide. So it’s not as good as switching with enumerations or using pure integer constants, but it’s not bad either.

Grammar sugar – automatic packing and unpacking

Automatic boxing is when Java automatically converts primitive values into corresponding objects, for example, converting an int variable into an Integer object. This process is called boxing, and converting an Integer object into an int value is called unboxing. Since the packing and unpacking here is automatic and impersonal, it is called automatic packing and unpacking. The primitive types byte, short, char, int, long, float, double, and Boolean correspond to encapsulated classes byte, short, Character, Integer, long, float, double, Boolean.

Let’s look at the code for automatic boxing:

 public static void main(String[] args) {
    int i = 10;
    Integer n = i;
}
Copy the code

The decompiled code is as follows:

public static void main(String args[])
{
    int i = 10;
    Integer n = Integer.valueOf(i);
}
Copy the code

Let’s look at the code for automatic unpacking:

public static void main(String[] args) {

    Integer i = 10;
    int n = i;
}
Copy the code

The decompiled code is as follows:

public static void main(String args[])
{
    Integer i = Integer.valueOf(10);
    int n = i.intValue();
}
Copy the code

As you can see from the decompilation, the Integer valueOf(int) method is automatically called during boxing. The Integer intValue method is automatically called when unpacking.

So, the packing process is done by calling the valueOf method of the wrapper, and the unpacking process is done by calling the xxxValue method of the wrapper.

Pay attention to

Object equality comparison may cause problems using automatic boxing

public class BoxingTest {
public static void main(String[] args) {
    Integer a = 1000;
    Integer b = 1000;
    Integer c = 100;
    Integer d = 100;
    System.out.println("a == b is " + (a == b));
    System.out.println(("c == d is " + (c == d)));
}
Copy the code

The output

a == b is false
c == d is true
Copy the code

In Java 5, a new feature was introduced on the operation of Integer to save memory and improve performance. Integer objects enable caching and reuse by using the same object reference.

Applies to integer values from -128 to +127.

Only applicable to automatic packing. Using constructors to create objects is not appropriate.

Syntax sugar – method variable length parameter

Variable arguments are a feature introduced in Java 1.5. It allows a method to take any number of values as arguments.

Take a look at the following mutable argument code, where the print method receives mutable arguments:

    public static void main(String[] args)
    {
        print("hello"."world"."123"."456");
    }

    public static void print(String... strs)
    {
        for (int i = 0; i < strs.length; i++) { System.out.println(strs[i]); }}Copy the code

Decompiled code:

public static void main(String args[])
{
    print(new String[] {
        "hello"."world"."123"."456"
    });
}

public static transient void print(String strs[])
{
    for(int i = 0; i < strs.length; i++)
        System.out.println(strs[i]);

}
Copy the code

As you can see from the decomcompiled code, when a variable argument is used, it first creates an array whose length is the number of arguments passed by the method, then puts all the argument values in the array, and then passes the array as arguments to the called method.

Syntax sugar – numeric literals

In Java 7, numeric literals, whether integers or floating-point numbers, allow any number of underscores to be inserted between numbers. These underscores have no effect on literal values, and are intended to be easy to read.

Such as:

public class Test {
    public static void main(String... args) {
        int i = 10 _000; System.out.println(i); }}Copy the code

After decompiling:

public class Test
{
  public static void main(String[] args)
  {
    int i = 10000; System.out.println(i); }}Copy the code

The decompilation removes the _. This means that the compiler does not recognize _ in numeric literals and needs to remove it at compile time.

Grammar sugar -for-each

For loop (for-each) enhancement for loop (for-each) enhancement for loop (for-each)

public static void main(String... args) {
    String[] strs = {"hello"."world"."123"."456"};
    for (String s : strs) {
        System.out.println(s);
    }
    List<String> strList = ImmutableList.of("hello"."world"."123"."456");
    for(String s : strList) { System.out.println(s); }}Copy the code

The decompiled code is as follows:

public static transient void main(String args[])
{
    String strs[] = {
        "hello"."world"."123"."456"
    };
    String args1[] = strs;
    int i = args1.length;
    for(int j = 0; j < i; j++)
    {
        String s = args1[j];
        System.out.println(s);
    }

    List strList = ImmutableList.of("hello"."world"."123"."456");
    String s;
    for(Iterator iterator = strList.iterator(); iterator.hasNext(); System.out.println(s))
        s = (String)iterator.next();

}
Copy the code

The code is simple, and for-each is implemented using a normal for loop and iterator.

Pay attention to

Using enhanced fou you may encounter ConcurrentModificationException anomalies

for (Student stu : students) {    
    if (stu.getId() == 2)     
        students.remove(stu);    
}
Copy the code

Will throw ConcurrentModificationException.

Iterator works on a separate thread and has a mutex lock. After the Iterator is created, it creates a single indexed table that points to the original object. When the number of original objects changes, the contents of the index table do not change synchronously. Therefore, when the index pointer moves backwards, the object to be iterated cannot be found. So the Iterator thrown immediately in accordance with the principle of fail – fast Java. Util. ConcurrentModificationException anomalies.

So the Iterator does not allow the iterated object to be changed while working. But you can remove objects using Iterator’s own method, remove(). Iterator.remove() maintains index consistency while removing the current iteration.

Syntax sugar-try-with-resource

In Java, you must use the close method to close expensive resources such as file operation I/O streams and database connections. Otherwise, the resources will remain open and may cause memory leaks.

A common way to close a resource is to release it ina finally block by calling the close method. For example, we often write code like this:

public static void main(String[] args) {
    BufferedReader br = null;
    try {
        String line;
        br = new BufferedReader(new FileReader("d:\\test.xml"));
        while((line = br.readLine()) ! =null) { System.out.println(line); }}catch (IOException e) {
        // handle exception
    } finally {
        try {
            if(br ! =null) { br.close(); }}catch (IOException ex) {
            // handle exception}}}Copy the code

Starting with Java 7, the JDK provides a better way to close resources. Use the try-with-resources statement and rewrite the above code to look like this:

public static void main(String... args) {
    try (BufferedReader br = new BufferedReader(new FileReader("d:\\ test.xml"))) {
        String line;
        while((line = br.readLine()) ! =null) { System.out.println(line); }}catch (IOException e) {
        // handle exception}}Copy the code

See, this is a great boon, even though I usually use IOUtils to close streams and don’t write a lot of code in Finally, but this new syntactic sugar seems a lot more elegant. Take a look behind him:

public static transient void main(String args[])
    {
        BufferedReader br;
        Throwable throwable;
        br = new BufferedReader(new FileReader("d:\\ test.xml"));
        throwable = null;
        String line;
        try
        {
            while((line = br.readLine()) ! =null)
                System.out.println(line);
        }
        catch(Throwable throwable2)
        {
            throwable = throwable2;
            throw throwable2;
        }
        if(br ! =null)
            if(throwable ! =null)
                try
                {
                    br.close();
                }
                catch(Throwable throwable1)
                {
                    throwable.addSuppressed(throwable1);
                }
            else
                br.close();
            break MISSING_BLOCK_LABEL_113;
            Exception exception;
            exception;
            if(br ! =null)
                if(throwable ! =null)
                    try
                    {
                        br.close();
                    }
                    catch(Throwable throwable3)
                      {
                        throwable.addSuppressed(throwable3);
                    }
                else
                    br.close();
        throwexception; IOException ioexception; ioexception; }}Copy the code

In fact, the principle behind it is very simple, the compiler does all the things we don’t do to close the resource. So, again, syntax sugar is meant to make it easier for programmers to use, but ultimately it has to be translated into a language that the compiler knows.

conclusion

Syntax sugar is just a syntax that is provided to developers for ease of development. But this syntax is known only to developers. To be executed, it needs to be sugared into a syntax that the JVM understands. When we sugar-sugar grammar, we will find that the convenient grammar we use every day is actually made up of other, simpler grammar.