【问题标题】:Necessity of static block in JavaJava中静态块的必要性
【发布时间】:2012-12-09 07:37:33
【问题描述】:

我发现在Java中,有一个叫做static block的特性,它包含了一个类第一次加载时执行的代码(我不明白'loaded'是什么意思,是不是表示已初始化?)。是否有任何理由在静态块内而不是在构造函数中进行初始化位?我的意思是,即使是构造函数也做同样的事情,在类第一次初始化时做所有必要的事情。静态块有什么是构造函数不能完成的吗?

【问题讨论】:

  • 类加载器加载类时调用一次。不是每次实例化类。
  • 请查看官方 Java 教程(来自 Oracle)的以下章节:Understanding Instance and Class MembersInitializing Fields。如果您仍有任何疑问,请回来。
  • 只是一个不合理的问题,如果一个类恰好同时具有main 和一个构造函数,那么在创建该类的实例时哪个会更早调用?
  • 初始化代码(类加载时间)-> Main(应用程序入口点)-> 构造函数:这里有一个working example来进一步说明这一点。
  • @Cupidvogel main 方法不会在每次创建类的实例时运行。它仅在显式调用(就像任何其他静态方法一样)或 JVM 启动应用程序时运行。

标签: java static initialization


【解决方案1】:

我首先想从你的问题中强调一件事:

构造函数做同样的事情,当一个类第一次初始化时做所有必要的事情

这是不正确的。当创建一个类的 instance 时,构造函数会进行所有必要的初始化。当类本身首次加载到内存并初始化时,不会执行任何构造函数(除非该类的实例恰好是作为类初始化的一部分创建的)。这种混淆(在初始化类和初始化类的实例之间)可能是您质疑 static 块的实用性的原因。

如果类具有需要复杂初始化的静态成员,则可以使用 static 块。假设您需要某种静态地图(此处的目的无关紧要)。您可以像这样内联声明它:

public static final Map<String, String> initials = new HashMap<String, String>();

但是,如果您想填充一次,则不能使用内联声明来执行此操作。为此,您需要一个 static 块:

public static final Map<String, String> initials = new HashMap<String, String>();
static {
    initials.put("AEN", "Alfred E. Newman");
    // etc.
}

如果您想更有保护性,可以这样做:

public static final Map<String, String> initials;
static {
    Map<String, String> map = new HashMap<String, String>()
    map.put("AEN", "Alfred E. Newman");
    // etc.
    initials = Collections.unmodifiableMap(map);
}

请注意,您不能将initials in-line 初始化为不可修改的映射,因为这样您就无法填充它!您也不能在构造函数中执行此操作,因为只需调用其中一种修改方法(put 等)就会产生异常。

公平地说,这不是您问题的完整答案。 static 块仍然可以通过使用私有静态函数来消除:

public static final Map<String, String> initials = makeInitials();

private static Map<String, String> makeInitials() {
    Map<String, String> map = new HashMap<String, String>()
    map.put("AEN", "Alfred E. Newman");
    // etc.
    return Collections.unmodifiableMap(map);
}

不过,请注意,这并没有像您建议的那样用构造函数中的代码替换 static 块!此外,如果您需要以相互关联的方式初始化多个 static 字段,这将不起作用。

static 块难以替换的情况是“主”类需要仅初始化几个其他类。

public class Master {
    static {
        SlaveClass1.init();
        SlaveClass2.init(SlaveClass1.someInitializedValue);
        // etc.
    }
}

特别是如果您不想将任何依赖硬连接到SlaveClass2 上的SlaveClass1,则需要某种类似这样的主代码。这种东西绝对不属于构造函数。

请注意,还有一个称为实例初始化块的东西。它是在创建每个实例时运行的匿名代码块。 (语法就像static 块,但没有static 关键字。)它对匿名类特别有用,因为它们不能有命名构造函数。这是一个真实的例子。由于(深不可测)GZIPOutputStream 没有构造函数或任何可用于指定压缩级别的 api 调用,并且默认压缩级别为无,因此您需要子类化 GZIPOutputStream 以获得任何压缩。你总是可以写一个显式的子类,但是写一个匿名类会更方便:

OutputStream os = . . .;
OutputStream gzos = new GZIPOutputStream(os) {
    {
        // def is an inherited, protected field that does the actual compression
        def = new Deflator(9, true); // maximum compression, no ZLIB header
    }
};

【讨论】:

  • 为什么我不能在构造函数里面做呢?
  • 静态初始化器的另一个典型用法是为具有本地方法的类加载本地库,例如:static { System.loadLibrary ("foo"); }
  • @Cupidvogel - 由于变量同时声明为staticfinal(因此无法更改),因此该语言不允许您在构造函数中执行此操作。
  • @Cupidvogel,您了解类的概念并对其进行实例化吗?如果您将该代码放在构造函数中,那么每次您创建一个新实例时,都会再次添加相同的条目。您希望这种行为在类级别发生一次,而不是针对每个实例。
  • @Cupidvogel - 我认为我发布的是构造函数无法执行静态块可以执行的操作的情况。 (请注意,这不是构造函数不应该使用的情况;这是构造函数不能使用的情况。)
【解决方案2】:

您可以理解静态块的一种方法是; 它充当构造函数。但是,两者之间的区别是 静态块实例化类或静态变量,而构造函数用于实例化对象变量

