Syntax sugar in Java, so sweet.

We often use generics, auto-unboxing and boxing, inner classes, enhanced for loops, try-with-resources syntax, lambda expressions, and so on in our daily development. We just feel comfortable using these features because they help reduce development effort. But we haven’t really studied the nature of these features, so this article, CXuan, will reveal the truth behind them for you.

Syntactic sugar

Before we start, Syntactic sugar is a concept that needs to be explained. It is also called sugar-coated grammar, a term invented by British scientists. It is often used to make programs more readable and thus less likely to fail.

Syntactic sugar is a syntax added to a computer language that has no effect on the language’s functionality but is more convenient for programmers to use. Because Java code needs to run in the JVM, the JVM does not support syntactic sugar. Syntactic sugar is reduced to simple basic syntactic structures during compilation, which is called syntactic sugar. So in Java, the only thing that really supports syntax sugar is the Java compiler, which is the same thing when the lights are off…

Let’s take a look at these syntactic sugar in Java

The generic

Generics are a syntactic sugar. In JDK1.5, generics were introduced, but the generics mechanism itself is implemented through type erase. there are no generics in the JVM, only normal types and methods, and the type parameters of a generic class are erased at compile time. Generics do not have their own unique Class type. See the code below

List<Integer> aList = new ArrayList(); List<String> bList = new ArrayList(); System.out.println(aList.getClass() == bList.getClass()); Copy the codeCopy the code

List

and List

are considered different types, but the output yields the same result. This is because generics information exists only at compile time, and generic-related information is erased before entering the JVM, a technical term called type erasation. However, putting an Integer into a List

or a String into a List

is not allowed.



As shown in the figure below

Failing to place an Integer in List

is the same as failing to place a String in List

.

Automatic unpacking and automatic packing

Autounboxing and autoboxing are syntactic candy that refer to automatic conversions between the wrapping classes of the eight basic data types and their basic data types. Simply put, boxing automatically converts the base data type to the wrapper type; Unpacking automatically converts wrapper types to primitive data types.

Let’s start by looking at the wrapper classes for the basic data types

That is, these basic data types and wrapper classes are automatically boxed/unboxed during conversion, as shown in the following code

Integer integer = 66; Int i1 = integer; // Automatic boxing copy codeCopy the code

The integer object in the code above is assigned to a primitive datatype, whereas the primitive datatype i1 assigns it to an object type. Normally this cannot be done, but the compiler allows us to do this, which is a syntactic sugar. This syntactic sugar makes it easy to perform numerical operations. Without syntactic sugar, you would need to convert the object to a primitive data type, which would also need to be converted to a wrapper type to use the built-in methods, adding redundancy.

So how to achieve automatic unpacking and automatic packing?

The idea behind this is that the compiler is optimized. Assigning a primitive type to the wrapper class actually creates a wrapper class by calling the valueOf() method of the wrapper class and assigns it to the primitive type.

int i1 = Integer.valueOf(1); Copy the codeCopy the code

Assigning a wrapper class to a base type is done by calling the xxxValue() method of the wrapper class after it gets the base type.

Integer i1 = new Integer(1).intValue(); Copy the codeCopy the code

Let’s use Javap-c to decompile the autoboxing and autounboxing above to verify

As you can see, when invokestatic is called at Code 2, the compiler automatically adds an integer.valueof method to convert the base data type to a wrapper type.

When invokevirtual is called in Code 7, the compiler adds integ.intValue () to convert the Integer value to the primitive data type.

The enumeration

We often use enum and public static final… This kind of syntax. When do you use enum or public static final constants? Seems to be all right.

But in Java bytecode structures, there are no enumerated types. Enumeration is just a syntactic sugar that will be compiled into a normal Class after compilation, again with the Class modifier. This class inherits from java.lang.Enum and is decorated with the final keyword.

Let’s take an example

public enum School { STUDENT, TEACHER; } Duplicate codeCopy the code

This is an enumeration of schools and contains two fields, STUDENT and TEACHER, and nothing else.

Let’s decompile the school. class using javap. When the decompilation is complete, the result is as follows

As you can see from the figure, enumeration is simply a class that inherits from the java.lang.Enum class. STUDENT and TEACHER are public static final fields. Public static Final School STUDENT is a public static final School STUDENT.

In addition, the compiler generates two methods for us, values() and valueOf, which the compiler added for us. The values() method fetches all the Enum property values. The valueOf method is used to get individual attribute values.

Note that the values() method of Enum is not part of the JDK API, and there are no annotations for the values() method in the Java source code.

Use the following

