Java Constructors vs Static Factory Methods – Java构造器与静态工厂方法

最后修改: 2018年 8月 18日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Java constructors are the default mechanism for getting fully-initialized class instances. After all, they provide all the infrastructure required for injecting dependencies, either manually or automatically.

Java构造函数是获得完全初始化的类实例的默认机制。毕竟,它们提供了手动或自动注入依赖关系所需的所有基础设施。

Even so, in a few specific use cases, it’s preferable to resort to static factory methods for achieving the same result.

即便如此,在一些特定的用例中,最好还是借助静态工厂方法来实现同样的结果。

In this tutorial, we’ll be highlighting the pros and cons of using static factory methods vs plain old Java constructors.

在本教程中,我们将强调使用静态工厂方法与普通老式Java构造函数的利弊

2. Advantages of Static Factory Methods Over Constructors

2.静态工厂方法相对于构造函数的优势

In an object-oriented language like Java, what could be wrong with constructors? Overall, nothing. Even so, the famous Joshua Block’s Effective Java Item 1 clearly states:

在像Java这样的面向对象的语言中,构造函数会有什么问题呢?总的来说,没有什么。即便如此,著名的Joshua Block的Effective Java项目1明确指出。

“Consider static factory methods instead of constructors”

“考虑用静态工厂方法代替构造函数”

While this isn’t a silver bullet, here are the most compelling reasons that sustain this approach:

虽然这不是银弹,但以下是支持这种方法的最令人信服的理由。

  1. Constructors don’t have meaningful names, so they are always restricted to the standard naming convention imposed by the language. Static factory methods can have meaningful names, hence explicitly conveying what they do
  2. Static factory methods can return the same type that implements the method(s), a subtype, and also primitives, so they offer a more flexible range of returning types
  3. Static factory methods can encapsulate all the logic required for pre-constructing fully initialized instances, so they can be used for moving this additional logic out of constructors. This prevents constructors from performing further tasks, others than just initializing fields
  4. Static factory methods can be controlled-instanced methods, with the Singleton pattern being the most glaring example of this feature

3. Static Factory Methods in the JDK

3.JDK中的静态工厂方法

There are plenty of examples of static factory methods in the JDK that showcase many of the advantages outlined above. Let’s explore some of them.

JDK中有很多静态工厂方法的例子,展示了上述的许多优点。让我们来探索其中的一些。

3.1. The String Class

3.1.字符串

Because of the well-known String interning, it’s very unlikely we’ll use the String class constructor to create a new String object. Even so, this is perfectly legal:

由于众所周知的String互换,我们非常不可能使用String类构造函数来创建一个新String对象。即便如此,这也是完全合法的。

String value = new String("Baeldung");

In this case, the constructor will create a new String object, which is the expected behavior.

在这种情况下,构造函数将创建一个新的String对象,这是预期行为。

Alternatively, if we want to create a new String object using a static factory method, we can use some of the following implementations of the valueOf() method:

另外,如果我们想使用静态工厂方法创建一个新的String对象,我们可以使用以下一些valueOf()方法的实现。

String value1 = String.valueOf(1);
String value2 = String.valueOf(1.0L);
String value3 = String.valueOf(true);
String value4 = String.valueOf('a');

There are several overloaded implementations of valueOf(). Each one will return a new String object, depending on the type of the argument passed to the method (e.g. int, long, boolean, char, and so forth).

valueOf()有几个重载的实现。每一个都将返回一个新的String对象,这取决于传递给该方法的参数类型(例如,intlongbooleanchar等等)。

The name expresses pretty clearly what the method does. It also sticks to a well-established standard in the Java ecosystem for naming static factory methods.

这个名字很清楚地表达了该方法的作用。它还坚持了Java生态系统中对静态工厂方法命名的一个成熟的标准。

3.2. The Optional Class

3.2.选择性的

Another neat example of static factory methods in the JDK is the Optional class. This class implements a few factory methods with pretty meaningful names, including empty(), of(), and ofNullable():

