1. Replace the constructor with a static factory
Summary:
The advantage mainly comes from the flexibility of static factory. It has a name and can return flexible types, including subclasses, hidden classes and extensible classes. For readability, the best practice is to follow the naming convention from, of, valueof, instance, getInstance, crite, newinstance, GetType, newtype
advantage:
- There is a name. The disadvantage of constructor is that it has many parameter lists, has order, and is easy to be used incorrectly
- You do not have to create a new object. For example, meta mode, constant pool design, etc.
- You can return a hidden type. Refer to the implementation of Collections (the associated class of Collection, because the JDK 1.8 interface does not support static methods, it generally implements the associated class encapsulation related static methods), in which a variety of different Collections are implemented, but these Collections are hidden from the outside, so it is not necessary to provide a large number of independent classes, which makes the API more complex.
- You can return different types of objects. Different types of objects are returned according to different conditions of the passed in parameters.
- The returned class is extensible. For example, JDBC, the returned classes can expand a variety of database implementations.
Disadvantages:
- Not subclassible. Encourage composition rather than inheritance.
- Hard to find. Compared with constructors, the naming of static factory methods is more diverse, which can be solved by convention. Common naming methods include from, of, valueof, instance, getInstance, crite, newinstance, GetType and newtype
2. Consider using constructors for multiple parameters
Summary:
It is suitable for a large number of optional parameter scenarios. The streaming API has good readability, convenient validity check and convenient initial value setting. But the builder class is more troublesome to write. The general implementation is as follows:
// Builder Pattern public class NutritionFacts { private final int servingSize; private final int servings; private final int calories; private final int fat; private final int sodium; private final int carbohydrate; public static class Builder { // Must be a non empty parameter private final int servingSize; private final int servings; // Default values are supported private int calories = 0; private int fat = 0; private int sodium = 0; private int carbohydrate = 0; public Builder(int servingSize, int servings) { // Perform parameter verification Objects.requireNonNull(servingSize); Objects.requireNonNull(servings); this.servingSize = servingSize; this.servings = servings; } public Builder calories(int val) { calories = val; return this; } public Builder fat(int val) { fat = val; return this; } public Builder sodium(int val) { sodium = val; return this; } public Builder carbohydrate(int val) { carbohydrate = val; return this; } public NutritionFacts build() { return new NutritionFacts(this); } } private NutritionFacts(Builder builder) { servingSize = builder.servingSize; servings = builder.servings; calories = builder.calories; fat = builder.fat; sodium = builder.sodium; carbohydrate = builder.carbohydrate; } }
advantage:
- Support for expanding a large number of optional parameters. The deficiency of static factory and constructor is to expand the support of a large number of optional parameters. For example, it is solved by overlapping constructors, but it will only make the code difficult to write, use and read.
- Remain immutable. JavaBean's design pattern can support optional parameter extension, but it destroys the immutability of classes. In this way, more thread safety issues must be considered. And it can cause inconsistent states -- classes cannot ensure consistency by verifying the validity of constructor parameters.
- Effectiveness check. You can check the validity of parameters during build in the method.
Insufficient:
- Constructors must be created with some overhead.
- The constructor code is troublesome, especially when there are only a few parameters.
3. Use enumeration class to strengthen singleton attribute
This implementation is similar to the public domain, but it is more concise. The advantage of enumeration class is that it provides a serialization mechanism to ensure that it is serialized only once even in the face of serialization attack and reflection attack.
// Enum singleton - the preferred approach public enum Elvis { INSTANCE; public void leaveTheBuilding() { ... } }
4. Strengthen non instantiation through private constructor
We often think of some tool classes Utils, static classes. We don't want it to be instantiated, but Java will provide a default parameterless constructor (if you don't provide any constructor). The best practice in this scenario is to prevent instantiation by privatizing the constructor.
// Noninstantiable utility class public class UtilityClass { // Suppress default constructor for noninstantiability private UtilityClass() { // To prevent internal error calls, you can actively throw exceptions throw new AssertionError(); } ... // Remainder omitted }
5. Give priority to referencing resources by dependency injection
This is a very common pattern, so that no one knows that it is dependency injection. It provides the flexibility to reference resources
Control reversal
In addition, it is worth mentioning that it is usually associated with control inversion. Dependency injection is a way to realize control inversion. Even if it is set through a simple constructor and set method, it gives the dependency from the program itself (referring to this class, such as SpellChecker) to the external caller.
The disadvantage of dependency injection is that it will make dependency complex and large projects chaotic, but modern frameworks have solved this problem very well, such as spring. Dependency injection and control inversion transfer the original dependency settings from the program to the container (user), so that the code is closed to modification, and can simply support the expansion of other dependencies.
// Dependency injection provides flexibility and testability public class SpellChecker { private final Lexicon dictionary; public SpellChecker(Lexicon dictionary) { this.dictionary = Objects.requireNonNull(dictionary); } }
6. Avoid creating unnecessary objects
Unnecessary creation of objects
Unnecessary objects should not be created in the following two cases:
- The object is immutable and reusable
- Object creation costs are high
The compiler usually does a lot of things for us, but it often causes unnecessary object creation, such as:
// 1. The '' itself will create an object, and if you use quotation marks, repeated strings will directly reuse the contents of the constant pool String s = new String("bikini"); // DON'T DO THIS! // 2. Regular matching seems OK, but a new pattern will be created every time it is called. The pattern should be compiled and reused // Performance can be greatly improved! static boolean isRomanNumeral(String s) { return s.matches("^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"); } // 3. Automatic packing and unpacking, common problems // Hideously slow! Can you spot the object creation? private static long sum() { Long sum = 0L; for (long i = 0; i <= Integer.MAX_VALUE; i++) sum += i; return sum; }
Static plants are preferred
Static factory methods can flexibly realize the reuse of such objects, such as valueOf, so always give priority to calling static factory methods.
When to use connection pooling
On the other hand, it should be noted that for modern compilers, the cost of creating objects is actually very small, so in most cases, it is not necessary to manage memory with thread pool, except for one case - the cost of object creation is very high. Typical examples are JDBC connection pool, thread pool and so on.
7. Eliminate expired object references - avoid memory leaks
Let's take a look at the implementation of a stack. It seems that there is no problem at all.
// Can you spot the "memory leak"? public class Stack { private Object[] elements; private int size = 0; private static final int DEFAULT_INITIAL_CAPACITY = 16; public Stack() { elements = new Object[DEFAULT_INITIAL_CAPACITY]; } public void push(Object e) { ensureCapacity(); elements[size++] = e; } public Object pop() { if (size == 0) throw new EmptyStackException(); return elements[--size]; } /** * Ensure space for at least one more element, roughly * doubling the capacity each time the array needs to grow. */ private void ensureCapacity() { if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size + 1); } }
In fact, when the elements in the stack are pop, these elements will never be recycled. Therefore, you need to set the element dropped by pop (i.e. size - 1) to null after pop.
If you need to manage your own memory
Java's memory leak problem seems very tricky and hidden, but the author points out that it is not necessary to deliberately empty objects every time.
Emptying objects is an exception, not a specification. So under what circumstances do you need to pay attention to memory leakage—— The answer is when the class manages its own memory.
For example, the above stack uses an array as the underlying container.
For example, maps in threadLocal and various maps that store data in classes are prone to this problem.
For example, in monitor and observer mode, cancel is not displayed after registering to monitor.
Weapon: weak reference
ThreadLocal uses weak references. For example, there is such a container in JDK, such as WeakHashMap. Their core is that the life cycle of reference objects is determined by external references, not value references. Weak references determine that when an object has only weak references, it will be recycled once it is scanned by gc.
8. Avoid using termination methods
The finalizer is executed before gc collection. Because the time of gc is uncertain, the call time of this method can not be expected. It is conceivable that if it is used to recycle resources, it may cause serious resource consumption.
There are two scenarios for the rational use of termination methods:
- As a way to recycle resources, it's better to recycle slowly than not
- Recycle local objects. Because of the local object, the gc of the JVM cannot detect it, so the local object related to the class can be recycled in the termination method.
In addition, there is a hidden security risk in the termination method: the termination method attack is roughly the object that failed to be created (for example, the creation failure throws an exception in the constructor). It should not exist, but the malicious subclass can implement the termination method and record the object reference in the static domain, so that it can not be recycled. The typical way to avoid method rewriting is to implement a final null termination method.
9. Try with resources takes precedence over try finally
Let's look at the way to try finally:
// try-finally is ugly when used with more than one resource! static void copy(String src, String dst) throws IOException { InputStream in = new FileInputStream(src); try { OutputStream out = new FileOutputStream(dst); try { byte[] buf = new byte[BUFFER_SIZE]; int n; while ((n = in.read(buf)) >= 0) out.write(buf, 0, n); } finally { out.close(); } } finally { in.close(); } }
The problem with this implementation is:
- Need to close manually, easy to forget
- Code ugliness
- It is easy to swallow exceptions. The exception thrown by the first out.close will be swallowed
The best practice is try with resources, which requires relevant resource classes to implement the autoclosable method, which solves all the above problems.
// try-with-resources on multiple resources - short and sweet static void copy(String src, String dst) throws IOException { try (InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dst)) { byte[] buf = new byte[BUFFER_SIZE]; int n; while ((n = in.read(buf)) >= 0) out.write(buf, 0, n); } }