public enum School { STUDENT("Student"), TEACHER("Teacher"); private String name; School(String name){ this.name = name; } public String getName() { return name; } public static void main(String[] args) { System.out.println(School.STUDENT.getName()); School[] values = School.values(); for(School school : values){ System.out.println("name = "+ school.getName()); }}} copy the codeCopy the code

The inner class

Inner class is a minority feature of Java, I say minority, not to say that inner class is useless, but we rarely use it in daily development, but look at the JDK source code, found many source code has the construction of inner class. For example, the common ArrayList source code has an Itr inner class that inherits from the Iterator class. In HashMap, for example, a Node is constructed from map. Entry

to represent each Node of the HashMap.
,v>

Inner classes were introduced in the Java language because sometimes a class only wants to be useful in one class and not be used elsewhere, that is, to hide its inner details from the outside world.

The inner class is also a syntactic sugar because it is just a compile-time concept. Once compiled, the compiler generates a separate class file called outer$innter.class for the inner class.

Let’s verify this with an example.

public class OuterClass { private String label; class InnerClass { public String linkOuter(){ return label = "inner"; } } public static void main(String[] args) { OuterClass outerClass = new OuterClass(); InnerClass innerClass = outerClass.new InnerClass(); System.out.println(innerClass.linkOuter()); }} Copy the codeCopy the code

OuterClass and OuterClass$innerclass. class. This means that an OuterClass can be linked to an InnerClass, and the InnerClass can modify properties of the OuterClass.

Let’s look at the result of compiling the inner class

As shown above, the compiled linkOuter() method of the inner class generates a this reference to the outer class that connects the outer class to the inner class.

Variable-length argument

Variable-length arguments are also a less popular use, where methods accept arguments of indeterminate length. Variable-length parameters are not generally used in our development, and they are not recommended, as they can make our programs difficult to handle. But it is important to understand the properties of variable length parameters.

The basic usage is as follows

public class VariableArgs { public static void printMessage(String... args){ for(String str : args){ System.out.println("str = " + str); } } public static void main(String[] args) { VariableArgs.printMessage("l","am","cxuan"); }} Copy the codeCopy the code

Variable-length arguments are also syntactic sugar, so how is it implemented? We can make a guess that it should be an array inside, otherwise it can’t accept multiple values, so let’s decompile to see if it’s an array.

As you can see, the printMessage() argument is received using an array, so don’t be fooled by the variable length argument!

The variable-length argument feature was introduced in JDK 1.5. Variable-length arguments can be used only if the part of the argument that is variable-length is of the same type, and only if the variable-length argument is at the end of the method argument list.

Enhanced for loop

Why do we have an enhanced for loop when we have a regular for loop? If you think about it, don’t you need to know the number of iterations for a regular for loop? You also need to know what the index of the array is each time, which is obviously a little tedious to write. Enhanced for loops are much more powerful and much cleaner than regular for loops. You don’t need to know how many times you iterate or the index of the array to iterate.

The object that enhances the for loop is either an array or implements the Iterable interface. This syntactic sugar is used to iterate over an array or collection and cannot change the size of the collection during the loop.

