我最近为一个赋值编写了一个类,我必须在ArrayList中存储名称(在java中)。我将ArrayList初始化为实例变量private ArrayList<String> names
。后来当我根据解决方案检查我的工作时,我注意到他们已经用run()
方法初始化了他们的ArrayList。
我考虑过这一点,我觉得这可能是一个品味问题,但总的来说,在这样的情况下如何选择?一个人占用的内存是少吗?
PS我喜欢Ruby中以@符号开头的实例变量:它们更可爱。
(元问题:对于这个问题,什么是更好的标题?)
答案 0 :(得分:3)
用伟大的Knuth的话说“过早的优化是万恶之源”。
只是担心您的程序正常运行并且没有错误。这比稍后难以调试的模糊优化要重要得多。
但要回答你的问题 - 如果你在类成员中初始化,将在代码中第一次提到你的类时(即从你调用一个方法时)分配内存。如果在方法中初始化,则在调用此特定方法时,将在稍后进行内存分配。
所以这只是后来初始化的问题......这在业界被称为延迟初始化。
答案 1 :(得分:2)
根据经验,尝试在声明变量时初始化变量。
如果一个变量的值永远不会改变,那么使用final
关键字将其显式化。这有助于您推断代码的正确性,虽然我不知道识别final
关键字的编译器或JVM优化,但它们肯定是可能的。
当然,这条规则有例外。例如,可以在if-else或switch中分配变量。在这种情况下,“空白”声明(没有初始化的声明)比在读取虚拟值之前保证被覆盖的初始化更可取。
/* DON'T DO THIS! */
Color color = null;
switch(colorCode) {
case RED: color = new Color("crimson"); break;
case GREEN: color = new Color("lime"); break;
case BLUE: color = new Color("azure"); break;
}
color.fill(widget);
如果出现无法识别的颜色代码,现在您有NullPointerException
。最好不要分配无意义的null
。编译器会在color.fill()
调用时产生错误,因为它会检测到您可能没有初始化color
。
为了在这种情况下回答你的问题,我必须看到有问题的代码。如果解决方案在run()
方法中初始化它,它必须用作临时存储,或者作为“返回”任务结果的方式。
如果集合用作临时存储,并且不能在方法之外访问,则应将其声明为局部变量,而不是实例变量,并且很可能应该在方法中声明它的位置进行初始化。
对于一门初级编程课程,你的教师可能并没有试图用并发编程的复杂性来面对你 - 尽管如果是这样,我不确定你为什么使用Thread
。但是,根据CPU设计的当前趋势,任何正在学习编程的人都需要牢牢掌握并发性。我会尝试在这里深入研究一下。
从线程的run
方法返回结果有点棘手。这个方法是Runnable接口,没有什么能阻止多个线程执行单个实例的run
方法。由此产生的并发问题是Java 5中引入的Callable接口背后的动机的一部分。它非常像Runnable
,但可以以线程安全的方式返回结果,并抛出{{1}如果任务无法执行。
这是一个题外话,但如果你很好奇,请考虑以下示例:
Exception
在class Oops extends Thread { /* Note that thread implements "Runnable" */
private int counter = 0;
private Collection<Integer> state = ...;
public void run() {
state.add(counter);
counter++;
}
public static void main(String... argv) throws Exception {
Oops oops = new Oops();
oops.start();
Thread t2 = new Thread(oops); /* Now pass the same Runnable to a new Thread. */
t2.start(); /* Execute the "run" method of the same instance again. */
...
}
}
方法结束时,您几乎不知道main
的“状态”是什么。两个线程同时处理它,我们还没有指定集合是否可以安全地并发使用。如果我们在线程中初始化它,至少我们可以说最终,Collection
将包含一个元素,但我们不能说它是0还是1。
答案 2 :(得分:0)
来自wikibooks:
Java中有三种基本的变量范围:
在类的方法中声明的局部变量,对方法执行的时间有效(并仅占用存储)。每次调用该方法时,都会使用该变量的新副本。
实例变量,在类中声明但在任何方法之外。只要相应的对象在内存中,它就有效并占用存储空间;程序可以实例化该类的多个对象,并且每个对象都获得其自己的所有实例变量的副本。这是面向对象编程的基本数据结构规则;类被定义为保存给定系统中特定于“对象类”的数据,并且每个实例都拥有自己的数据。
静态变量,在类中声明为static,在任何方法之外。无论从该类实例化多少个对象,这个变量只有一个副本。
所以是的,内存消耗是一个问题,特别是如果run()内的ArrayList是本地的。
答案 3 :(得分:0)
我并不完全理解你的完整问题。
但就我现在所理解的而言,性能/内存的好处将相当轻微。因此,我绝对赞成易用性方面。
那么最适合你的是什么。仅在需要时解决性能/内存优化问题。
答案 4 :(得分:0)
我个人的经验法则变量是初始化它们,至少是默认值:
在退火时,即
private ArrayList<String> myStrings = new ArrayList<String>();
在构造函数
如果它确实是一个实例变量,并且表示对象的状态,那么它会在构造函数退出时完全初始化。否则,您可以尝试在变量具有值之前尝试访问该变量。当然,这不适用于您将自动获得默认值的基元。
对于静态(类级别)变量,在声明或静态初始化程序中初始化它们。如果我做计算或其他工作来获取值,我使用静态初始化程序。如果您只是调用new Foo()
或将变量设置为已知值,请在声明中初始化。
答案 5 :(得分:-1)
您必须避免延迟初始化。这会导致以后出现问题 但是如果你必须这样做,因为初始化太重了,你必须这样做:
静态字段:
// Lazy initialization holder class idiom for static fields
private static class FieldHolder {
static final FieldType field = computeFieldValue();
}
static FieldType getField() { return FieldHolder.field; }
实例字段:
// Double-check idiom for lazy initialization of instance fields
private volatile FieldType field;
FieldType getField() {
FieldType result = field;
if (result == null) { // First check (no locking)
synchronized(this) {
result = field;
if (result == null) // Second check (with locking)
field = result = computeFieldValue();
}
}
return result;
}
根据Joshua Bolch的书“Effective Java™”
第二版“(ISBN-13:978-0-321-35668-0):
“明智地使用延迟初始化”