JDK 中静态工厂方法的另一个很好的例子是 Optional 类。这个类实现了一些名称相当有意义的工厂方法,包括empty()of(),以及ofNullable()

Optional<String> value1 = Optional.empty();
Optional<String> value2 = Optional.of("Baeldung");
Optional<String> value3 = Optional.ofNullable(null);

3.3. The Collections Class

3.3.Collections

Quite possibly the most representative example of static factory methods in the JDK is the Collections class. This is a non-instantiable class that implements only static methods.

JDK 中最具代表性的静态工厂方法的例子是 Collections 类。这是一个不可实例化的类,只实现静态方法。

Many of these are factory methods that also return collections, after applying to the supplied collection some type of algorithm.

其中许多是工厂方法,在对所提供的集合应用某种类型的算法后,也会返回集合。

Here are some typical examples of the class’ factory methods:

下面是该类工厂方法的一些典型例子。

Collection syncedCollection = Collections.synchronizedCollection(originalCollection);
Set syncedSet = Collections.synchronizedSet(new HashSet());
List<Integer> unmodifiableList = Collections.unmodifiableList(originalList);
Map<String, Integer> unmodifiableMap = Collections.unmodifiableMap(originalMap);

The number of static factory methods in the JDK is really extensive, so we’ll keep the list of examples short for brevity’s sake.

JDK 中静态工厂方法的数量实在是太多了,所以为了简洁起见,我们将缩短例子的清单。

Nevertheless, the above examples should give us a clear idea of how ubiquitous static factory methods are in Java.

尽管如此,上面的例子应该能让我们清楚地了解到静态工厂方法在Java中是多么的无处不在。

4. Custom Static Factory Methods

4.自定义静态工厂方法

Of course, we can implement our own static factory methods. But when is it really worth doing so, instead of creating class instances via plain constructors?

当然,我们可以实现我们自己的静态工厂方法。但什么时候才真正值得这样做,而不是通过普通的构造函数来创建类实例。

Let’s see a simple example.

让我们看一个简单的例子。

Let’s consider this naive User class:

让我们考虑一下这个天真的User类。

public class User {
    
    private final String name;
    private final String email;
    private final String country;
    
    public User(String name, String email, String country) {
        this.name = name;
        this.email = email;
        this.country = country;
    }
    
    // standard getters / toString
}

In this case, there’re no visible warnings to indicate that a static factory method could be better than the standard constructor.

在这种情况下,没有明显的警告表明静态工厂方法可能比标准构造函数更好。

What if we want that all the User instances get a default value for the country field?

如果我们想让所有的User实例得到一个country字段的默认值,该怎么办?

If we initialize the field with a default value, we’d have to refactor the constructor too, hence making the design more rigid.

如果我们用默认值初始化该字段,我们也必须重构构造函数,从而使设计更加僵硬。

We can use a static factory method instead:

我们可以使用一个静态工厂方法来代替。

public static User createWithDefaultCountry(String name, String email) {
    return new User(name, email, "Argentina");
}

Here’s how we’d get a User instance with a default value assigned to the country field:

下面是我们如何获得一个User实例,并为country字段分配一个默认值。

User user = User.createWithDefaultCountry("John", "john@domain.com");

5. Moving Logic out of Constructors

5.将逻辑从构造器中移出

Our User class could quickly rot into a flawed design if we decide to implement features that would require adding further logic to the constructor (alarm bells should be sounding off by this time).

如果我们决定实现一些需要在构造函数中添加更多逻辑的功能,我们的User类可能很快就会腐烂成一个有缺陷的设计(这个时候应该已经敲响了警钟)。

Let’s suppose that we want to provide the class with the ability for logging the time at which every User object is created.

假设我们想为该类提供记录每个User对象创建时间的能力。

If we just put this logic into the constructor, we’d be breaking the Single Responsibility Principle. We would end up with a monolithic constructor that does a lot more than initialize fields.

如果我们只是把这个逻辑放在构造函数中,我们就会破坏单一责任原则。我们最终会得到一个单一的构造函数,该构造函数除了初始化字段外还做了很多事情。

