Effective Java Chapter 2 creating and destroying objects

1. Replace the constructor with the static factory method //Example public static Boolean valueOf(boolean b){ return b ?...

1. Replace the constructor with the static factory method
//Example public static Boolean valueOf(boolean b){ return b ? Boolean.TRUE : Boolean.FALSE; }

This method has five advantages

  1. Method has a name. The returned object can be described more accurately than the constructor.
  2. You don't have to create a new object every time you call.
  3. You can return objects of any subclass type of the original return type.
  4. The class of the object returned by the method can change with each call. (by modifying the parameter value of the static factory method)
  5. The class of the object returned by the method may not exist when writing the class containing the static factory method. (service provider framework, through reflection).
    This method has two disadvantages
  6. A class cannot be subclassed without a public or protected constructor. But this is a blessing in disguise. Programmers tend to use composition rather than inheritance, which is what immutable types need. If your class design is for inheritance, you can't set the constructor private.
  7. The second disadvantage of the static factory approach is that it is difficult for programmers to find them. They are not identified in the API documentation.

summary
Static factories and public constructors are useful, and most of the time static factories are more appropriate. Therefore, the first reaction should not be to provide a public constructor without considering the static factory.

2. Consider using the builder when multiple constructor parameters are encountered

Class to implement the Builder pattern.

//Builder mode //For an account, account number and password are required parameters, while gender and email are optional parameters public class AccountFacts{ private final int accountNumber; private final int password; private final int sex; private final String email; public static class Builder{ //Required parameter private int accountNumber; private int password; //Optional parameter, to set the default value private int sex = 1;//The default is male private String email = "null"; public Builder(int accountNumber,int password){ this.accountNumber = accountNumber; this.password= password; } public Builder sex(int val){ sex = val; return this; } public Builder email(String val){ email = val; return this; } public AccountFacts build(){ return new AccountFacts(this); } } private AccountFacts(Builder builder){ accountNumber=builder.accountNumber; password=builder.password; sex=builder.sex; email=builder.email; } }

Client code

//The account number is 1234, the password is 123, the gender is male, and the email is not filled in AccountFacts zcyAccount = new AccountFacts.Builder(1234,123).sex(1).build();

The Builder pattern also applies to class hierarchies. But the code is relatively long, so we won't share it here. It is recommended to read the details in the book.

The advantage of builder over constructor is that it supports multiple variable parameters. At the same time, a method can be called multiple times to pass multiple parameters into a domain.
The disadvantage is that in order to create an object, you must first create its builder, which has some overhead and affects performance. At the same time, the code is lengthy.

summary
If the constructor or static factory of a class has multiple parameters, the Builder pattern is a good choice.

3. Strengthen the Singleton attribute with a private constructor or enumeration type

Singleton: a class that is instantiated only once.

Private constructor (singleton mode - hungry man)

public class BBA{ private BBA(){...};//Private constructor private static final BBA INSTANCE = new BBA();//Static, immutable public static BBA getInstance(){ return INSTANCE; } }

Private constructor (singleton mode - lazy)

public class BBA{ private BBA(){...};//Private constructor private static volatile BBA instance;//Declare only, do not create objects public static BBA getInstance(){ //Create the object when calling getInstance() if(instance == null){ sychronized(A.class){ if(instance==null){ instance = new BBA(); } } } return instance; } }

enumeration

//Declare an enumeration type that contains a single element public enum Elvis{ INSTANCE; }

summary
Singleton mode is flexible, but it has disadvantages: when serializing, it must declare that all instance fields are transient. Otherwise, each time a serialized instance is deserialized, a new instance will be created.
The enumeration type of single element is more brief, and the serialization mechanism is provided free of charge. It can absolutely prevent multiple instantiations even in the face of complex serialization and reflection attacks. Although it has not been widely used, single element enumeration types often become the best way to implement Singleton.

4. Strengthen the ability of non instantiation through private constructor

Classes that only contain static methods or static fields, such as java.lang.Math or java.util.Arrays, do not want to be instantiated because instantiation has no meaning to them.
In the actual operation process, it is impossible to force the class not to be instantiated by making it an abstract class.

The reasons are as follows:

