Java静态字段初始化

8
我刚花了半个小时弄清楚这件事情,我已经成功修复了我的代码,但我并不完全理解发生了什么,并想知道是否有人能够阐明一下。
我有一个名为utils的类型类,其中包含一些静态字段(例如数据库连接端点),这些字段根据手头任务而被各种其他程序使用。基本上是一个库。
以下是之前的样子(尽管还有问题);
//DBUtils.java
public final class DBUtils {

    private static DBConnection myDBConnection = spawnDBConnection();
    private static DBIndex myDBIndex = null;

    private static DBConnection spawnDBConnection() {
        //connect to the database
        //assign a value to myDBIndex (by calling a method on the DBConnection object) <- IMPORTANT
        //myDbIndex NOT NULL HERE
        System.out.println("database connection completed");
        //return the DBConnection object
    }

    public static searchDB(String name) {
        //use the myDBIndex to find a row and return it
    }
}

简单来说,我使用静态的spawnDBConnection()方法为myDBConnectionmyDBIndex分配一个值。这很完美地工作,我的程序的第一行输出总是“数据库连接完成”,在spawnDBConnection()方法结束时,myDBConnection和myDBIndex都不为null,一切都如预期。

我的外部程序看起来像这样:

//DoSomethingUsefulWithTheDatabase.java
public final class DoSomethingUsefulWithTheDatabase {

    public static void main(String args[]) {
        DBUtils.searchDB("John Smith"); //fails with NullPointerException on myDBIndex!
    }
}

这个searchDB的调用发生在spawnDBConnection完成之后,我已经广泛使用标准输出来展示这一点。然而,一旦进入searchDB方法,myDBIndex的值为null!它是一个静态字段,在spawnDBConnection结束时不为null,没有进行其他赋值,现在却为null :(
简单的修复方法是删除“= null”,因此字段声明现在看起来像;
private static DBIndex myDBIndex;

为什么这会有所不同?我对这个问题感到非常困惑。

4
考虑将你的静态变量声明为 final。这样会强制你只能赋值一次,从而消除此类意外情况。 - Matt Ball
4
你在这里做的是一个可怕的噩梦式反模式。你将类的初始化与获取数据库连接耦合在一起。 - Marko Topolnik
你不应该静态初始化DBConnection。如果myDBConnection死了,你会开始在你的代码中使用DbUtils2吗? - Alexander Pogrebnyak
@MarkoTopolnik,我该如何使我的DBUtils类像现在这样易于使用(外部程序无需编写代码,只需跳入并开始使用静态方法),而不会造成混乱? - lynks
数据库连接管理是很困难的工作,你不应该从头开始。市面上有广泛使用、经过实战验证的连接池库。我最喜欢的是 boneCP。下载它看看吧,我认为你会发现它非常容易上手。 - Marko Topolnik
4个回答

14

那是因为将 null 赋值给 myDBIndex 是在之后进行的。

private static DBConnection myDBConnection = spawnDBConnection();

比如,覆盖了spawnDBConnection中的赋值操作。

步骤如下:

  1. 声明字段myDBConnectionmyDBIndex
  2. 初始化myDBConnection = spawnDBConnection();

    这包括调用spawnDBConnection和将返回值赋给myDBConnection

  3. 初始化myDBIndex(为null)

在您的第二个示例中,第3步不存在。


谢谢您澄清这一点,现在我完全明白了。由于某种原因,我一直认为任何方法调用都会发生在“简单”的赋值之后。 - lynks

7
为什么会造成这样的差异? 这个让我感到很困惑。 spawnDBConnection 的初始化器在运行,然后是 myDBIndex 的初始化器在运行。 myDBIndex 的初始化器将值设置为 null。由于这是在 spawnDBConnection 将其设置为非空值之后发生的,最终结果就是它为 null。
尽量不要这样做——通过静态初始化程序调用的方法来设置其他静态变量是奇怪的。

谢谢回复,我写这段代码的时候就知道在一个静态变量中初始化另一个静态变量是一件奇怪的事情,我只是为了进行一些快速测试而将代码拼凑在一起。 - lynks

1

这是在生成的static初始化器块中发生的情况:

static {
   myDBConnection = spawnDBConnection();
   myDBIndex = null;
}

我希望现在已经清楚了。


0
据我所知,如果您在字段之前定义方法,则在初始化时它会起作用,类从顶部解析
public class DbUtils {
    private static String spawnDBConnection() {
        System.out.println("database connection completed");
        return "INIT";
    }
    private static String myDBConnection = spawnDBConnection();
    private static int myDBIndex = 0;

    public static void main(final String[] args) {
        System.out.println(myDBConnection);
    }
}

输出:

database connection completed
INIT

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接