【问题标题】:Static Initialization Blocks静态初始化块
【发布时间】:2011-01-26 02:19:52
【问题描述】:

据我了解,如果不能在一行中完成,则“静态初始化块”用于设置静态字段的值。

但我不明白为什么我们需要一个特殊的块。例如,我们将一个字段声明为静态的(没有赋值)。然后编写几行代码,为上面声明的静态字段生成并赋值。

为什么我们需要将这些行放在一个特殊的块中,例如:static {...}

【问题讨论】:

  • 少量反馈,但如果您能清楚地陈述您的假设并因此澄清哪个答案是正确的,这将有所帮助。当我第一次阅读您的问题时,我误解了并认为您知道{...}static {...} 之间的区别。 (在这种情况下,Jon Skeet 肯定更好地回答了你的问题)
  • 这个问题很不清楚;你让回答者争先恐后地对你的意思做出很多冗长的猜想。如何明确写出您想到的示例静态初始化块和替代方案,以便人们有明确的答案?

标签: java static initialization static-block initialization-block


【解决方案1】:

非静态块:

{
    // Do Something...
}

每次调用一个类的实例。 静态块只会在类本身被初始化时被调用一次,无论你创建了多少该类型的对象。

例子:

public class Test {

    static{
        System.out.println("Static");
    }

    {
        System.out.println("Non-static block");
    }

    public static void main(String[] args) {
        Test t = new Test();
        Test t2 = new Test();
    }
}

打印出来:

Static
Non-static block
Non-static block

【讨论】:

  • 它回答了这个问题:“每次构造类时都会调用它。无论您创建多少该类型的对象,静态块只会被调用一次。”
  • 对于好奇的读者,Java 编译器实际上将非静态块复制到类的每个构造函数中 (source)。所以初始化字段仍然是构造函数的工作。
  • 为什么这个答案突然被否决了?您可能不同意这是公认的答案,但它肯定不会有任何错误或误导性。它只是试图通过一个简单的例子来帮助理解这些语言结构。
  • 也许这不是问题的真正答案,但可以阅读真正的答案来回答我的问题。 :-) 这是因为它上升了。真正的答案也得到了提升,因为它是公正的。
  • 执行顺序注意事项:无论在代码中的位置如何,总是先调用静态块。
【解决方案2】:

如果它们不在静态初始化块中,它们会在哪里?您将如何声明一个仅用于初始化目的的本地变量,并将其与字段区分开来?例如,想怎么写:

public class Foo {
    private static final int widgets;

    static {
        int first = Widgets.getFirstCount();
        int second = Widgets.getSecondCount();
        // Imagine more complex logic here which really used first/second
        widgets = first + second;
    }
}

如果firstsecond 不在一个块中,它们看起来就像字段。如果它们位于前面没有static 的块中,那将被视为实例初始化块而不是静态初始化块,因此它将在 per 构造实例后执行一次,而不是在总计。

现在在这种特殊情况下,您可以改用静态方法:

public class Foo {
    private static final int widgets = getWidgets();

    static int getWidgets() {
        int first = Widgets.getFirstCount();
        int second = Widgets.getSecondCount();
        // Imagine more complex logic here which really used first/second
        return first + second;
    }
}

...但是当您希望在同一个块中分配多个变量时,这不起作用,或者没有(例如,如果您只想记录一些东西 - 或者可能初始化一个本地库)。

【讨论】:

  • 静态块是在分配静态变量之前还是之后发生? private static int widgets = 0; static{widgets = 2;}
  • 很好奇静态块是在分配静态变量之前还是之后发生的。例如private static int widgets = 0; static{widgets = 2;} 发现‘=’赋值是按顺序进行的,也就是说先赋值的‘=’先赋值。上面的例子会给'widgets'一个值2。(PS不知道cmets只能在5分钟内编辑......)
  • @WeishiZeng:是的,这在docs.oracle.com/javase/specs/jls/se8/html/… - 第9点中有记录。
  • 但是您不能也使用与静态初始化块具有完全相同代码的私有静态方法并将小部件分配给私有静态方法吗?
  • @Zachary:你的意思是返回值,并分配方法调用的结果?如果是这样,是的 - 当您 正在 作为块的结果分配给一个变量时。将在大约 7 小时内用详细信息编辑我的答案...
【解决方案3】:

这是一个例子:

  private static final HashMap<String, String> MAP = new HashMap<String, String>();
  static {
    MAP.put("banana", "honey");
    MAP.put("peanut butter", "jelly");
    MAP.put("rice", "beans");
  }

“静态”部分中的代码将在类加载时执行,在构造任何类实例之前(以及在从其他地方调用任何静态方法之前)。这样你就可以确保类资源都可以使用了。

