【问题标题】:How to initialize a constant object field for a class in java如何在java中为类初始化一个常量对象字段
【发布时间】:2019-06-25 16:54:39
【问题描述】:

我想知道为类初始化复杂对象类型的常量字段的最佳方法是什么,哪个性能最高?

1) 内联初始化

public class TopClass {
   private static final ComplexObject sdf = new ComplexObject();
            
   public TopClass (
   }
}

2) 初始化方法

public class TopClass {
     private static final ComplexObject sdf = initializeComplexObject();
     private static ComplexObject initializeComplexObject(){
                return sdf == null ? new ComplexObject() : sdf;
     }
     public TopClass (
     }
}

3) 在构造函数中初始化, 4) 静态初始化块 或您建议的其他方法...

每次创建新的 TopClass 类时,sdf 是否都会初始化? 我希望 sdf 字段在应用程序生命周期内只初始化一次。

【问题讨论】:

  • 静态初始化器的性能几乎无关紧要(尤其是对于这种微不足道的初始化),因为它只为一个类完成一次。这里更大的问题是确保sdf 永远不会在线程之间共享。
  • 我同意@AndyTurner,在这种情况下,目标是可读性,而不是性能。第一个好,一行行就不要加没用的静态方法。
  • 另外,如果这是用于日志记录,大多数日志记录框架可能已经可以格式化您给它们的日期(以可配置的方式)

标签: java


【解决方案1】:

静态初始化器的性能几乎无关紧要(尤其是对于这种微不足道的初始化),因为它只为一个类完成一次。

2) 这种具体的方法方式是多余的,因为它在类初始化的时候立即调用; sdf 在静态初始化程序调用该方法时始终为 null,并且您不会再次调用该方法(至少,不是为了给 sdf 一个不同的值)。它也很笨拙,因为您有意读取未初始化的最终字段。

因此,只需删除条件,您最终会返回有效的内联初始化方法,以及方法调用的间接性。

例如,如果您想在格式化程序上进行其他配置,例如设置时区,则方法方法将很有用。

3) 不要在构造函数中初始化静态成员。构造函数用于初始化实例成员。

特别是,这要求您将静态字段设置为非最终字段。这意味着您必须担心字段更新的可见性,以避免多个线程因为看到空值而初始化该字段

4) 在声明时初始化字段只是声明静态初始化程序的简写。问题中的第一个代码块在语义上与此相同:

private static final ComplexObject sdf;

static {
  sdf = new ComplexObject(); 
}

如果你能侥幸逃脱,那么明确地这样做没有任何好处。

静态初始化器有点像匿名方法。 Google 的内部 Java 实践建议尽可能使用方法而不是显式静态初始化程序块,部分原因是您可以显式调用它们进行测试,但也因为它们必须强制您只初始化一个字段。 (我基本上同意这是一个很好的建议,但请注意,您在方法中丢失了明确的分配检查 - 如上所示 - 这有助于捕获某些类型的错误)。

总结:使用private static final ComplexObject sdf = new ComplexObject();,因为你不需要更复杂的东西。


这里更大的问题是正确性,即确保sdf 不在线程之间共享:在您编辑问题之前,sdfSimpleDateFormat,它不是线程安全的。我不知道ComplexObject 是什么,但您需要确保它是线程安全的,或者以线程安全的方式访问它。在微优化之前担心这样的事情。

【讨论】:

  • 常量字段的类类型不是我关心的,我只是想找到正确的方法来初始化复杂对象类型的常量字段。及其在LogInfo 的新对象创建时的初始化。
【解决方案2】:

正如另一个答案所说:性能绝对在这里不是问题。启动 JVM 时,它必须加载可能数千个甚至数十万个类。在该过程中如何初始化单个常量根本不重要。最好,我们讨论不同方法的纳秒。

因此,唯一剩下来指导决策的是:干净编码的想法,例如:什么是“最”人类可读/可理解的方式来看待这个问题。

我认为:如果可能,您决定选择选项 1。如果表达式不太复杂,并且可以简单地查看它并理解 SOME_CONSTANT = some expression,为什么要增加方法调用/初始化程序块的复杂性,好复杂东西?

当然:当表达式已经“复杂”了,并且你想写评论来解释为什么它以特定的方式做事时,那么辅助方法是一个很好的选择主意。仅一个有用的方法名称就可以解释需要解释的内容(省去评论的需要!)

换句话说:始终专注于编写最少的代码,这也易于阅读和理解。您不使用初始化方法是因为可以,但因为这样做会使事情更容易理解(在您的情况下:它没有)。初始化程序块(恕我直言)更糟,仅仅是因为它们非常罕见。在我看来,它们是已知的异常,因为这些天您甚至可以将地图、列表等创建为“文字”。

【讨论】:

  • 我会声称,唯一真正需要初始化器的地方是如果您正在初始化相互依赖的字段。
【解决方案3】:

@Andy Turner 的回答几乎可以回答这个问题,但我想添加一个额外的设计考虑因素,因为您的示例对象是 ComplexObject,并且您似乎专注于性能方面。

如果您只想实例化对象一次,但初始化非常昂贵(ComplexObject 可能会建议这样做),您可能希望仅在对象实际使用时初始化对象,即第一次使用访问(lazy initializationlazy loading)。

在其简单形式中,您基本上将您的 ComplexObject 隐藏在静态 getter private static ComplexObject getComplexObject() 后面。然后你可以使用某些成语:

如果你不想要额外的方法,你可以定义一个Lazy 类,例如:

class Lazy<T> implements Supplier<T> {

    private T value;
    private final Supplier<T> initializer;

    public Lazy(final Supplier<T> initializer) {
        this.initializer = initializer;
    }

    @Override
    public T get() {
        if (value == null) {
            synchronized (this) {
                if (value == null) {
                    value = initializer.get();
                }
            }
        }
        return value;
    }
}

public class TopClass {
    private static final Lazy<ComplexObject> sdf = new Lazy<>(ComplexObject::new);

    public void exampleMethod() {
        ComplexObject o = sdf.get();
    }
}

如果你不想要get()的额外方法调用,你可以使用proxy pattern,如果你可以将ComplexObject的行为封装在一个接口中。然后代理应该delegate 对真实实例的ComplexObject 的所有方法调用(在这种情况下,我使用前面的代码通过扩展Lazy 来创建实例,该Lazy 使用通过get() 访问,但您可以实现在代理中以任何你想要的方式惰性初始化)。

interface ComplexObject {}

class ComplexObjectImpl implements ComplexObject {}

class ComplexObjectProxy extends Lazy<ComplexObjectImpl> implements ComplexObject {

    public ComplexObjectProxy() {
        super(ComplexObjectImpl::new);
    }
}

public class TopClass {
    private static final ComplexObject sdf = new ComplexObjectProxy();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-16
    相关资源
    最近更新 更多