在Java中使用初始化与构造函数
所以我最近一直在研究Java技术,并且发现了一些我以前不知道的function。 静态和实例初始化器是两种这样的技术。
我的问题是什么时候会使用初始化,而不是在构造函数中包含代码? 我想到了一些明显的可能性:
-
静态/实例初始化可以用来设置“最终”静态/实例variables的值,而构造函数不能
-
静态初始化器可以用来设置类中的任何静态variables的值,这应该比每个构造函数的开头有一个“if(someStaticVar == null)// do stuff”代码块更有效率
这两种情况都假设设置这些variables所需的代码比简单的“var = value”更复杂,否则似乎没有任何理由使用初始化程序,而不是在声明variables时简单地设置值。
然而,虽然这些并不是微不足道的收益(特别是设置最终variables的能力),但似乎应该使用初始化器的情况似乎有限。
人们当然可以使用一个初始化器来完成构造函数中的大量工作,但是我并不认为这样做的理由。 即使一个类的所有构造函数都共享了大量的代码,使用私有的initialize()函数似乎比使用初始值设定项更有意义,因为它不会locking您在编写新代码时运行该代码构造函数。
我错过了什么吗? 是否还有一些其他情况下应该使用初始化器? 或者,这真的只是一个相当有限的工具,用于非常具体的情况?
静态初始化器在提到cletus时是有用的,我也以相同的方式使用它们。 如果你有一个静态variables在被加载的时候被初始化,那么静态初始化是一个很好的select,尤其是它允许你做一个复杂的初始化,并且静态variables仍然是final
。 这是一个很大的胜利。
我发现“如果(someStaticVar == null)/ /做东西”是混乱和容易出错。 如果它被静态地初始化并且被声明为final
,那么你就避免了它为null
的可能性。
不过,当你说:
静态/实例初始化可以用来设置“最终”静态/实例variables的值,而构造函数不能
我假设你是这样说的:
- 静态初始化器可以用来设置“最终”静态variables的值,而构造函数则不能
- 实例初始化器可以用来设置“final”实例variables的值,而构造函数则不能
第一点你是正确的,第二点是错的。 例如,你可以这样做:
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; } }
匿名内部类不能有一个构造函数(因为它们是匿名的),所以它们非常适合实例初始化器。
我经常使用静态初始化块来设置最终的静态数据,特别是集合。 例如:
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") );
但是静态版本可以更加整洁,特别是当这些项目是不平凡的初始化。
一个天真的实现也可能不会创build一个不可修改的列表,这是一个潜在的错误。 上面创build了一个不可变的数据结构,您可以从公共方法中愉快地返回等等。
只是在这里添加一些已经很好的观点。 静态初始化器是线程安全的。 它在类被加载时执行,因此比使用构造函数更简单的静态数据初始化,在这种情况下,您需要一个同步块来检查静态数据是否被初始化,然后实际初始化它。
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")); } } } }
不要忘记,你现在必须在课堂同步,而不是实例级别。 当这个类被加载时,这会产生每个构造的代价,而不是一次性代价。 另外,这是丑陋的;-)
我阅读了整篇文章,寻找初始化程序与其构造函数的初始化顺序的答案。 我没有find它,所以我写了一些代码来检查我的理解。 我想我会添加这个小示范作为评论。 为了testing你的理解,看看你能否在底部阅读之前预测答案。
/** * 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
静态初始值设定项相当于静态上下文中的构造函数。 你一定会比实例初始化器更经常地看到它。 有时您需要运行代码来设置静态环境。
一般来说,一个实例initalizer是最好的匿名内部类。 看看JMock的食谱 ,看看一个创新的方法,使用它来使代码更具可读性。
有时,如果你有一些在构造函数中链接的逻辑比较复杂(比如你是inheritance的,并且你不能调用this(),因为你需要调用super()),你可以避免重复实例initalizer。 实例初始化是非常罕见的,但是,它们对于很多人来说是一个令人惊讶的语法,所以我避免了它们,并且宁愿让我的类具体而不是匿名,如果我需要构造函数的行为。
JMock是一个例外,因为这是如何使用框架的。
我还想补充一点以及所有上述的神话般的答案。 当我们使用Class.forName(“”)在JDBC中加载驱动程序时,会发生类加载,并且Driver类的静态初始化器被激发,并且其中的代码将Driver to Driver Manager注册。 这是静态代码块的重要用途之一。
正如你所提到的那样,在许多情况下它并没有什么用处,对于任何语法较差的用法,你可能都想要避免这种情况,只是为了阻止下一个人花30秒钟将代码从保险库中取出。
另一方面,这是做一些事情的唯一方法(我想你几乎涵盖了这些)。
静态variables本身应该有所回避 – 不总是,但是如果你使用了很多静态variables,或者你在一个类中使用了很多,你可能会发现不同的方法,你将来的自我会感谢你。