初始化
作为经验法则,尝试在声明变量时对其进行初始化。
如果变量的值永远不会改变,请使用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
,但可以以线程安全的方式返回结果,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
是什么。两个线程同时处理它,我们还没有指定集合是否可以安全地并发使用。如果我们在线程内部初始化它,至少我们可以说最终state
将包含一个元素,但我们不能说它是 0 还是 1。