首页 文章

在Java中使用Initializers与Constructors

提问于
浏览
85

所以我最近一直在研究我的Java技能,并且发现了一些我之前不知道的功能 . 静态和实例初始化器是两种这样的技术 .

我的问题是什么时候会使用初始化程序而不是在构造函数中包含代码?我想到了几个明显的可能性:

  • static / instance初始值设定项可用于设置“final”静态/实例变量的值,而构造函数不能

  • 静态初始值设定项可用于设置类中任何静态变量的值,这应该比在每个构造函数的开头具有“if(someStaticVar == null)// do stuff”代码块更有效

这两种情况都假设设置这些变量所需的代码比简单的“var = value”更复杂,否则似乎没有任何理由使用初始化器而不是在声明变量时简单地设置值 .

然而,虽然这些并非微不足道的收益(特别是设置最终变量的能力),但似乎应该使用初始化程序的情况相当有限 .

当然可以在构造函数中使用初始化器来完成很多工作,但我真的没有看到这样做的原因 . 即使一个类的所有构造函数共享大量代码,使用私有initialize()函数似乎比使用初始化程序更有意义,因为它不会阻止您在编写新代码时运行该代码构造函数 .

我错过了什么吗?是否还有许多其他情况需要使用初始化程序?或者它是否真的只是在非常具体的情况下使用的相当有限的工具?

