1

我最近为一个作业写了一个类,我必须将名称存储在一个 ArrayList(在 java 中)中。我将 ArrayList 初始化为实例变量private ArrayList<String> names。后来当我根据解决方案检查我的工作时,我注意到他们已经在run()方法中初始化了他们的 ArrayList。

我想了一会儿,我觉得这可能是一个品味问题,但总的来说,在这种情况下,一个人如何选择?是占用更少的内存还是什么?

PS我喜欢Ruby中以@符号开头的实例变量:它们更可爱。

(元问题:这个问题的更好标题是什么?)

4

6 回答 6

3

用伟大的 Knuth 的话来说“过早的优化是万恶之源”。

只需担心您的程序正常运行并且没有错误。这比以后难以调试的晦涩优化要重要得多。

但是要回答您的问题-如果您在类成员中进行初始化,则将在第一次在代码中提到您的类时分配内存(即,当您从中调用方法时)。如果您在一个方法中进行初始化,则内存分配会在稍后调用此特定方法时发生。

所以只是后期初始化的问题……这在业界称为惰性初始化。

于 2008-11-15T10:06:00.693 回答
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,但可以以线程安全的方式返回结果,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。

于 2008-11-15T18:52:21.690 回答
0

来自维基书籍

Java 中变量的三种基本作用域:

  • 局部变量,在类的方法中声明,在方法执行期间有效(并且仅占用存储空间)。每次调用该方法时,都会使用一个新的变量副本。

  • 实例变量,在类中声明但在任何方法之外。只要对应的对象在内存中就有效并占用存储;一个程序可以实例化该类的多个对象,每个对象都有自己的所有实例变量的副本。这是面向对象编程的基本数据结构规则;类被定义为在给定系统中保存特定于“对象类”的数据,并且每个实例都保存自己的数据。

  • 静态变量,在类中声明为静态,在任何方法之外。无论从该类实例化多少对象,都只有一个这样的变量副本。

所以是的,内存消耗是一个问题,特别是如果 run() 中的 ArrayList 是本地的。

于 2008-11-15T10:07:39.847 回答
0

我不完全理解你的完整问题。

但据我现在了解,性能/内存优势将相当小。因此,我肯定会支持易用性方面。

所以做最适合你的事情。仅在需要时解决性能/内存优化问题。

于 2008-11-15T10:12:40.943 回答
0

对于实例变量,我个人的经验法则是初始化它们,至少使用默认值,或者:

  1. 在声明时间,即

    private ArrayList<String> myStrings = new ArrayList<String>();

  2. 在构造函数中

如果它确实是一个实例变量,并且表示对象的状态,那么它会在构造函数退出时完全初始化。否则,您可能会尝试在变量具有值之前对其进行访问。当然,这不适用于您将自动获得默认值的原语。

对于静态(类级)变量,在声明或静态初始化程序中初始化它们。如果我进行计算或其他工作来获取值,我会使用静态初始化程序。如果您只是调用new Foo()变量或将变量设置为已知值,请在声明中初始化。

于 2008-11-15T19:37:47.137 回答
-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™ Second Edition”(ISBN-13:978-0-321-35668-0):
“明智地使用惰性初始化”

于 2008-11-21T14:33:52.927 回答