  • The class can be subclassed, and the subclass can be instantiated
  • Doing so will mislead users into thinking that this kind is specially designed for inheritance

Solution: let the class contain a private constructor. (in this case, the compiler will not automatically generate the default constructor)

//Classes that cannot be instantiated public class UtilityClass{ private UtilityClass(){ throw new AssertionError(); } }

Of course, this has a side effect, which makes a class unable to be subclassed.

5. Give priority to dependency injection to reference resources

Many classes depend on one or more underlying resources. That is, this class may depend on instances of other classes.
The best approach is to pass the resources that the instance depends on into the constructor when a new instance is created. This is a form of dependency injection.

public class SpellChecker { private final Lexicon dictionary; public SpellChecker(Lexicon dictionary) { this.dictionary = Objects.requireNonNull(dictionary); } }

Advantages: the object resources of dependency injection are not changeable, so multiple clients can share dependent objects. At the same time, it greatly improves the flexibility, reusability and testability.
Disadvantages: dependency injection will lead to messy structure of large projects, which can be solved by using dependency injection framework, such as Dagger, Guice and Spring.

6. Avoid creating unnecessary objects
  • In general, it's better to reuse a single object rather than create a new object with the same function every time you need it.
    Counterexample: String s = new String("BMW");
    Improvement: String s = "BMW";

  • Try to call static factory methods instead of using constructors. Because the constructor creates a new object every time it is called, the static factory method does not.

  • When the creation cost of some objects is much higher than that of other objects, it is recommended to cache them for reuse.

    class Test{ //Omit constructor public int calculateThatValue(){ int value;//Suppose this is a value that will remain unchanged if assigned, and it can only be calculated after very complex operations //Suppose this is a very complex piece of code that needs to run for a long time return value; } }

    improvement:

    class Test{ private final int VALUE_COSTLONGTIME;//Suppose this is a constant value that can only be calculated through very complex operations //Omit constructor //A static method block that is executed when the class is first loaded static{ int value; //Suppose this is a very complex piece of code that needs to run for a long time VALUE_COSTLONGTIME = value;//Assign the calculated value to the final constant } }
  • When object reuse is not obvious, consider using adapters (also known as views) to avoid creating unnecessary objects.
    An adapter refers to an object that delegates functions to a backup object, thereby providing an alternative interface for the backup object. Because the adapter has no other status information except the backup object, it does not need to create multiple adapter instances for the specific adapter of a given object.
    For example, the keySet method implementation of HashMap:

    public Set<K> keySet() { Set<K> ks = keySet; return (ks != null ? ks : (keySet = new KeySet())); }

    You can see that the first line of implementation code is to assign the keyset to ks, followed by a ternary expression after return. When ks is not empty, ks is returned, otherwise a keyset is created to return. (that is, create a keyset object to return when the keyset is empty)
    Definition of keySet in HashMap: transient volatile set < k > keySet = null;
    The member variable modified by volatile keyword is forced to reread the value of the member variable from the shared memory every time it is accessed by the thread. Moreover, when the member variable changes, the thread is forced to write the change value back to the shared memory.

  • Avoid unconscious use of automatic packing.
    Basic type first, beware of uninteresting automatic packing
    Example: Long type declared as Long

Summary:
Don't misunderstand it to avoid creating objects as much as possible. The creation and recycling of small objects is very cheap. By creating additional objects, it is usually a good thing to improve the clarity, simplicity and functionality of the program.
At the same time, it is not a good practice to avoid creating objects by maintaining your own object pool. Unless the objects in the pool are very heavyweight (such as database connection pool). Thanks to the highly optimized garbage collector of modern JVM s, maintaining the object pool can easily consume more performance than creating it directly.

7. Eliminate expired object references

Here are three common sources of memory leaks:

  • Unconscious object retention
    In the following program, objects that pop up from the stack will not be garbage collected. Even if the program no longer references these objects, they will not be recycled. Expired references to these objects are maintained inside the stack.

    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]; } private void ensureCapacity(){ if(elements.length == size) elements = Arrays.copyOf(elements,2*size+1); } }

    As you can see, any reference outside the "active part" of the elements array is out of date. The active part refers to those elements in elements whose subscript is less than size.

    Repair method:

    //Once the object reference expires, clear the reference. //The element of the pop-up stack is expired. Empty it public Object pop(){ if(size==0) throw new EmptyStackException(); Object result = elements[--size]; elements[size] = null;//Release expired references return result ; }

    Note that:
    1. Emptying objects should be an exception, not a normative behavior. When each variable is defined with the most compact scope, these object references will be terminated by the variable containing it, rather than manually.
    2. * * as long as the class manages its own memory, programmers should be alert to memory leakage** Once an element is released, any object references it contains should be cleared.

