Writing high-quality code: 151 suggestions for improving Java programs (Chapter 1: classes, objects, and Methods ___ 36 ~ 40), java151
Recommendation 36: Use constructor code block to streamline programs
What is Code Block )? Multiple lines of code are encapsulated in braces to form an independent data body. The code set that implements a specific algorithm is a code block. Generally, the Code cannot run independently quickly and must have a running subject. There are four types of code blocks in Java:
I know that there is at least one constructor in a class (if not, the compiler will create a non-argument constructor for it). The constructor is called when an object is generated, now, let's look at the relationship between the constructor and the code block. When will the constructor execute the code block? Before answering this question, let's take a look at how the compiler handles the construction of code blocks and look at the following code:
1 public class Client36 {2 3 {4 // construct the code block 5 System. out. println ("construct code blocks"); 6} 7 8 public Client36 () {9 System. out. println ("execution without parameter construction"); 10} 11 12 public Client36 (String name) {13 System. out. println ("Construction with parameters executed"); 14} 15}
This is a very simple piece of code. It contains the Construction Code block, no parameter construction, and parameter construction. We know that the code block does not have independent execution capabilities. How does the compiler handle the construction of the code block? The compiler inserts the constructor code block into the frontend of each constructor. The code above is equivalent:
1 public class Client36 {2 3 public Client36 () {4 System. out. println ("construct code blocks"); 5 System. out. println ("execution without parameter construction"); 6} 7 8 public Client36 (String name) {9 System. out. println ("construct code blocks"); 10 System. out. println ("Construction with parameters executed"); 11} 12}
The front-end of each constructor is inserted with the constructor code block. Obviously, when an instance is generated using the new keyword, the constructor will first execute the constructor code block and then execute other code, that is: the constructor code block is executed first in each Constructor (note that the constructor code block is not run before the constructor, and relies on the execution of constructor, we can apply the constructed code block to the following scenarios:
The preceding two scenarios use the two features of constructing a code block: Running in each constructor and running in the constructor first. Using these features to construct code blocks can not only reduce the amount of code, but also make the program easier to read, especially when all constructors need to implement logic, in addition, when the logic is complex, you can compile multiple construction code blocks. Each code block completes different business logic (of course, the constructor should be as simple as possible, which is the basic principle) and is stored in the order of business, so that the JVM will execute in sequence when creating instance objects, implement modular creation of complex objects.
Suggestion 37: Create a code block and think about what you think
In the previous suggestion, we proposed to use a constructor code block to simplify the code. We also learned that the compiler will automatically Insert the constructor code block into each constructor. Then let's see if the compiler is smart enough, it can solve the real development problem for us. In this case, count the number of instance variables of a class. You can say, this is very simple. Add an object counter in each constructor to remedy it? Or we can use the code block constructed as described in the previous suggestion. We can see whether the following code is feasible:
1 public class Client37 {2 public static void main (String [] args) {3 new Student (); 4 new Student ("Zhang San"); 5 new Student (10 ); 6 System. out. println ("Number of instance objects:" + Student. getNumOfObjects (); 7} 8} 9 10 class Student {11 // object counter 12 private static int numOfObjects = 0; 13 14 {15 // construct the code block, the number of objects produced by the calculation is 16 numOfObjects ++; 17} 18 19 public Student () {20 21} 22 23 // the call with parameters constructs 24 public Student (String stuName) without Parameters) {25 this (); 26} 27 28 // The construction with parameters does not call the construction without parameters 29 public Student (int stuAge) {30 31} 32 // return in a JVM, how many instance objects have you created? 33 public static int getNumOfObjects () {34 return numOfObjects; 35} 36}
Is this code feasible? Can we calculate the number of instance objects? If the compiler inserts a constructor code block into each constructor, the constructor with String parameters may have problems. It will call the construction without parameters, then, the Student object generated by it will execute two construction code blocks: one is the Construction Code block of the no-argument constructor call, and the other is the execution of its own construction code block. In this way, the calculation is inaccurate, the main function actually generates three objects in the memory, but the result is indeed 4. But is that true? After running, the result is:
Number of instance objects: 3;
The number of instance objects is 3, so there is no problem with the program. Is it strange? Not surprisingly, the previous suggestion was that the compiler would insert the constructor code block into every constructor, but there was an exception that was not described: if you encounter the this keyword (that is, when the constructor calls another constructor), the constructor code block is not inserted. For our example, during compilation, the compiler finds that the constructor of the String parameter calls the construction without any parameters, so it does not insert the constructor code block, so it only executes the Construction Code block once.
Why is the Java compiler so smart? This also starts from the birth of the constructed code block. The constructed code block is used to extract the common amount of the constructor and reduce the code generation of each constructor. Therefore, java is wise to think that the code can be inserted into the constructor of this method, but other constructor calls are not inserted, so that each constructor only executes the constructor code block once.
Another point worth noting is that we should never think that this is a special case, so super will handle it like this. Actually, no. There is nothing special about the super method in block construction processing, the compiler only inserts the constructed code block into the super method and then executes it. This is the only difference.
Note: You can safely construct code blocks. Java already misses what you think.
Suggestion 38: use static internal classes to improve Encapsulation
The Nested Class in Java is divided into two types: Static internal Class (also called Static Nested Class) and internal Class (Inner Class ). This article mainly looks at static internal classes. What is a static internal class? It is an internal class and static (static) is a static internal class. The static modifier can be placed before the class only when it is a static internal class, the static class cannot be modified at any time.
The form of static internal classes is easy to understand, but why do we need static internal classes? This is because static internal classes have two advantages: enhanced class encapsulation and improved code readability. The following code is used to explain these two advantages.
1 public class Person {2 // name 3 private String name; 4 // family 5 private Home home; 6 7 public Person (String _ name) {8 name = _ name; 9} 10 11/* setter and getter methods for home and name */12 13 public static class Home {14 // home address 15 private String address; 16 // Home phone 17 private String tel; 18 19 public Home (String _ address, String _ tel) {20 address = _ address; 21 tel = _ tel; 22} 23/* address, tel's setter and getter methods are omitted */24} 25}
Specifically, the Person class defines a static internal class Home, which indicates "people's Home Information". Because the Home class encapsulates Home information, no need to define homeAddr in the Person class, homeTel and other attributes, which improves encapsulation. At the same time, we can analyze the strong association between Person and Home through code, that is, the semantics is enhanced and the readability is improved. Therefore, we will be very clear about its meaning during use.
Public static void main (String [] args) {// defines the Person p = new Person ("Zhang San"); // sets the Family Information p. setHome (new Home ("Beijing", "010 "));}
Define Michael Jacob and set her family information through the Person. Home class. Is this the same as what we are in the world? First, the primary information of the registrant, and then the classified information of the registrant. Maybe you have to ask, what is the difference between this and the class we generally define? What's attractive? As follows:
With so many explanations, you may think that the external class and the static internal class are Composition. This is incorrect. There is a strong relationship between the external class and the static internal class, this is only manifested in "literal", while the profound abstract meaning is based on the class design.
What is the difference between static class internal class and general internal class? The following is an example:
Recommendation 39: use an anonymous class Constructor
Read the following code to see if compilation is possible:
public static void main(String[] args) { List list1=new ArrayList(); List list2=new ArrayList(){}; List list3=new ArrayList(){{}}; System.out.println(list1.getClass() == list2.getClass()); System.out.println(list2.getClass() == list3.getClass()); System.out.println(list1.getClass() == list3.getClass()); }
Note that there is no link after the ArrayList variable: there is no link behind the list1 variable, There is a pair of {} Behind list2, and there are two nested {} Behind list3. Can this program be compiled? If it can be compiled, what is the result?
The answer is compilation, and three false values are output. List1 is easy to understand, that is, the instance object that lives in ArrayList. What does list2 and list3 represent?
(1) list2 = new ArrayList () {}: list2 represents the Declaration and assignment of an anonymous class. It defines an anonymous class inherited from ArrayList, there is no overwriting method. The code is similar:
// Define an internal class Sub extends ArrayList {}// declare and assign values to List list2 = new Sub ();
(2) list3 = new ArrayList () {{}}: This statement is a bit strange. With two pairs of {}, we can explain it separately, this is also the definition of an anonymous class, and its code is similar:
// Define an internal class Sub extends ArrayList that inherits the ArrayList {// initialization code block} // declaration and value assignment List list3 = new Sub ();
As you can see, there is only one more initialization block to function as a constructor. We know that a class must have a constructor, And the constructor name is the same as the class name, the question is: What is the constructor of the anonymous class? It has no name! Obviously, the initialization block is its constructor. Of course, the constructor blocks in a class can be multiple, that is, the following code appears:
List list4 = new ArrayList(){{} {} {} {} {}};
The above code is correct and there is no problem. Now it is clear that although the Anonymous class has no name, it can also have constructor. It uses the constructor block to replace the constructor, the above three outputs are obvious: although the parent class is the same, the class is still different.
Suggestion 40: Special constructors for anonymous classes
In the previous suggestion, we mentioned that although the Anonymous class has no name, there can be an initialization block to act as the constructor. Is this constructor totally different from a normal constructor? Let's take an example. Design a calculator and perform addition and subtraction. The Code is as follows:
1 public class Calculator {2 enum Ops {3 ADD, SUB 4}; 5 6 private int I, j, result; 7 8 // No parameter to construct 9 public Calculator () {10 11} 12 13 // 14 public Calculator (int _ I, int _ j) {15 I = _ I; 16 j = _ j; 17} 18 19 // set the symbol, whether to add or subtract 20 protected void setOperator (Ops _ ops) {21 result = _ ops. equals (Ops. ADD )? I + j: I-j; 22} 23 24 // obtain the calculation result 25 public int getResult () {26 return result; 27} 28 29}
The intention of the code is to pass two int-type numbers through the constructor, then perform operations based on the set operator (addition or subtraction), and write a client call:
public static void main(String[] args) { Calculator c1 = new Calculator(1, 2) { { setOperator(Ops.ADD); } }; System.out.println(c1.getResult()); }
The code for this anonymous class is very clear: receive two parameters 1 and 2, then set an operation symbol, calculate its value, and the result is 3, which is undoubtedly, but there is a hidden question: Which constructor is called when an anonymous class declaration with parameters is called? Let's simulate this program:
// Add extends Calculator {setOperator (Ops. ADD) ;}// override the constructor of the parent class public Add (int _ I, int _ j ){}}
Is the Anonymous class equivalent to the Add class? Some people may say: The above only adds a name for the anonymous class, and nothing else is changed. It must be equivalent. There is no doubt, well, compile a client to call the Add class method. The code is omitted, because it is easy to Add new, and then call the getResult method of the parent class. After testing, the output result is 0 (why is it 0? This is easy, and there is no value for parameter construction ). This indicates that the two are not equivalent, but what is the reason?
Because of the special processing mechanism of the constructor of an anonymous class, all constructors of a general class (that is, a class without a display name) call the non-argument constructor of the parent class by default, because the Anonymous class has no name, it can only be replaced by the Construction Code block, so there is no argument or no argument for the constructor. It directly calls the same parameter constructor of the parent class during initialization, then we call our own construction code block, that is, the above Anonymous class is equivalent to the following code:
// Add extends Calculator {setOperator (Ops. ADD) ;}// override the constructor of the parent class public Add (int _ I, int _ j) {super (_ I, _ j );}}
It will first call the constructor with two parameters in the parent class, instead of the non-argument constructor. This is the difference between the constructor of the anonymous class and the normal class, but it is still worth pondering, because its processing mechanism conforms to the habit, I pass two parameters, that is, I want to call the parent class to have two parameter constructor and then execute my own constructor, the Processing Mechanism of Java is also like this.