如何在Java中为类初始化常量对象字段

时间:2019-06-25 06:40:36

标签: java

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

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字段在应用程序生存期内仅初始化一次。

3 个答案:

答案 0 :(得分:3)

静态初始化器的性能几乎无关紧要(特别是对于这种琐碎的初始化),因为对于一个类而言,它只完成一次。

2)这种特定的方法方法是多余的,因为在初始化类时会立即调用它;当静态初始值设定项调用该方法时,sdf总是 为空,并且您不会再次调用该方法(至少不是为了给sdf提供不同的值)。由于您有意读取未初始化的final字段,因此它也很鲁ro。

因此,只需要删除条件,就可以有效地使用内联初始化方法,并间接调用方法。

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

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

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

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

private static final ComplexObject sdf;

static {
  sdf = new ComplexObject(); 
}

如果不能避免,那么明确地这样做是没有好处的。

静态初始值设定项类似于匿名方法。 Google的内部Java实践建议在可能的情况下使用方法而不是显式的静态初始化程序块,部分原因是您可以显式调用它们进行测试,也因为它们必然会迫使您仅初始化一个字段。 (我在很大程度上同意这是一个很好的建议,但是请注意,您在方法中失去了明确的赋值检查-如上所示,这可能有助于捕获某些类型的错误。)

最后:使用private static final ComplexObject sdf = new ComplexObject();,因为您不需要任何更复杂的操作。


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

答案 1 :(得分:1)

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

因此,唯一可以指导决策的就是:干净编码的思想,例如:什么是人类最容易理解/理解的方式。

我认为:如果可能,您可以选择选项1。如果表达式不太复杂,并且可以简单地看一下并理解SOME_CONSTANT = some expression,那么为什么还要增加方法调用/初始化程序块的复杂性,使事情复杂化?

但是,当然:当表达式已经“复杂”了,并且您很想写一条注释来解释为什么为什么以特定方式运行时,那么一个辅助方法是一个很好的选择理念。一个有用的方法名称可能会解释需要解释的内容(省去您的注释!)

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

答案 2 :(得分:0)

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

如果您只想实例化对象一次,但是初始化非常昂贵(ComplexObject可能会提示这种情况),则您可能只想在对象实际使用后(即第一次)初始化该对象。它被访问(lazy initializationlazy loading)。

基本上,您将ComplexObject隐藏在静态获取器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来创建实例,该get()使用通过{{1 }},但您可以在代理中以任何所需的方式实现惰性初始化。

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();
}