Consider using static factory methods instead of constructors.

More specifically, the constructor that replaces public. A static factory method here refers to a static method in a class that returns an instance of that class. Java’s Boolean wrapper class, for example, provides the following static factory methods:

public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}
Copy the code

The book Outlines the following advantages of using a static factory approach:

  1. Static factory methods, in contrast to constructors, can have names.
  2. In contrast to construction, the static factory method does not require the creation of a new object every time it is called, thus avoiding the creation of many unnecessary objects.
  3. The third advantage is that the static factory method can not only create an object of the current class, but also return any subclass object of the return type.
  4. A fourth advantage is that the type returned by the static factory method can vary depending on the input parameter.
  5. A fifth advantage is that the static factory method returns an object whose corresponding class may not exist at the time the static method is written.

Static factory methods, in contrast to constructors, can have names

When we create classes, sometimes we need more than one way to generate objects. One way is to overload the constructor, but the constructor method can only be overloaded with different parameters. Sometimes, with the same parameters, we want to construct objects in a different way, which is hard to do with constructors.

Using the static factory approach, we can use different naming methods to build objects in different ways.

For example, a Boolean class, there are several methods to create a Boolean object:

  • Boolean(boolean)
  • Boolean(String)
  • valueOf(boolean)
  • valueOf(String)

The first two are Boolean public constructors and each receive a parameter. The first accepts a Boolean type and the second a String type. Can convert corresponding values to Boolean objects, respectively.

But the use of constructors here is not clear. These two methods actually correspond to the following two valueOf() methods. The two uses are the same, but the semantics of valueOf are a little more explicit. ValueOf is literally a cast. ValueOf represents a Boolean type or String converted to a Boolean object.

The following is a more practical example of a practical use in a project. When constructing a network interface and determining the return value of the network interface, we usually need to carry out some encapsulation. For example, if all return types are encapsulated in a generic class called ResponseModel

, add the following basic information.

public class ResponseModel<M> {
    // return code
    private int code;
    // Description
    private String message;
    // Create time
    private LocalDateTime time = LocalDateTime.now();
    // Specific content
    private M result;
}
Copy the code

If we were writing a service program, this class would be a very common one for us to communicate with the client, and we would often need to create different ResponseModel to return to the client. Therefore, it is best to provide different methods to quickly create different ResponseModel using different methods. For example, a successful request Response (200), a Response containing different types of error messages, and so on. If you use a constructor to implementation, it is difficult to achieve, we need to be very careful to build different overloading to achieve, and when the call is very chaotic, because in this case each constructor parameters of different cannot provide very clear semantic to say we want to create objects, it may lead to a large extent of chaos, It is also very inconvenient to use.

Using the static factory approach, we can quickly build objects by naming different methods to provide very clear semantic information. Such as:

public static <M> ResponseModel<M> buildOk(a) {
    return new ResponseModel<M>();
}

public static <M> ResponseModel<M> buildOk(M result) {
    return new ResponseModel<M>(result);
}

public static <M> ResponseModel<M> buildParameterError(a) {
    return new ResponseModel<M>(ERROR_PARAMETERS, "Parameters Error.");
}
Copy the code

The above list of several different static factory methods, through the name of the method can be very clear to know the meaning of the object we are building, in a real sense to provide shortcuts.

The static factory method does not require the creation of a new object every time it is called, thus avoiding the creation of many unnecessary objects.

This mechanism is common for many valued classes, and a good example is the boxing classes in Java.

There are eight primitive types of Primitive in Java, which correspond to eight auto-boxing classes:

  • char -> Character
  • boolean -> Boolean
  • byte -> Byte
  • short -> Short
  • int -> Integer
  • long -> Long
  • float -> Float
  • double -> Double

Boolean class

The Boolean class is a relatively simple class. Boolean has only two possible values, True and False. So, in theory, a Boolean class needs to create at most two objects at run time. This is also implemented inside the Boolean class, which contains two static members:

/**
 * The {@code Boolean} object corresponding to the primitive
 * value {@code true}.
 */
public static final Boolean TRUE = new Boolean(true);

/**
 * The {@code Boolean} object corresponding to the primitive
 * value {@code false}.
 */
public static final Boolean FALSE = new Boolean(false);
Copy the code

When we use Boolean objects, we should always use these two objects and avoid creating additional variables so that we can use them easily.

The Boolean public constructor has been marked as @deprecated in newer JDK versions.

@Deprecated(since="9")
public Boolean(boolean value) {
    this.value = value;
}

@Deprecated(since="9")
public Boolean(String s) {
    this(parseBoolean(s));
}
Copy the code

When using Boolean, we should use its static factory method valueOf() to create Boolean objects, or use the static members TRUE and FALSE directly.

For auto-boxing and auto-unboxing, I read that the corresponding valueOf() method is automatically called.

As long as you don’t call the Boolean constructor externally (nor should we), the program runs with only two static objects.

The Integer class

Integer is a bit more complex, but the class is also designed with static factory methods in place of constructors. The Integer constructor is also marked @deprecated and should also not be used.

@Deprecated(since="9")
public Integer(int value) {
    this.value = value;
}

@Deprecated(since="9")
public Integer(String s) throws NumberFormatException {
    this.value = parseInt(s, 10);
}
Copy the code

