Overview
The traditional way to obtain an instance of a class is through a public constructor.
```javascript
public class Member {
}
public enum MemberStatus {
}
Although public constructors are usually sufficient, providing static factory methods (static factory method) in addition to constructors often makes it easier for users to create instances as intended.
A typical example of a static factory method is the valueOf() method of Boolean.
```javascript
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
This method receives a boolean value, which is a basic type, and returns it as a Boolean object.
Advantages of Static Factory Methods
Can have a name.
The parameters passed to the constructor and the constructor itself cannot fully explain the characteristics of the returned object. For example, it is difficult to determine what kind of Member is based on only the main constructor of the Member class above (name, age, hobby, memberStatus).
Furthermore, one signature can only create one constructor. However, since static factory methods can have names, one signature can create multiple static factory methods to return instances.
```javascript
public class Member {
}
Unlike using MemberStatus to distinguish it from a constructor, creating multiple static factory methods with the same signature allows users to create a Member instance with a specific skill level without any confusion.
Looking at the libraries defined in the JDK, there is a static factory method, probablePrime(), in BigInteger.
```javascript
public static BigInteger probablePrime(int bitLength, Random rnd) {
if (bitLength < 2)
throw new ArithmeticException("bitLength < 2");
}
When comparing the general constructor of BigInteger with the static factory method probablePrime(), the latter would be better at explaining that it returns a BigInteger whose value is a prime number.
You don't have to create a new instance every time it is called.
```javascript
public static Boolean valueOf(boolean b) {
return (b ? Boolean.TRUE : Boolean.FALSE);
}
You can see that Boolean's valueOf() method caches instances and returns them. This feature can significantly improve performance in situations where objects with high creation costs are frequently requested, andflyweight patterncan be seen as a similar technique.
Classes that use the static factory method approach to return the same object for repeated requests are called instance-controlled classes because they can control the life cycle of instances. By controlling instances, you can create singleton classes or classes that cannot be instantiated. You can also ensure that there is only one instance of an immutable value class.
Instance control is the basis of the flyweight pattern, and enumeration types guarantee that only one instance is created.
You have to plant a tree in Minecraft. If a new object is created for each tree object, there is a possibility of a memory overflow.
Therefore, red trees and green tree objects can be stored, and only the positions can be changed and returned. Of course, the color can be more than two colors, so it would be efficient to store trees according to their colors in a data structure like Map.
```javascript
public class Tree {
}
public class TreeFactory {
// HashMap data structure is used to manage created trees.
public static final Map<String, Tree> treeMap = new HashMap<>();
}
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
}
Difference from Singleton Pattern
The Singleton pattern allows only one tree to be created in the Tree class. Therefore, if the Singleton pattern is used, the color of the one created object must be changed. That is, the Singleton pattern can have only one type regardless of type.
The flyweight pattern is used in the Java String Constant Pool.
You can return an object of a subtype of the return type.
If you have used the asList() method of the Arrays utility class, you can understand this advantage.
```javascript
public static List asList(T... a) [
return new ArrayList<>(a);
}
It wraps the value in an ArrayList, which is a subclass of List, and returns it. The user does not need to know about this implementation. In other words, the flexibility to choose the class of the returned object means that developers can return the implementation without disclosing it, so they can keep the API small.
About Static Methods of Java Interfaces
Before Java 8, static methods could not be declared in interfaces. So, if a static method that returns an interface named "Type" was needed, an uninstantiable companion class named "Types" was created and the method was defined inside it.
A prime example is the 45 utility implementations provided by JCF, most of which are obtained through static factory methods in a single companion class, java.util.Collections. In particular, some of these implementations are not public, so instances can only be created through static factory methods. (These implementations naturally cannot be inherited.)
Furthermore, since 45 implementations are not disclosed, the API can be made much smaller.
```javascript
// Example of interface and companion class
List empty = Collections.emptyList();
However, since Java 8, static methods can be added directly to interfaces, so companion classes no longer need to be defined separately.
You can return objects of different classes depending on the input parameters.
It goes beyond simply returning subtypes. You can return different subtypes based on the value of the parameter. For example, if you want to return a different MemberStatus depending on the score, you can create a static factory method as below and set up comparison logic within it.
```javascript
public enum MemberStatus {
}
@DisplayName("MemberStatus Test")
class MemberStatusTest {
}
The class of the object to be returned does not have to exist at the time the static factory method is written.
In the above sentence,class of the objectmeans the class file we wrote.
For reference, Class> refers to the Class object allocated to the heap area when the class loader loads the class. This Class object holds various metadata about the class we wrote.
```javascript
package algorithm.dataStructure;
public abstract class StaticFactoryMethodType {
}
Looking at the code above, you can see that the Class object is created based on the location of the interface implementation, and the actual implementation is initialized using reflection techniques. At this time,at the time the static factory method is written, the StaticFactoryMethodTypeChild class does not have to exist.
If there is no implementation in the algorithm.dataStructure.StaticFactoryMethodTypeChild path at the time the static factory method is used, an error will occur, but there is no problem at the time the static factory method is written, so it is flexible.
```javascript
public interface Test {
}
public class Main {
}
The same flexibility can be achieved without using reflection. Looking at create(), the static factory method of Test, there is no problem at the time of writing it, even if there is no implementation. Of course, NPE will occur at the time of actual use, so an implementation needs to be returned later.
This flexibility is the basis for creating service provider frameworks, and JDBC is a prime example. A provider of a JDBC service provider framework is an implementation of a service, and the framework controls the provision of these implementations to clients, separating clients from implementations. (DIP)
- Components of a service provider framework
- Service Interface
- Defines the behavior of the implementation
- JDBC's Connection
- Provider Registration API
- The provider registers the implementation.
- JDBC's DriverManager.registerDriver()
- Service Access API
- Used by the client to obtain an instance of the service. If no conditions are specified, it returns the default implementation or rotates the implementations that support it.
- Correspondence to static factory methods
- JDBC's DriverManager.getConnection()
- (Optional) Service Provider Interface
- If this is not present, reflection must be used to create each implementation as an instance.
- JDBC's Driver
The service provider framework pattern has various variations, including the bridge pattern, dependency injection frameworks, etc.
```javascript
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection connection = null;
connection = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:ORA92", "root", "root");
// sql logic using various Statements
JDBC is typically written as above. Class.forName() registers one of the Driver implementations, OracleDriver, and DriverManager.getConnection() obtains one of the Connection implementations, Connection for OracleDriver.
Here, Connection is the service interface, DriverManager.getConnection() is the service access API, and Driver is the service provider interface. However, the provider registration API, DriverManager.registerDriver(), was not used. Nevertheless, we can register OracleDriver, which is an implementation of Driver, with only Class.forName(). How is this possible?
Operation of Class.forName()
This method requests the JVM to load this class when the physical class file name is passed as an argument. The class loader then stores the class's metadata in the method area and allocates a Class object to the heap area. Also, when class loading is complete,static fields and static blocks are initialized. At this time, the provider registration API is used.
```javascript
public class OracleDriver implements Driver {
}
In fact, you can see that OracleDriver uses DriverManager.registerDriver() to register OracleDriver, which is an implementation of Driver, within the static block.
DriverManager Class Analysis
```javascript
public class DriverManager {
}
The DriverManager class is actually much more complex, but it is similar to the above when only the essentials are summarized. As explained above, registerDriver() is called in the static block of OracleDriver to register OracleDriver, and users can obtain implementations of Connection by calling getConnection().
Looking closely at the user access API, getConnetion(), you can see that a Connection is obtained from the Driver interface. If there is no Driver service provider interface, reflection, such as Class.forName(), can be used to return the desired Connection implementation. At this time,the Connection implementation does not have to exist at the time the static factory is written.
Instead, we use the Driver interface, dynamically register the Driver implementation, and then easily obtain the corresponding Connection implementation for this Driver.
For reference, I analyzed the actual JDK code of the DriverManager's getConnection() method. If you are not interested, you can skip it.
```javascript
@CallerSensitive
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
}
First, the public static method getConnection() is called, and url, Properties, and CallerClass are passed as arguments to the private static method getConnection(). At this time, Reflection.getCallerClass() obtains the class that called the public static method getConnection(). If the Car class called getConnection(), a Class object can be obtained by Reflection.getCallerClass().
```javascript
private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
}
callerCL is a class loader object and is created by the caller or the class loader of the current thread. Then, one aDriver is taken out of registeredDrivers, which is a list of Drivers registered in the current application. If this Driver returns true by isDriverAllowed(), a Connection object is obtained from this Driver and returned. isDriverAllowed() checks whether aDriver exists in the caller.
Advantages of the JDBC Framework
The point of the JDBC framework is that Driver and Connection interfaces and their actual implementations are provided completely separately. By using interfaces to create a framework, you can simply create an implementation class that fits the framework, which is very flexible.
Therefore, even if a different DBMS comes out, the vendor can provide an implementation of the Driver and Connection interfaces, allowing Java developers to use the same API as other DBMS drivers.
Disadvantages of Static Factory Methods
When inheriting, public or protected constructors are required, so you cannot create subclasses if only static factory methods are provided.
However, this constraint can actually be an advantage because it encourages composition over inheritance and this constraint must be followed to create immutable types.
Static factory methods are difficult for programmers to find.
Since it is not explicitly shown in the API description like constructors, developers need to alleviate the problem by writing good API documentation and naming methods according to widely known conventions.
Naming Conventions for Static Factory Methods
- from
- Receives one parameter and returns an instance of that type
- Date date = Date.from(instant);
- of
- Receives multiple parameters and returns an instance of an appropriate type
- Set faceCards = EnumSet.of(JACK, QUEEN, KING);
- valueOf
- More detailed version of from and of
- BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
- instance or getInstance
- Returns the instance specified by the parameter, but does not guarantee that it is the same instance.
- StackWalker luke = StackWalker.getInstance(options);
- create or newInstance
- Same as instance or getInstance, but guarantees that a new instance is created and returned.
- Object newArray = Array.newInstance(classObject, arraylen);
- getType
- Same as getInstance, but used when defining a factory method in a different class than the one to be created.
- FileStore fs = Files.getFileStore(path);
- newType
- Same as newInstance, but used when defining a factory method in a different class than the one to be created.
- BufferedReader br = Files.newBufferedReader(path);
- type
- Concise version of getType and newType
- List litany = Collections.list(legacyLitany);
Summary
Static factory methods and public constructors each have their own uses, so use them appropriately.
Sources