Writing high-quality code: 151 suggestions for improving Java programs (Chapter 1: classes, objects, and Methods ___ 31 ~ 35), java151
If you read many books without thinking about them, you will feel that you know a lot.
When you read a lot of books and think about it, you will feel that you do not understand more and more.
--- Voltaire
In the Object-Oriented Programming (OOP) World, classes and objects are the descriptive tools in the real world, and methods are the presentation of behaviors and actions, encapsulation, inheritance, and polymorphism are the main implementation methods. This chapter describes the rules, restrictions, and suggestions of Java objects, objects, and methods.
Recommendation 31: do not have implementation code in the interface
Are you depressed when you see this title? Is there any implementation code in the interface? How is this possible? Indeed, the interface can declare constants, declare abstract methods, and inherit the parent interface, but there is no specific implementation, because the interface is a Contract and a framework protocol, this indicates that its implementation classes are of the same type or a collection of similar features. For general programs, the interface does not have any implementation, but it is an exception in those special programs. Read the following code:
1 public class Client31 {2 public static void main (String [] args) {3 // call interface implementation 4 B. s. doSomeThing (); 5} 6} 7 8 // Code 9 interface B {10 public static final S s = new S () {11 public void doSomeThing () exists in the interface () {12 System. out. println ("I implemented in the interface"); 13} 14}; 15} 16 17 // implemented interface 18 interface S {19 public void doSomeThing (); 20}
Take a closer look at the main method and pay attention to the B interface. It calls an interface constant. If no display implementation class is implemented, it prints the result. The s constant in interface B (the interface is S) where is it implemented? The answer is in interface B.
In interface B, a static constant s is declared. Its value is an instance object of an Anonymous internal Class (Anonymous Inner Class), that is, the Anonymous internal Class (of course, you can also skip the Anonymous, it is allowed to implement internal classes directly in the interface) to implement the S interface. As you can see, there are also implementation code in the interface!
This is really good, very powerful, but in general projects, such code is strictly prohibited, the reason is very simple: This is a very bad coding habit, what is the interface used? An interface is a contract, which not only limits implementation, but also guarantees that the provided services (constants and methods) are stable and reliable. If you write the implementation code to the interface, the interface will be bound with factors that may change, which will cause the implementation to be no longer stable and reliable, and may be discarded, changed, and reconstructed at any time. Therefore, though the interface can be implemented, it should be avoided.
Note: The implementation code cannot appear in the interface.
Recommendation 32: Make sure to declare the static variables and assign values first.
Is the title as depressing as the title of the previous suggestion? What is a variable? Must it be declared first and then assigned a value? Isn't all variables in Java used after being declared first? Can I declare it after using it? Let's look at an example. The Code is as follows:
1 public class Client32 { 2 public static int i = 1; 3 4 static { 5 i = 100; 6 } 7 public static void main(String[] args) { 8 System.out.println(i); 9 }10 }
This program is very simple. It outputs 100. Yes, it is indeed 100. Let's make a slight modification. The Code is as follows:
1 public class Client32 { 2 static { 3 i = 100; 4 } 5 6 public static int i = 1; 7 8 public static void main(String[] args) { 9 System.out.println(i);10 }11 }
Note that the declaration and value assignment of variable I have been changed. The question is: can this program be compiled? If it can be compiled, what is the output? Note that the variable I is declared after being used (that is, assigned a value.
The answer is: it can be compiled. If there is no such question, the output result is 1. Yes, the output is 1, not 100. After the position is changed, the output changes, and the variable I is first used and then declared. Is it reversed?
This requires the birth of static variables. Static variables are allocated to the Data Area during class loading. They only have one copy in the memory and are not allocated multiple times, all subsequent value assignment operations change values, and the address remains unchanged. We know that the JVM initialization variable declares the space first, and then assigns a value, that is, it is executed separately in the JVM, which is equivalent:
Int I; // allocate space
I = 100; // value assignment
Static variables are first loaded during class initialization. JVM searches for all static declarations in the class and allocates space. Note that at this time, only the address space is allocated, no value is assigned. Then, the JVM will execute the value based on the sequence of static values (including static class values and static block values) in the class. For the program, the int type address space is first declared, and the address is passed to I, and then the value assignment operation is executed according to the class sequence. First, the static block I = 100 is executed, then execute I = 1, and the final result is I = 1.
Oh, that's all. What if multiple static blocks continue to assign values to I? I is equal to 1, of course, and who is at the bottom has the final right to decide.
Some programmers like to put variable definitions at the bottom of the class. If this is an instance variable, there is no problem, but if it is a static variable and it is still assigned a value in the static block, this result is different from the Expected One. Therefore, it is a good coding style to follow the general Java Development specifications "declare variables first and assign values to use variables.
Note: It is not a nonsense to reiterate that variables must be declared and used first.
Recommendation 33: do not override static methods
We know that in Java, Override can be used to enhance or weaken the methods and behaviors of the parent class, but overwriting is for non-static methods (also called instance methods, methods that can be called only when an instance is generated. static methods (static methods, also called class methods) cannot be used. Why? Let's take an example. The Code is as follows:
1 public class Client33 {2 public static void main (String [] args) {3 Base base = new Sub (); 4 // call non-static method 5 base. doAnything (); 6 // call the static method 7 base. doSomething (); 8} 9} 10 11 class Base {12 // I am a parent class static Method 13 public static void doSomething () {14 System. out. println ("I Am a static method of the parent class"); 15} 16 17 // non-static method of the parent class 18 public void doAnything () {19 System. out. println ("I am a non-static method of the parent class "); 20} 21} 22 23 class Sub extends Base {24 // static method with the same name as subclass and same parameter 25 public static void doSomething () {26 System. out. println ("I'm a subclass static method"); 27} 28 29 // override the parent class non-static method 30 @ Override31 public void doAnything () {32 System. out. println ("I Am a subclass non-static method"); 33} 34}
Check out the program. The doAnything method of the subclass overwrites the parent class method. There is no problem. What about the doSomething method? It is the same as the method name of the parent class, and the input and output are also the same. In principle, it should be overwritten, but is it overwritten? Let's look at the output result:I am a subclass non-static method. I am a parent static method.
This result is confusing. It also calls the subclass method. When a parent class method is executed, the difference between the two methods is that there is no static modification, but different results are obtained. Why?
We know that an instance object has two types: the surface Type (Apparent Type) and the Actual Type (Actual Type). The surface Type is the declared Type, and the Actual Type is the Type when the object is generated, for example, the surface type of the variable base is Base and the actual type is Sub. For non-static methods, it is executed based on the actual object type, that is, the doAnything method in the Sub class is executed. Static methods are special. First, static methods do not depend on instance objects. They are accessed by class names. Second, static methods can be accessed through objects, if the static method is accessed through an object, JVM searches for the entry of the static method through the object surface type, and then executes it. Therefore, it is not surprising that the above program prints "I am a non-static method of the parent class.
In the subclass, construct the same method name, input parameter, output parameter, and access permission as the parent class method (the permission can be extended), and the parent class and subclass are static methods, this behavior is called hidden (Hide). It is different from overwriting:
(1) different forms of representation: Hiding is used for static methods. Overwriting is used for non-static methods. In code, the @ Override annotation can be used for overwriting and cannot be used for hiding.
(2) different responsibilities: the purpose of hiding is to discard the static method of the parent class and reproduce the subclass method. For example, in our example, Sub. doSomething appears to cover the Base of the parent class. the doSomething method, that is, I expect that the static method of the parent class should not destroy the business behavior of the subclass. Overwriting is to enhance or weaken the behavior of the parent class and continue the responsibility of the parent class.
With so many explanations, let's look back at the title of this suggestion. Static methods cannot be overwritten, and the last sentence can be continued. Although they cannot be overwritten, they can be hidden. By the way, it is not a good habit to access static methods or static attributes through instance objects. It brings a "bad taste" to the code. We suggest you read it.
Recommendation 34: constructor should be simplified as much as possible
We know that the object generated with the new keyword will inevitably call the constructor. the constructor's simplicity directly affects the cumbersome creation of instance objects. in project development, we usually make the constructor as simple as possible, do not throw exceptions as much as possible, and do not do complex operations and other specifications as much as possible. What if a constructor is indeed complicated? Let's look at a piece of code:
1 public class Client34 {2 public static void main (String [] args) {3 Server s = new SimpleServer (1000 ); 4} 5} 6 7 abstract class Server {8 public final static int DEFAULT_PORT = 40000; 9 10 public Server () {11 // obtain the port number 12 int port = getPort (); 13 System. out. println ("port:" + port); 14/* Listen */15} 16 17 // The port number is provided by the subclass, perform availability check 18 protected abstract int getPort (); 19} 20 21 class SimpleServer exte Nds Server {22 private int port = 100; 23 24 // initialize and pass a port number 25 public SimpleServer (int _ port) {26 port = _ port; 27} 28 29 // check whether the port is valid. If the port is invalid, use the default port. Here, use a random number to simulate 30 @ Override31 protected int getPort () {32 return Math. random ()> 0.5? Port: DEFAULT_PORT; 33} 34 35}
This code is a simple simulation program of a service class. The Server class implements the Server creation logic. The subclass can create a listening port service by passing a port number when generating an instance object, the intention of this Code is as follows:
It seems reasonable. Let's take a closer look at the code, which is indeed consistent with our intention. Then we try to run it multiple times. The output result is either "port: 40000" or "Port Number: 0 ", there will never be" port: 100 "or" port: 1000 ", which is strange. 40000 is okay to say, how did that 0 come out? What is the problem with neglect?
To solve this problem, we should first talk about how subclass is instantiated. When the subclass is instantiated, the parent class will be initialized first (note that initialization is used here, instead of generating the parent class Object), that is, the variables of the parent class will be initialized, And the constructor of the parent class will be called, then, the variable of the subclass is initialized, the constructor of the subclass is called, and an instance object is generated. After learning the relevant knowledge, let's take a look at the above program. The execution process is as follows:
Finally, it is clear that no value is assigned to the return value of the getPort method during class initialization. The port only obtains the default initial value (the default initial value of the int type instance variable is 0 ), therefore, the Server always listens to port 40000 (Port 0 is meaningless ). This problem is caused by the initial sequence of class elements, which is due to the complexity of the constructor. The constructor is used as the initialization variable to declare the context of the Instance. This is a simple implementation and there is no problem, but our example implements a complicated logic, this is not suitable in the constructor.
The problem is known, and the modification is very simple. All the implementations in the non-argument constructor of the parent class are moved to a method called start, and the SimpleServer class is initialized, you can call the start method to start the server, which is concise and intuitive. This is also the implementation method of most JEE servers.
Note: The constructor is simplified and simplified. It should be a "one-glance" realm..
Suggestion 35: Avoid initializing other classes in the constructor
A constructor is a code that must be executed during class initialization. It determines the efficiency of class initialization. If the constructor is complex and associated with other classes, unexpected problems may occur, let's look at the following code:
1 public class Client35 {2 public static void main (String [] args) {3 Son son = new Son (); 4 son. doSomething (); 5} 6} 7 8 // parent class 9 class Father {10 public Father () {11 new Other (); 12} 13} 14 15 // related class 16 class Other {17 public Other () {18 new Son (); 19} 20} 21 22 // subclass 23 class Son extends Father {24 public void doSomething () {25 System. out. println ("Hi, show me Something! "); 26} 27}
This code is not complex. It only initializes other classes in the constructor. What is the running result of this code? "Hi, show me Something!" is printed! ?
The answer is that this Code cannot be run. The StatckOverflowError error is reported and the Stack memory overflow occurs. This is because son's no-argument constructor is called when the variable Son is declared, by default, JVM calls the constructor of the parent class, and then Father initializes the Other class, and the Other class calls the Son class, so an endless loop is born, know that the memory is stopped after consumption.
You may think that such a scenario will not appear in development. Let's think about this scenario. Father is provided by the framework, and Son class is the extended Code Compiled by ourselves, the Other class is the interception class required by the Framework (Interceptor class, Handle class, or Hook method). Let's look at the problem. Isn't this possible?
You may think that such a scenario will not occur. This problem can be found as long as the system runs, and cannot affect the project.
The reason is that the code we show here is relatively simple, and it is easy to see at a glance. There can be more than one or two constructors in a project, and the relationship between classes will not be so simple, it is impossible for all people to understand whether there are defects at a Glance. The best solution to this problem is: do not declare initialization of other classes in the constructor, develop good habits.