public static void main(String[] args) { String[] params = new String[]{"hello","world"}; // Enhance the for loop object to array for(String STR: params){system.out.println (STR); } List<String> lists = Arrays.asList("hello","world"); For (String STR: Lists){system.out.println (STR); for(String STR: lists){system.out.println (STR); }} Copy the codeCopy the code

The compiled class file looks like this

public static void main(String[] args) { String[] params = new String[]{"hello", "world"}; String[] lists = params; int var3 = params.length; For (int STR = 0; str < var3; ++str) { String str1 = lists[str]; System.out.println(str1); } List var6 = Arrays.asList(new String[]{"hello", "world"}); Iterator var7 = var6.iterator(); While (var7.hasnext ()) {String var8 = (String)var7.next(); System.out.println(var8); }} Copy the codeCopy the code

As the code above shows, if you augment the for loop with an array, it still iterates through the array internally, but the syntax tricks you into writing code in a cleaner way.

To iterate through an enhanced for loop inherited from an Iterator is equivalent to calling Iterator’s hasNext() and next() methods.

The Switch supports strings and enumerations

The switch keyword natively supports only integer types. If a switch is followed by a String, the compiler converts it to the value of the String’s hashCode, so the switch syntax compares the String’s hashCode.

See the code below

public class SwitchCaseTest { public static void main(String[] args) { String str = "cxuan"; switch (str){ case "cuan": System.out.println("cuan"); break; case "xuan": System.out.println("xuan"); break; case "cxuan": System.out.println("cxuan"); break; default: break; }}} copy the codeCopy the code

Let’s decompile to see if our guess is correct

As you can see from the bytecode, the actual switch is hashCode and then compared using equals because strings can cause hash collisions.

Conditional compilation

What is conditional compilation? In fact, if you’ve ever used C or C++ you know that conditional compilation can be implemented with preprocessed statements.

So what is conditional compilation?

In general, all lines in the source program are compiled. However, it is sometimes desirable to compile a part of the content only if certain conditions are met, that is, to specify compilation conditions for a part of the content, which is conditional compile.

#define DEBUG #IFDEF DEBUUG /* code block 1 */ #ELSE /* code block 2 */ #ENDIFCopy the code

But there are no preprocessing and macro definitions in Java, so what do we do if we want to implement conditional compilation?

Conditional compilation can be achieved using a combination of final + if. See the code below

public static void main(String[] args) { final boolean DEBUG = true; if (DEBUG) { System.out.println("Hello, world!" ); } else { System.out.println("nothing"); }} Copy the codeCopy the code

What happens to this code? So let’s decompile it

We can see that we are using if… Else statement, but the compiler only compiles the DEBUG = true condition for us,

So conditional compilation of Java syntax is done by if statements that judge conditions to be constant, and the compiler will not compile code for us that branches to false.

assertions

Do you use assertions as everyday judgment criteria in Java?

Assertions: The so-called Assert keyword is a new feature added in JDK 1.4. It is mainly used during code development and testing to determine certain critical data, and if the key data is not what your program expected, the program will warn or quit. When the software is officially released, you can cancel the assertion part of the code. Is it also a grammar candy? I’m not going to tell you right now, but let’s look at how Assert works.

Static int I = 5; static int I = 5; static int I = 5; public static void main(String[] args) { assert i == 5; System.out.println(" If the assertion is ok, I'm printed "); } Duplicate codeCopy the code

If you want to enableassertion checking, you need to enable it with the -enableassertions or -EA switch. The underlying implementation of an assertion is an if judgment. If the assertion is true, nothing is done and the program continues. If the assertion is false, an AssertError is thrown to interrupt the program.

An assert makes an if judgment on a Boolean flag bit.

try-with-resources

Starting with JDK 1.7, Java introduced the try-with-resources declaration, simplifying the try-catch-finally statement to a try-catch, which is a syntactic sugar that is converted to a try-catch-finally statement at compile time. The new declaration contains three parts: a try-with-resources declaration, a try block, and a catch block. It requires variables defined in the try-with-resources declaration to implement the AutoCloseable interface so that their close methods can be called automatically in the system, replacing the closing of resources in finally.

See the code below

public class TryWithResourcesTest { public static void main(String[] args) { try(InputStream inputStream = new FileInputStream(new File("xxx"))) { inputStream.read(); }catch (Exception e){ e.printStackTrace(); }}} copy the codeCopy the code

We can look at the decomcompiled try-with-resources code

Try-with-resources are compiled using a try… catch… Finally statement, except that the compiler does the work for us, which makes our code simpler and eliminates boilerplate code.

String addition

If the result of concatenation can be determined at compile time, the string with the + sign concatenation will be optimized by the compiler. If the result of concatenation cannot be determined at compile time, the string will be concatenated directly using the AppEnd of StringBuilder. As shown in the figure below.

public class StringAppendTest { public static void main(String[] args) { String s1 = "I am " + "cxuan"; String s2 = "I am " + new String("cxuan"); String s3 = "I am "; String s4 = "cxuan"; String s5 = s3 + s4; }} Copy the codeCopy the code

The above code contains the result of two string concatenation, let’s decompilate to see

First let’s look at s1, because s1 has two constants to the right of the = sign, the concatenation of the two strings will be directly optimized to become I am Cxuan. S2 allocates a CXuan object in the heap space, so string splicing on both sides of the + sign will be directly converted into StringBuilder, and its append method will be called for splicing, and finally toString() method will be called for conversion into string.

Since the results of the two objects spliced by S5 cannot be determined during compilation, StringBuilder is directly used for splicing.

Learn the meaning of grammar sugar

In the Internet era, there are many innovative ideas and frameworks emerging in endlessly, but we should grasp the core of technology for learning. However, software engineering is a collaborative art, how to improve the quality of engineering, how to improve the efficiency of engineering is also our concern, since these syntactic sugar can help us write popular code in a better way, why should we programmers resist?

Grammar sugar is also a kind of progress, which is like writing a composition with you, plain English can tell the story clearly it is not beautiful language, dripping dripping vivid story more people like.

We need to embrace change while also mastering its ability to slay the dragon.

In addition, I have copied six PDFS by myself and put the ones I want here. Get all the PDFS as follows

Six PDF links

Author: cxuan