提问者:小点点

如何为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个答案

匿名用户

静态初始化程序的性能几乎无关紧要(尤其是对于这种琐碎的初始化),因为它只为一个类完成一次。

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

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

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

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

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

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

private static final ComplexObject sdf;

static {
  sdf = new ComplexObject(); 
}

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

静态初始化器有点像匿名方法。谷歌的内部Java实践建议尽可能使用方法而不是显式静态初始化器块,部分原因是您可以显式调用它们进行测试,但也因为它们必然会迫使您只初始化一个字段。(我基本上同意这是一个好建议,但请注意,您在方法中失去了明确的赋值检查——如上所述——这有助于捕获某些类型的bug)。

总之:使用私有静态最终ComplexObject sdf=new ComplexObject();,因为您不需要任何更复杂的东西。

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

匿名用户

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

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

我想:如果可能的话,你决定选择选项1。如果表达式不是太复杂,人们可以简单地看它并理解SOME_CONSTANT=一些表达式,为什么要增加方法调用/初始化块的复杂性来使事情复杂化?

但是当然:当表达式已经“复杂”时,你想写一个注释来解释为什么它以特定的方式做事,那么帮助方法是个好主意。单独一个有用的方法名称可能会解释需要解释的内容(节省你对注释的需求!)

换句话说:始终专注于编写最少量的代码,这也很容易阅读和理解。你不会因为可以而使用初始化方法,而是因为这样做会让事情更容易理解(在你的情况下:它不会)。初始化块(imho)更糟糕,仅仅是因为它们太罕见了。在我看来,它们是一种已知的异常,因为你现在甚至可以创建地图、列表…作为“文字”。

匿名用户

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

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

在其简单的文本形式中,您基本上将您的ComplexObject隐藏在静态getter私有静态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()的附加方法调用,您可以使用代理模式,如果您可以将ComplexObject的行为封装在接口中。然后代理应该将ComplexObject的所有方法调用委托给实际实例(在这种情况下,我使用前面的代码通过扩展Lazy来创建实例,它通过get()使用访问,但您可以在代理中以任何您想要的方式实现惰性init)。

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