Similarly, the static factory methods used to replace the above two constructors are valueOf(String S, int radix), valueOf(String S), and valueOf(int I). The first two constructors convert from String to Integer objects; radix denotes base.

An additional mechanism for Integer over Boolean is the caching mechanism. Boolean objects have only two values, so just use two static member variables.

Integer uses an additional caching mechanism. Integer has a static member class, IntegerCache, which is a singleton class initialized with a static code block.

private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer[] cache;
    static Integer[] archivedCache;

    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
            VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if(integerCacheHighPropValue ! =null) {
            try {
                h = Math.max(parseInt(integerCacheHighPropValue), 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;

        // Load IntegerCache.archivedCache from archive, if possible
        VM.initializeFromArchive(IntegerCache.class);
        int size = (high - low) + 1;

        // Use the archived cache if it exists and is large enough
        if (archivedCache == null || size > archivedCache.length) {
            Integer[] c = new Integer[size];
            int j = low;
            for(int i = 0; i < c.length; i++) {
                c[i] = new Integer(j++);
            }
            archivedCache = c;
        }
        cache = archivedCache;
        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }

    private IntegerCache(a) {}}Copy the code
  1. The default value of IntegerCache is -128 to 127.
  2. During implementation, only the default lower bound of the cache is allowed, while the upper bound can be changed by setting vm parameters.
    1. String integerCacheHighPropValue = VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
    2. You can specify vm parameters-XX:AutoBoxCacheMax=<size>To set the upper bound of the cache. This value is cached to the JVM during initializationjdk.internal.misc.VMIn thejava.lang.Integer.IntegerCache.highAttribute.
    3. Why does the JVM allow to set the “high” value for the IntegerCache, but not the “low”? The problem, he says it’s a problem, but there’s no need to change the lower bound.
    4. RFP official language.
    5. This adjustment only exists in Integer. There are no cache parameters that can be adjusted in Long. They are fixed values.
  3. Another point to note during implementation is that the cache uses a MECHANISM called CDS (Class Data Sharing).

Because of the above caching mechanism, we performed the following tests:

@SuppressWarnings({"NumberEquality"})
public static void testInteger(a) {
    System.out.println("\n=====Some test for Integer=====\n");
    Integer a = 1000, b = 1000;
    System.out.println("a = " + a + ", b = " + b);
    // Warning: Only for test, don't use "==" to compare two boxed object
    System.out.println("a == b: " + (a == b));

    Integer c = 100, d = 100;
    System.out.println("c = " + c + ", d = " + d);
    // Warning: Only for test, don't use "==" to compare two boxed object
    System.out.println("a == b: " + (c == d));
}
Copy the code

It is easier to understand the following output from the above code:

=====Some test for Integer=====

a = 1000, b = 1000
a == b: false
c = 100, d = 100
a == b: true
Copy the code

Char, Byte, Long, and Short

Char, Byte, Long, and Short are implemented almost in the same way as Integer, providing the same static factory method and also using caching, which I won’t go into here.

Double, Float

Double and Float also provide the static factory method valueOf(), but no caching mechanism is provided because decimals are not suitable for caching.

A static factory method can not only create an object of the current class, but also return any subclass object of the return type.

One application of this feature is to return a subclass object without exposing the subclass’s implementation. For example, the Java Collections Framework. In a companion class called java.util.Collections, UnmodifiableSet, SynchronizedSet, EmptySet, and so on are implemented.

The implementations of these Collections are non-public, and if you want to get objects of these classes, you can call the corresponding static factory methods in Collections and use interfaces to reference those objects.

The type returned by the static factory method can vary depending on the input parameter.

EnumSet is an abstract class that provides no public constructors. It provides a series of static factory methods to create enumsets, including noneOf(), allOf(), of(), range(). This series of static factory methods all end up calling noneOf().

The noneOf() method takes the type information of an enumerated class, and the source code implementation is as follows.

public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
    // Get an array of all enumerationsEnum<? >[] universe = getUniverse(elementType);if (universe == null)
        throw new ClassCastException(elementType + " not an enum");

    // Determine the implementation of EnumSet based on the number of enumeration elements
    if (universe.length <= 64)
        return new RegularEnumSet<>(elementType, universe);
    else
        return new JumboEnumSet<>(elementType, universe);
}
Copy the code

As you can see, it eventually returns a RegularEnumSet object or a JumboEnumSet object. Both of these classes are concrete implementations of EnumSet. EnumSet is based on the specific number of enumeration elements to determine the specific implementation of EnumSet.

RegularEnumSet is internally supported using a single long type:

/** * Bit vector representation of this set. The 2^k bit indicates the * presence of universe[k] in this set. */
private long elements = 0L;
Copy the code

When the number of elements is less than or equal to 64, a RegularEnumSet is sufficient because a long data is 64-bit.

When the number of elements is greater than 64, the JumboEnumSet implementation uses a long array for internal storage.

/** * Bit vector representation of this set. The ith bit of the jth * element of this array represents the presence of universe[64*j +i] * in this set. */
private long elements[];
Copy the code

An object returned by a static factory method whose corresponding class may not exist when the static method is written.

Its typical application is JDBC application mode.