We can keep our design clean with a static factory method:

我们可以通过静态工厂方法保持我们的设计简洁:

public class User {
    
    private static final Logger LOGGER = Logger.getLogger(User.class.getName());
    private final String name;
    private final String email;
    private final String country;
    
    // standard constructors / getters
    
    public static User createWithLoggedInstantiationTime(
      String name, String email, String country) {
        LOGGER.log(Level.INFO, "Creating User instance at : {0}", LocalTime.now());
        return new User(name, email, country);
    }
}

Here’s how we’d create our improved User instance:

下面是我们如何创建我们改进的User实例。

User user 
  = User.createWithLoggedInstantiationTime("John", "john@domain.com", "Argentina");

6. Instance-Controlled Instantiation

6.实例控制的实例化

As shown above, we can encapsulate chunks of logic into static factory methods before returning fully-initialized User objects. And we can do this without polluting the constructor with the responsibility of performing multiple, unrelated tasks.

如上所示,我们可以在返回完全初始化的User对象之前将大块的逻辑封装到静态工厂方法中。我们可以做到这一点,而不必让构造函数承担执行多个不相关任务的责任。

For instance, suppose we want to make our User class a Singleton. We can achieve this by implementing an instance-controlled static factory method:

例如,假设我们想让我们的User类成为单子。我们可以通过实现一个由实例控制的静态工厂方法来实现这一目标:

public class User {
    
    private static volatile User instance = null;
    
    // other fields / standard constructors / getters
    
    public static User getSingletonInstance(String name, String email, String country) {
        if (instance == null) {
            synchronized (User.class) {
                if (instance == null) {
                    instance = new User(name, email, country);
                }
            }
        }
        return instance;
    }
}

The implementation of the getSingletonInstance() method is thread-safe, with a small performance penalty, due to the synchronized block.

getSingletonInstance()方法的实现是线程安全的,但由于同步块的存在,会有小的性能损失

In this case, we used lazy initialization to demonstrate the implementation of an instance-controlled static factory method.

在这种情况下,我们使用懒惰初始化来演示一个实例控制的静态工厂方法的实现。

It’s worth mentioning, however, that the best way to implement a Singleton is with a Java enum type, as it’s both serialization-safe and thread-safe. For the full details on how to implement Singletons using different approaches, please check this article.

然而,值得一提的是,实现Singleton的最佳方式是使用Java enum类型,因为它既是序列化安全的,又是线程安全的。有关如何使用不同方法实现Singletons的全部细节,请查看这篇文章

As expected, getting a User object with this method looks very similar to the previous examples:

正如预期的那样,用这个方法得到一个User对象,看起来与前面的例子非常相似。

User user = User.getSingletonInstance("John", "john@domain.com", "Argentina");

7. Conclusion

7.结论

In this article, we explored a few use cases where static factory methods can be a better alternative to using plain Java constructors.

在这篇文章中,我们探讨了几个用例,在这些用例中,静态工厂方法可以成为使用普通Java构造函数的更好选择。

Moreover, this refactoring pattern is so tightly rooted to a typical workflow that most IDEs will do it for us.

此外,这种重构模式是如此紧密地扎根于典型的工作流程中,以至于大多数IDE都会为我们做这件事。

Of course, Apache NetBeans, IntelliJ IDEA, and Eclipse will perform the refactoring in slightly different ways, so please make sure first to check your IDE documentation.

当然,Apache NetBeansIntelliJ IDEAEclipse将以稍微不同的方式执行重构,所以请确保首先查看你的IDE文档。

As with many other refactoring patterns, we should use static factory methods with due caution, and only when it’s worth the trade-off between producing more flexible and clean designs and the cost of having to implement additional methods.

与其他许多重构模式一样,我们应该谨慎地使用静态工厂方法,而且只有在产生更灵活、更干净的设计与必须实现额外方法的成本之间的权衡是值得的。

As usual, all the code samples shown in this article are available over on GitHub.

像往常一样,本文中显示的所有代码样本都可以在GitHub上找到