9 回答

  • 13

    静态初始化器可用作提到的cletus,我以相同的方式使用它们 . 如果你有一个静态变量,在加载类时要初始化,那么静态初始化器就是要走的路,特别是因为它允许你进行复杂的初始化并且静态变量仍然是 final . 这是一个很大的胜利 .

    我发现"if (someStaticVar == null) // do stuff"很乱并且容易出错 . 如果它是静态初始化并声明 final ,那么你可以避免它被 null 的可能性 .

    但是,当你说:

    static / instance initializers可用于设置“final”静态/实例变量的值,而构造函数则不能

    我假设你说两个:

    • 静态初始值设定项可用于设置"final"静态变量的值,而构造函数则不能

    • 实例初始值设定项可用于设置"final"实例变量的值,而构造函数则不能

    你在第一点是正确的,第二点是错的 . 例如,您可以这样做:

    class MyClass {
        private final int counter;
        public MyClass(final int counter) {
            this.counter = counter;
        }
    }
    

    此外,当构造函数之间共享大量代码时,处理此问题的最佳方法之一是链构造函数,提供默认值 . 这使得很清楚正在做什么:

    class MyClass {
        private final int counter;
        public MyClass() {
            this(0);
        }
        public MyClass(final int counter) {
            this.counter = counter;
        }
    }
    
  • 3

    匿名内部类不能有构造函数(因为它们是匿名的),因此它们非常适合实例初始化程序 .

  • 4

    我经常使用静态初始化程序块来设置最终的静态数据,尤其是集合 . 例如:

    public class Deck {
      private final static List<String> SUITS;
    
      static {
        List<String> list = new ArrayList<String>();
        list.add("Clubs");
        list.add("Spades");
        list.add("Hearts");
        list.add("Diamonds");
        SUITS = Collections.unmodifiableList(list);
      }
    
      ...
    }
    

    现在这个例子可以用一行代码完成:

    private final static List<String> SUITS =
      Collections.unmodifiableList(
        Arrays.asList("Clubs", "Spades", "Hearts", "Diamonds")
      );
    

    但是静态版本可以更加整洁,特别是当项目初始化时非常重要 .

    一个天真的实现也可能不会创建一个不可修改的列表,这是一个潜在的错误 . 上面创建了一个不可变的数据结构,您可以愉快地从公共方法返回等等 .

  • 25

    只是为了增加一些已经很好的点 . 静态初始化程序是线程安全的 . 它在加载类时执行,因此比使用构造函数更简单的静态数据初始化,在构造函数中,您需要一个synchronized块来检查静态数据是否已初始化然后实际初始化它 .

    public class MyClass {
    
        static private Properties propTable;
    
        static
        {
            try 
            {
                propTable.load(new FileInputStream("/data/user.prop"));
            } 
            catch (Exception e) 
            {
                propTable.put("user", System.getProperty("user"));
                propTable.put("password", System.getProperty("password"));
            }
        }
    

    public class MyClass 
    {
        public MyClass()
        {
            synchronized (MyClass.class) 
            {
                if (propTable == null)
                {
                    try 
                    {
                        propTable.load(new FileInputStream("/data/user.prop"));
                    } 
                    catch (Exception e) 
                    {
                        propTable.put("user", System.getProperty("user"));
                        propTable.put("password", System.getProperty("password"));
                    }
                }
            }
        }
    

    不要忘记,您现在必须在类同步,而不是实例级别 . 这会导致每个实例构建的成本,而不是在加载类时的一次性成本 . 另外,它很难看;-)

  • 14

    我阅读了整篇文章,寻找初始化器与其构造函数的初始化顺序的答案 . 我没有找到它,所以我写了一些代码来检查我的理解 . 我以为我会把这个小小的演示添加为评论 . 为了测试您的理解,看看您是否可以在底部阅读之前预测答案 .

    /**
     * Demonstrate order of initialization in Java.
     * @author Daniel S. Wilkerson
     */
    public class CtorOrder {
      public static void main(String[] args) {
        B a = new B();
      }
    }
    
    class A {
      A() {
        System.out.println("A ctor");
      }
    }
    
    class B extends A {
    
      int x = initX();
    
      int initX() {
        System.out.println("B initX");
        return 1;
      }
    
      B() {
        super();
        System.out.println("B ctor");
      }
    
    }
    

    输出:

    java CtorOrder
    A ctor
    B initX
    B ctor
    
  • 53

    静态初始化程序相当于静态上下文中的构造函数 . 您肯定会比实例初始化程序更常见到 . 有时您需要运行代码来设置静态环境 .

    通常,实例初始化器最适合匿名内部类 . 看一下JMock's cookbook,看看一种创新的方式来使用它来使代码更具可读性 .

    有时,如果你有一些复杂的逻辑链接到构造函数(比如说你是子类,你不能调用this(),因为你需要调用super()),你可以通过在实例中执行常见的东西来避免重复initalizer . 然而,实例初始化是非常罕见的,它们对许多人来说是一种令人惊讶的语法,所以我避免使用它们,如果我需要构造函数行为,我宁愿让我的类具体而不是匿名 .

    JMock是一个例外,因为这就是框架的使用方式 .

  • 7

    我还想补充一点以及所有上述神话般的答案 . 当我们使用Class.forName(“”)在JDBC中加载驱动程序时,会发生类加载,并且会触发Driver类的静态初始化程序,并且其中的代码会将Driver注册到Driver Manager . 这是静态代码块的重要用途之一 .

  • 4

    您选择时必须考虑一个重要方面:

    Initializer blocks are members 类/对象,而 constructors are not . 在考虑 extension/subclassing 时这很重要:

    • Initializers are inherited 由子类 . (虽然,可以被遮蔽)
      这意味着基本上保证子类按父类的预期初始化 .
      但是,
    • Constructors are not inherited . (它们只是隐式调用 super() [即没有参数],或者您必须手动进行特定的 super(...) 调用 . )
      这意味着隐式或隐式 super(...) 调用可能不会按父类的意图初始化子类 .

    考虑一个初始化块的示例:

    class ParentWithInitializer {
        protected final String aFieldToInitialize;
    
        {
            aFieldToInitialize = "init";
            System.out.println("initializing in initializer block of: " 
                + this.getClass().getSimpleName());
        }
    }
    
    class ChildOfParentWithInitializer extends ParentWithInitializer{
        public static void main(String... args){
            System.out.println(new ChildOfParentWithInitializer().aFieldToInitialize);
        }
    }
    

    输出:
    initializing in initializer block of: ChildOfParentWithInitializer init

    • 无论子类实现什么构造函数,都将初始化该字段 .

    现在考虑构造函数的这个例子:

    class ParentWithConstructor {
        protected final String aFieldToInitialize;
    
        // different constructors initialize the value differently:
        ParentWithConstructor(){
            //init a null object
            aFieldToInitialize = null;
            System.out.println("Constructor of " 
                + this.getClass().getSimpleName() + " inits to null");
        }
    
        ParentWithConstructor(String... params) {
            //init all fields to intended values
            aFieldToInitialize = "intended init Value";
            System.out.println("initializing in parameterized constructor of:" 
                + this.getClass().getSimpleName());
        }
    }
    
    class ChildOfParentWithConstructor extends ParentWithConstructor{
        public static void main (String... args){
            System.out.println(new ChildOfParentWithConstructor().aFieldToInitialize);
        }
    }
    

    输出:
    Constructor of ChildOfParentWithConstructor inits to null null

    • 默认情况下,这会将字段初始化为 null ,即使它可能不是您想要的结果 .

  • 46

    正如你所提到的,它在很多情况下并没有用,而且对于任何较少使用的语法,你可能想要避免它只是为了阻止下一个看着你的代码的人花费30秒将其拉出保险库 .

    另一方面,它是做一些事情的唯一方法(我认为你几乎涵盖了那些) .

    静态变量本身应该在某种程度上避免 - 不总是如此,但如果你使用了很多,或者你在一个类中使用了很多,你可能会找到不同的方法,你的未来自己会感谢你 .

相关问题