也可以有非静态初始化块。这些行为类似于为类定义的构造方法集的扩展。它们看起来就像静态初始化块,除了关键字“static”被省略了。

【讨论】:

  • 对于那个特定的例子,有时 双大括号 模式被“滥用”了:)
  • 它可以被滥用,但另一方面它确实清理了一些混乱,并使某些类型的代码更加“可靠”。我在 Erlang 中编程是为了好玩,而你却迷上了不需要局部变量 :-)
  • >(上面的答案中提到了“尖”)这是在静态块执行时需要注意的非常重要的一点。
  • 我们可以在 afterPropertiesSet 方法之后使用 InitializingBean 来做到这一点吗?
【解决方案4】:

当您实际上不想将值分配给任何东西时,它也很有用,例如在运行时加载某些类只一次

例如

static {
    try {
        Class.forName("com.example.jdbc.Driver");
    } catch (ClassNotFoundException e) {
        throw new ExceptionInInitializerError("Cannot load JDBC driver.", e);
    }
}

嘿,还有一个好处,你可以用它来处理异常。想象一下getStuff() 在这里抛出了一个Exception,它真的 属于一个catch 块:

private static Object stuff = getStuff(); // Won't compile: unhandled exception.

那么 static 初始化器在这里很有用。您可以在那里处理异常。

另一个例子是事后做一些在分配期间不能做的事情:

private static Properties config = new Properties();