考虑下面的类

public class Part{

  String name;
  static String producer;

  public Part(String name){
      this.name = name;
  }

  static {
    producer = "Boeing";
  }

}

从此类创建的对象的生产者设置为波音,但它们的名称根据传递的参数而有所不同。比如

Part engine = new Part("JetEngine");
Part Wheel = new Part("JetWheel");

【讨论】:

    【解决方案3】:

    当您必须执行一些操作时,静态块对构造函数很有用,即使仍然没有创建实例。例如,用于初始化具有非静态值的静态变量。

    【讨论】:

      【解决方案4】:

      在创建类的实例时调用构造函数。

      当类加载器加载这个类定义时调用静态块,以便我们可以初始化这个类的静态成员。 我们不应该从构造函数初始化静态成员,因为它们是类定义的一部分而不是对象

      【讨论】:

      • 请解释一下“加载”是什么意思。
      • 每个类都需要通过类加载器加载到jvm,当在代码中请求该类类型的对象或在代码中请求静态变量时,可能会发生这种情况。例如: int b= ClassA.staticMenber; staticMember 是 ClassA 中存在的静态变量,可以在静态块中初始化。
      • 所以本质上你的意思是'当一个类的实例第一次被'加载'创建时',对吧?
      • 请查看编辑。可以是创建实例时,也可以是静态引用。
      • +1 for 我们不应该从构造函数初始化静态成员,因为它们是类定义的一部分而不是对象
      【解决方案5】:

      如果我们初始化一个类,静态初始化程序将运行,这不需要我们实例化一个类。但是构造函数只有在我们创建类的实例时才会运行。

      例如:

      class MyClass
      {   
          static
          {
              System.out.println("I am static initializer");
          }
          MyClass()
          {
              System.out.println("I am constructor");
          }
      
          static void staticMethod()
          {
              System.out.println("I am static method");
          }
      }
      

      如果我们运行:

      MyClass.staticMethod();
      

      输出:

      I am static initializer
      I am static method
      

      我们从未创建过实例,因此不会调用构造函数,而是调用静态初始化器。

      如果我们创建一个类的实例,静态初始化器和构造器都会运行。没有惊喜。

      MyClass x = new MyClass();
      

      输出:

      I am static initializer
      I am constructor
      

      请注意,如果我们运行:

      MyClass x;
      

      输出:(空)

      声明变量x不需要初始化MyClass,所以静态初始化器不会运行。

      【讨论】:

      • 您误解了正在发生的事情。静态块类初始化时运行。只是简单地声明一个变量MyClass x 不需要加载或初始化类。 Java 足够聪明,知道它可以推迟加载和初始化类,直到实际需要有关它的某些内容(除了它的名称)。 (顺便说一句,简单地引用类的静态字段也会导致类被初始化。您不需要创建实例或调用方法。)
      • 感谢您的澄清!
      • @Ted Hopp 我编辑了我的答案以反映你的观点,这基本上是我现在回答的要点。
      • 改进很多。但是,我看不出这如何解决 OP 的问题。
      • @Ted Hopp,OP基本上是在询问静态初始化程序和构造函数之间的区别。 OP 的最后一个问题是“静态块是否可以完成构造函数无法完成的任何事情”。我的回答说明,当未创建 MyClass 的实例时运行静态初始化程序,而未创建构造函数。这是静态初始化器能做的,而构造器不能做的事情。
      【解决方案6】:

      当您想要初始化静态字段时,静态块很有用。

      【讨论】:

        【解决方案7】:

        静态初始化程序会在加载类时运行,即使您从未创建过该类型的任何对象。

        • 并非所有类都需要实例化。构造函数可能永远不会被调用。它甚至可能是私人的。
        • 您可能希望在运行构造函数之前访问类的静态字段。
        • 静态初始化程序仅在加载类时运行一次。为您实例化的该类型的每个对象调用构造函数。

        【讨论】:

          【解决方案8】:

          static 块的作用与构造函数不同。基本上有两个不同的概念。

          静态块在类加载到内存时初始化,这意味着当JVM读取你的字节码时。 初始化可以是任何东西,它可以是变量初始化或任何其他应该由该类的所有对象共享的东西

          而构造函数只为该对象初始化变量。

          【讨论】:

            【解决方案9】:

            当您必须执行某些操作时,即使仍然没有创建任何实例,静态块也很有用。例如,用于初始化具有非静态值的静态变量。

            【讨论】:

              【解决方案10】:

              它们是两个不同的东西。您使用构造函数来初始化类的一个实例,静态初始化块在加载类时初始化静态成员。

              【讨论】:

                【解决方案11】:

                你不能用构造函数初始化静态变量——或者至少你可能不应该,而且它不会特别有用。

                特别是当您尝试初始化需要大量逻辑来生成的静态常量时,这确实应该发生在静态块中,而不是构造函数中。

                【讨论】:

                • 我不是反对者,但我怀疑问题在于技术上您可以在构造函数中初始化静态变量。
                • 我完全同意你的看法,这只是关于否决票原因的理论。
                猜你喜欢
                • 2015-03-26
                • 1970-01-01
                • 1970-01-01
                • 2011-02-25
                • 1970-01-01
                • 2010-11-05
                • 1970-01-01
                • 2014-06-05
                相关资源
                最近更新 更多