在Java中使用静态构造函数


11

我没有完全理解Java中的静态构造函数。如果允许,为什么要允许?在什么情况下会使用它?它有什么作用?有人可以给我一个简单的例子吗?


2
分享您的研究成果对所有人都有帮助。告诉我们您尝试过的内容以及为什么它不能满足您的需求。这表明您已花时间尝试自我帮助,这使我们免于重复显而易见的答案,并且最重要的是,它可以帮助您获得更具体和相关的答案。另请参见“ 如何问”
t 2014年

3
没有所谓的“静态构造函数”。
戴维·康拉德

Answers:


28

严格来说,Java没有静态构造函数,因为根据定义,构造函数不能静态的。您所指的是所谓的“静态初始化块”。构造函数意味着您正在构造一个对象。您不能为类具有构造函数,因为类不是其本身的实例。它只是一个类。“构造”该类的东西称为编译器(或虚拟机,具体取决于“构造”的含义),如果您打算在其他代码中构造代码,那么您将进入代码生成,即完全不同的野兽

除了所有挑剔的问题,静态初始化块还用于初始化类的复杂静态(或类级别)字段。通常,它们用于初始化无法在一行中初始化的事物,或者要求首先初始化某些其他对象(该对象可能在实现静态块的类中,也可能不在该类中)。

基本上,可以使用它们来告诉类“嘿,首先将变量A设置为该值,然后,一旦完成,就使用A的值初始化B。” 由于Java要求标准字段初始化必须在构造函数或方法内完成,或者通过构造函数或方法的调用来完成(除非它是文字),因此对于初始化复杂的静态对象,这些方法可能是一种便捷的方法。

静态初始化块并不是经常需要的,除非有实际用途,否则通常应避免使用它们。别误会,它们在Java中占有一席之地,但是像许多其他事情(例如break,return,switch和goto语句)一样,它们很容易被过度使用,从而降低了代码的可读性和可维护性。 -使用它们的基础。

下面是一个使用静态初始化块的简短示例(根据此处找到的关于静态初始化块的出色解释):

码:

public class StaticExample{
    static {
        System.out.println("This is first static block");
    }

    public StaticExample(){
        System.out.println("This is constructor");
    }

    public static String staticString = "Static Variable";

    static {
        System.out.println("This is second static block and "
                                                    + staticString);
    }

    public static void main(String[] args){
        StaticExample statEx = new StaticExample();
        StaticExample.staticMethod2();
    }

    static {
        staticMethod();
        System.out.println("This is third static block");
    }

    public static void staticMethod() {
        System.out.println("This is static method");
    }

    public static void staticMethod2() {
        System.out.println("This is static method2");
    }
}    

输出:

This is first static block
This is second static block and Static Variable
This is static method
This is third static block
This is constructor
This is static method2

它们列出了静态块有用的某些实例:

  • 如果要将驱动程序和其他项加载到名称空间中。例如,Class类有一个静态块在其中注册本机。
  • 如果需要进行计算以初始化静态变量,则可以声明一个静态块,该类在首次加载该类时会被执行一次。
  • 与安全性相关的问题或与日志记录相关的任务

一些不使用静态块的原因(在其他情况下):

  • JVM的局限性在于静态初始化程序块不得超过64K。
  • 您不能抛出Checked Exception。
  • this由于没有实例,因此无法使用关键字。
  • 您不应该尝试访问super,因为静态块没有这种东西。
  • 您不应从该块返回任何内容。
  • 静态块使测试成为噩梦。