static {
    try { 
        config.load(Thread.currentThread().getClassLoader().getResourceAsStream("config.properties");
    } catch (IOException e) {
        throw new ExceptionInInitializerError("Cannot load properties file.", e);
    }
}

回到 JDBC 驱动程序示例,任何体面的 JDBC 驱动程序本身也使用 static 初始化程序在 DriverManager 中注册自己。另请参阅thisthis 答案。

【讨论】:

  • 这里有危险的巫术......静态初始化程序在合成的 clinit() 方法中运行,该方法是隐式同步的。这意味着 JVM 将获得对相关类文件的锁定。如果两个类尝试相互加载,并且每个类都开始在不同的线程中加载,这可能会导致多线程环境中的死锁。见www-01.ibm.com/support/docview.wss?uid=swg1IV48872
  • @Ajax:我认为这是相关 JDBC 驱动程序或负责加载它的应用程序代码中的错误。通常,在良好的 JDBC 驱动程序的情况下,只要您在应用程序启动期间仅在应用程序范围内加载一次,就没有任何问题。
  • 这肯定是一个错误,但不完全是 JDBC 驱动程序的错。也许驱动程序无辜地拥有自己的静态初始化器,也许你无辜地初始化这个类以及应用程序中的其他一些类,哦不,一些意想不到的类循环加载彼此,现在你的应用程序死锁。由于 java.awt.AWTEvent 和 sun.util.logging.PlatformLogger 之间的死锁,我发现了这一点。我只是触摸了 AWTEvent 来告诉它无头运行,而其他一些库最终加载了 PlatformLogger ... AWTEvent 也加载了。
  • 两个类最终在不同的线程上同步,我的构建在大约 1/150 次运行时陷入僵局。所以,我现在对静态块中的类加载更加小心。在我上面提到的情况下,使用延迟提供程序模式,我可以立即创建一个临时提供程序类(没有死锁的机会),初始化字段,然后在实际访问它时(在非同步字段访问中),然后我实际上加载了可能导致死锁的类。
【解决方案5】:

我会说static block 只是语法糖。 static 块和其他任何东西都不能做任何事情。

重复使用此处发布的一些示例。

这段代码可以在不使用static初始化器的情况下重写。

方法#1:使用static

private static final HashMap<String, String> MAP;
static {
    MAP.put("banana", "honey");
    MAP.put("peanut butter", "jelly");
    MAP.put("rice", "beans");
  }

方法#2:不使用static

private static final HashMap<String, String> MAP = getMap();
private static HashMap<String, String> getMap()
{
    HashMap<String, String> ret = new HashMap<>();
    ret.put("banana", "honey");
    ret.put("peanut butter", "jelly");
    ret.put("rice", "beans");
    return ret;
}

【讨论】:

    【解决方案6】:

    它必须存在的几个实际原因:

    1. 正在初始化 static final 成员,其初始化可能会引发异常
    2. 使用计算值初始化 static final 成员

    人们倾向于使用 static {} 块作为一种方便的方式来初始化类在运行时中所依赖的东西 - 例如确保加载特定类(例如,JDBC 驱动程序)。这可以通过其他方式完成;但是,我上面提到的两件事只能通过 static {} 块这样的构造来完成。

    【讨论】:

      【解决方案7】:

      在静态块中构造对象之前,您可以为类执行一次代码。

      例如

      class A {
        static int var1 = 6;
        static int var2 = 9;
        static int var3;
        static long var4;
      
        static Date date1;
        static Date date2;
      
        static {
          date1 = new Date();
      
          for(int cnt = 0; cnt < var2; cnt++){
            var3 += var1;
          }
      
          System.out.println("End first static init: " + new Date());
        }
      }
      

      【讨论】:

        【解决方案8】:

        认为静态块只能访问静态字段是一种常见的误解。为此,我想在下面展示我在实际项目中经常使用的一段代码(部分复制自another answer,但上下文略有不同):

        public enum Language { 
          ENGLISH("eng", "en", "en_GB", "en_US"),   
          GERMAN("de", "ge"),   
          CROATIAN("hr", "cro"),   
          RUSSIAN("ru"),
          BELGIAN("be",";-)");
        
          static final private Map<String,Language> ALIAS_MAP = new HashMap<String,Language>(); 
          static { 
            for (Language l:Language.values()) { 
              // ignoring the case by normalizing to uppercase
              ALIAS_MAP.put(l.name().toUpperCase(),l); 
              for (String alias:l.aliases) ALIAS_MAP.put(alias.toUpperCase(),l); 
            } 
          } 
        
          static public boolean has(String value) { 
            // ignoring the case by normalizing to uppercase
            return ALIAS_MAP.containsKey(value.toUpper()); 
          } 
        
          static public Language fromString(String value) { 
            if (value == null) throw new NullPointerException("alias null"); 
            Language l = ALIAS_MAP.get(value); 
            if (l == null) throw new IllegalArgumentException("Not an alias: "+value); 
            return l; 
          } 
        
          private List<String> aliases; 
          private Language(String... aliases) { 
            this.aliases = Arrays.asList(aliases); 
          } 
        } 
        

        这里初始化器用于维护一个索引(ALIAS_MAP),将一组别名映射回原始枚举类型。它旨在作为 Enum 本身提供的内置 valueOf 方法的扩展。

        如您所见,静态初始化程序甚至访问private 字段aliases。重要的是要了解static 块已经可以访问Enum 值实例(例如ENGLISH)。这是因为order of initialization and execution in the case of Enum types,就像static private 字段在调用static 块之前已经用实例初始化:

        1. Enum 常量是隐式静态字段。这需要 Enum 构造函数和实例块,以及实例初始化首先发生。
        2. static 按出现顺序阻止和初始化静态字段。

        这种无序初始化(static 块之前的构造函数)需要注意。当我们使用类似于 Singleton 的实例初始化静态字段时也会发生这种情况(进行了简化):

        public class Foo {
          static { System.out.println("Static Block 1"); }
          public static final Foo FOO = new Foo();
          static { System.out.println("Static Block 2"); }
          public Foo() { System.out.println("Constructor"); }
          static public void main(String p[]) {
            System.out.println("In Main");
            new Foo();
          }
        }
        

        我们看到的是以下输出:

        Static Block 1
        Constructor
        Static Block 2
        In Main
        Constructor
        

        很明显,静态初始化实际上可以发生在构造函数之前,甚至之后:

        只需在 main 方法中访问 Foo,就会加载类并开始静态初始化。但是作为静态初始化的一部分,我们再次调用静态字段的构造函数,之后它恢复静态初始化,并完成从 main 方法中调用的构造函数。相当复杂的情况,我希望在正常编码中我们不必处理。

        有关这方面的更多信息,请参阅“Effective Java”一书。

        【讨论】:

        • 可以访问aliases 并不意味着静态块可以访问非静态成员。 aliases 通过 /static/ values() 方法返回的 Language 值访问。正如您所提到的,枚举变量此时已经可用的事实是不寻常的 - 在这种情况下将无法访问常规类的非静态成员。
        • 静态块仍然只访问静态字段(在您的枚举 ENGLISH、GERMAN、... 的情况下),在这种情况下是对象。由于静态字段本身就是对象,因此您可以访问静态对象的实例字段。
        • class Foo { static final Foo Inst1; static final Foo Inst2; static{ Inst1 = new Foo("Inst1"); Inst2 = new Foo("Inst2"); } static { System.out.println("Inst1: " + Inst1.member); System.out.println("Inst2: " + Inst2.member); } private final String member; private Foo(String member){ this.member = member; } } 上面的代码与枚举示例没有什么不同,仍然允许访问静态块内的实例变量
        • @SwamiPR 确实可以编译,令我惊讶的是,我不得不同意代码原则上没有什么不同。我必须重新阅读Java规范,我觉得我错过了一些东西。很好的回应,谢谢。
        • @SwamiPR 真正的问题是我们应该使用Enum。这是保证我们指向单个实例的最佳方法 - 请参阅here。根据您的观点,我已经做了一些更新。
        【解决方案9】:

        所以你有一个静态字段(它也被称为“类变量”,因为它属于类而不是类的实例;换句话说,它与类相关联,而不是与任何对象相关联)并且你想要初始化它。因此,如果您不想创建此类的实例并且想要操作此静态字段,则可以通过三种方式进行:

        1- 声明变量时初始化它:

        static int x = 3;
        

        2- 有一个静态初始化块:

        static int x;
        
        static {
         x=3;
        }
        

        3- 有一个访问类变量并初始化它的类方法(静态方法): 这是上述静态块的替代方案;你可以写一个私有静态方法:

        public static int x=initializeX();
        
        private static int initializeX(){
         return 3;
        }
        

        现在为什么要使用静态初始化块而不是静态方法?

        这完全取决于您在程序中需要什么。但是你必须知道静态初始化块被调用一次,类方法的唯一优点是如果你需要重新初始化类变量,它们可以在以后重用。

        假设您的程序中有一个复杂的数组。你初始化它(例如使用 for 循环),然后这个数组中的值将在整个程序中发生变化,但在某些时候你想重新初始化它(回到初始值)。在这种情况下,您可以调用私有静态方法。如果您不需要在程序中重新初始化值,您可以只使用静态块而无需静态方法,因为您以后不会在程序中使用它。

        注意:静态块按照它们在代码中出现的顺序被调用。

        示例 1:

        class A{
         public static int a =f();
        
        // this is a static method
         private static int f(){
          return 3;
         }
        
        // this is a static block
         static {
          a=5;
         }
        
         public static void main(String args[]) {
        // As I mentioned, you do not need to create an instance of the class to use the class variable
          System.out.print(A.a); // this will print 5
         }
        
        }
        

        示例 2:

        class A{
         static {
          a=5;
         }
         public static int a =f();
        
         private static int f(){
          return 3;
         }
        
         public static void main(String args[]) {
          System.out.print(A.a); // this will print 3
         }
        
        }
        

        【讨论】:

          【解决方案10】:

          如果您的静态变量需要在运行时设置,那么 static {...} 块非常有用。

          例如,如果您需要将静态成员设置为存储在配置文件或数据库中的值。

          当您想将值添加到静态 Map 成员时也很有用,因为您无法在初始成员声明中添加这些值。

          【讨论】:

            【解决方案11】:

            作为补充,就像@Pointy所说的

            “静态”部分中的代码将在类加载时执行 时间,在类的任何实例被构造之前(和之前 任何静态方法都是从其他地方调用的)。

            应该将System.loadLibrary("I_am_native_library")添加到静态块中。

            static{
                System.loadLibrary("I_am_a_library");
            }
            

            保证在相关库加载到内存之前不会调用native方法。

            根据loadLibrary from oracle

            如果使用相同的库名多次调用此方法, 第二次及以后的调用将被忽略。

            所以出乎意料的是,没有使用 System.loadLibrary 来避免库被多次加载。

            【讨论】:

              【解决方案12】:

              您首先需要了解您的应用程序类本身在运行时被实例化为java.class.Class 对象。这是运行静态块的时间。所以你实际上可以这样做:

              public class Main {
              
                  private static int myInt;
              
                  static {
                      myInt = 1;
                      System.out.println("myInt is 1");
                  }
              
                  //  needed only to run this class
                  public static void main(String[] args) {
                  }
              
              }
              

              它会打印“myInt is 1”到控制台。请注意,我没有实例化任何类。

              【讨论】:

                【解决方案13】:
                static int B,H;
                static boolean flag = true;
                static{
                    Scanner scan = new Scanner(System.in);
                    B = scan.nextInt();
                    scan.nextLine();
                    H = scan.nextInt();
                
                    if(B < 0 || H < 0){
                        flag = false;
                        System.out.println("java.lang.Exception: Breadth and height must be positive");
                    } 
                }
                

                【讨论】:

                  【解决方案14】:

                  静态块用于任何技术以动态方式初始化静态数据成员,或者我们可以说静态数据成员的动态初始化正在使用静态块..因为对于非静态数据成员初始化我们有构造函数但是我们没有可以动态初始化静态数据成员的地方

                  Eg:-class Solution{
                           // static int x=10;
                             static int x;
                         static{
                          try{
                            x=System.out.println();
                            }
                           catch(Exception e){}
                          }
                         }
                  
                       class Solution1{
                        public static void main(String a[]){
                        System.out.println(Solution.x);
                          }
                          }
                  

                  现在我的静态 int x 将动态初始化..Bcoz 当编译器转到 Solution.x 时,它将加载解决方案类并在类加载时加载静态块..所以我们可以动态初始化该静态数据成员..

                  }

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 2011-01-12
                    • 1970-01-01
                    • 1970-01-01
                    • 2016-09-12
                    • 1970-01-01
                    • 1970-01-01
                    • 2012-01-01
                    相关资源
                    最近更新 更多