  • cache
    When an object reference is put into the cache, it is easy to forget, which will cause it to remain in memory for a long time after it is no longer used.
    Solution: there is a reference to the key of an item outside the cache.

  • Listeners and other callbacks

Summary:
Since memory leaks usually do not show obvious failure, they can exist in a system for many years. Often, only by carefully examining the code or with the help of Heap analysis tools can we find the problem of memory leakage. Therefore, it is best to know how to predict such problems and prevent them before using memory leaks.

8. Avoid using termination and removal methods

The finalizer is unpredictable and dangerous, which is generally unnecessary.

In Java 9, the finalization method is replaced by a cleaner. Removal methods are not as dangerous as termination methods, but they are still unpredictable, slow, and generally unnecessary.

  • The disadvantage of termination and clarity methods is that they cannot be guaranteed to be implemented in time. This means that time critical tasks should not be completed by termination methods or cleanup methods.
  • The Java language specification not only does not guarantee that termination methods or cleanup methods will be executed in time, but also does not guarantee that they will be executed at all. Therefore, you should never rely on finalization or cleanup methods to update important persistent states.
    For example, if you ignore the uncapped exception thrown during the termination process, the termination process of the object will also terminate. An uncapped exception will put the object in a corrupt state. If another thread attempts to use this corrupted object, any uncertain behavior may occur.
  • Using the finalization method and cleanup method has a very serious performance loss.
  • The finalization method has serious security problems. The exception thrown by the constructor was enough to prevent the object from continuing to exist. With the existence of termination methods, this cannot be done. So write an empty final finalize method. (of course, if it is a final class, it is not necessary, because this attack method is realized by running the termination method of malicious subclasses. The final method has no subclasses and naturally does not need to be processed.)

Q: If the resources encapsulated in the class object (such as files or threads) do need to be terminated, what should we do so that we don't need to write termination methods or clear methods?
A: Let the class implement the autoclosable interface. The client calls the close method when each instance is no longer needed. Generally, try with resources is used to ensure termination, even if exceptions are encountered.

Benefits of termination and cleanup methods:

  • When the resource owner forgets to call the close method, it can act as a "safety net". Although there is no guarantee to clean up resources in time, it is better to clean up later than not. Of course, if we want to write such a safety net termination method, we must seriously consider whether this protection is worth paying such a price.
  • The native peer of the processing object, which is delegated to the native object by the ordinary object through the local method. Therefore, it will not be collected by the garbage collector. If it has no key resources or does not affect performance, it can be handed over to the termination method or cleanup method. If there are key resources or performance is unacceptable, it should have a close method.

Summary:
Do not use the cleanup method unless it is used as a safety net or to terminate non critical local resources. For releases before Java 9, try not to use the termination method. If the termination method or cleanup method is used, pay attention to its uncertainty and performance consequences.

9. Try with resources takes precedence over try finally

Both try and finally blocks may throw exceptions. If exceptions are thrown at the same time, the second exception in finally block will overwrite the exception of try block, so that we cannot trace the source of program error. For example, the following code example:

@Test public void tryFinallyTest() throws Exception { try { throw new Exception("try block Exception"); } finally { throw new Exception("finally block Exception"); } }

As can be seen from the execution result, the exception in the try block is not displayed.

Solution:
Since java7, the try with resource syntax has been added and the autoclosable interface has been introduced. As long as the class or interface of the relevant resource implements or inherits this interface, the resource can be closed automatically without explicitly calling the close() method, and the above "exception coverage" will not occur.
We use two forms of try statements to test at the same time, and the throwException() method accumulates the exceptions thrown.

public class TryTest implements AutoCloseable { private int count = 0; @Test public void tryResourceTest() throws Exception { try(TryTest test = new TryTest()) { test.throwException(); } } @Test public void tryFinallyTest() throws Exception { TryTest test = new TryTest(); try { test.throwException(); } finally { test.close(); } } public void throwException() throws Exception { count++; throw new Exception("the No." + count + " Exception occurs"); } @Override public void close() throws Exception { throwException(); } }

Execution result of try finally statement block

Execution result of try with resources statement block

We can successfully see the first exception we really want to see, and the second exception is marked as suppression.

Summary:
Therefore, when dealing with resources that must be closed, we should always give priority to try with resources rather than try finally. In this way, the code will be more concise and clear, and the generated exceptions will be more valuable.

16 September 2021, 22:21 | Views: 4293

Add new comment

For adding a comment, please log in
or create account

0 comments