我应该注意:虽然某些语言(例如C#)可能具有静态的“构造函数”语法,但这些“构造函数”的功能与Java中的静态初始化块的方式几乎相同,并且被许多(包括我自己)视为给出了OOP构造函数的基本概念后,该语言中的名词错误。


关于静态块的链接不再起作用
ASh

5

它用于初始化比简单分配它更难的字段:

public class Example{

    public final static Map<String, String> preFilledField;

    static{
        Map<String, String> tmp = new HashMap<>();
        //fill map
        preFilledField = Collections.unmodifiableMap(tmp);
    }
}

不可能在初始化时填写地图(除非您使用匿名子类hack),因此这是保证在首次使用前将其填满的最佳方法

您也可以在初始化时捕获已检查的异常


这是我所见过的主要用途之一,但是由于Java 9中包含了collections框架的静态工厂方法,因此该用例确实已经过时了。
Hangman4358 '18

2

Gurgadurgen的答案可能是您正在寻找的答案,但是我将添加一些其他的观点,当有人想要“静态构造函数”时,这些观点有时会被忽略。

如果要使用静态方法创建类的实例,则可以创建仅调用类的构造函数的静态方法。

public class Example
{
    /** Static method to create an instance. */
    public static Example build()
    { return new Example() ; }

    /** A field of each instance. */
    private String stuff ;

    /** The class's actual constructor. */
    public Example()
    { stuff = new String() ; }

    public String getStuff()
    { return this.stuff ; }

    /**
     * Mutator for "stuff" property. By convention this returns "void"
     * but you might want to return the object itself, to support the
     * sort of chained invocations that are becoming trendy now. You'll
     * see the stylistic benefits of chaining later in this example.
     */
    public Example setStuff( String newStuff )
    {
        this.stuff = newStuff ;
        return this ;
    }
}

public class ExampleTest
{
    public static void main( String[] args )
    {
        // The usual instance model.
        Example first = new Example() ;
        System.out.println( first.setStuff("stuff").getStuff() ) ;

        // Using your static method to construct an instance:
        Example second = Example.build() ;
        System.out.println( second.setStuff("more stuff").getStuff() ) ;

        // Chaining all the invocations at once:
        System.out.println( Example.build().setStuff("even more stuff").getStuff() ) ;
    }
}

产生输出:

stuff
more stuff
even more stuff

使用静态方法构造实例的另一个原因是,当您要确保在任何给定时间都确实存在类的一个实例时,就会出现这种情况。这称为单例。按照约定,此类将提供一个静态方法getInstance()方法称为获得唯一的一个实例,该实例被视为“单例”。

public class SingletonExample extends Example
{
    // Note: This extends my previous example, which has a "stuff"
    // property, and a trivial constructor.

    /** The singleton instance, statically initialized as null. */
    private static SingletonExample singleton = null ;

    /**
     * The static accessor for the singleton. If no instance exists,
     * then it will be created; otherwise, the one that already exists
     * will be returned.
     */
    public static SingletonExample getInstance()
    {
        if( singleton == null )
            singleton = new SingletonExample() ;
        return singleton ;
    }
}

public class SingletonExampleTest
{
    public static void main( String[] args )
    {
        System.out.println( SingletonExample.getInstance().setStuff("stuff").getStuff() ) ;

        // You could still create instances of this class normally if you want to.
        SingletonExample otherstuff = new SingletonExample() ;
        otherstuff.setStuff("other stuff") ;

        // But watch what happens to this.
        System.out.println( SingletonExample.getInstance().getStuff() ) ;
        System.out.println( otherstuff.getStuff() ) ;

        // Now we show what happens when you start modifying the singleton.
        SingletonExample theoneandonly = SingletonExample.getInstance() ;
        theoneandonly.setStuff("changed stuff") ;
        System.out.println( SingletonExample.getInstance().getStuff() ) ;
    }
}

这将产生以下结果。

stuff
stuff
other stuff
changed stuff

通过存储对单例的引用,然后对其进行修改,下一个调用将getInstance()获取该修改后的单例。

使用单例作为开始为应用程序创建全局变量的一种方法很诱人。在某些情况下,这可能很有用,但也会给您带来麻烦。特别是在开发Android应用程序时遇到了一个有趣的错误,其中单例实例可能会丢失。从一种活动跳转到另一种活动有时可能导致JVM使用新的“类加载器”,而该类加载器不会知道以前的类加载器静态存储的单例。


0

尽管我知道有很多人将“静态构造函数”的概念误称为“错误”,但我认为情况并非如此。问题在于构造类及其实例对象。在其他线程中已经指出,构造类是编译器的工作。即使在Java中,这也只有一半正确。

编译器为每个类定义构造一个支架。支架包含有关类以及在构建时实例应包含哪些实例的元数据。如果一个类定义了一个分配了常量原始值的字段,则编译器会将其包含在支架中。对于分配的任何其他值类型,编译器会生成一个初始化例程,该例程将在创建第一个类实例之前运行1次,以使用适当的值更新支架。编译器无法完成此更新

由于对支架的这种一次性更新通常是实例构造函数正常运行的关键先决条件,因此将其称为构造函数类型也是合理的。这就是为什么在支持该概念的通用OO语言中,将其称为静态构造函数的原因。Java静态初始化块背后的概念不过是语义上的更改,以与Java程序员应该在系统和实现方面均不可知的观念保持一致。


0

正如其他答案所言,您可以编写构造对象的静态方法。这就解决了Java(以及许多其他语言)缺少命名构造函数的问题。Delphi确实支持命名构造函数。您可以使用参数的类型和顺序,但这会导致代码不清晰和脆弱。

例如,我们可以发明一种方案,其中可以从XML字符串或JSON字符串构造您的对象。您可以编写如下方法:

static MyObject createFromXml(String xml);
static MyObject createFromJson(String json);

我很少使用此方法来替代带有命名初始化方法的无参数构造函数:

MyObject myObject = new MyObject();
myObject.loadXml(xml). 

您可以在实现类内部的构建器模式时考虑使用静态create方法。


当我读到这个问题时,它是关于静态初始化程序的,而不是关于正常静态方法的。
CodesInChaos

-2

您可以查看“静态”部分,就像类级构造函数用于初始化类属性一样(在Java中为静态)。与用于初始化实例级别属性的“常规”构造器相同。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.