1

最近、名前を ArrayList (Java) に格納する必要がある割り当て用のクラスを作成しました。ArrayList をインスタンス変数として初期化しましたprivate ArrayList<String> names。後でソリューションに対して自分の作業を確認したところ、run()代わりにメソッドで ArrayList が初期化されていることに気付きました。

少し考えてみたところ、好みの問題かもしれませんが、一般的には、このような状況でどのように選択するのでしょうか? メモリの消費量が少ないですか?

PS Ruby では @ 記号で始まるインスタンス変数が気に入っています。

(メタ質問: この質問のより適切なタイトルは何ですか?)

4

6 に答える 6

3

偉大なクヌースの言葉を借りれば、「時期尚早の最適化は諸悪の根源である」。

プログラムが正しく機能し、バグがないことだけを心配してください。これは、後でデバッグするのが難しいあいまいな最適化よりもはるかに重要です。

しかし、あなたの質問に答えるために-クラスメンバーで初期化すると、コードでクラスの最初の言及が行われたとき(つまり、そこからメソッドを呼び出したとき)にメモリが割り当てられます。メソッドで初期化すると、後でこの特定のメソッドを呼び出したときにメモリ割り当てが行われます。

したがって、後で初期化するだけの問題です...これは、業界では遅延初期化と呼ばれています。

于 2008-11-15T10:06:00.693 に答える
2

初期化

経験則として、変数を宣言するときに変数を初期化してみてください。

変数の値が変更されないように意図されている場合は、finalキーワードを使用して明示的にします。これは、コードの正確性を判断するのに役立ちます。キーワードを認識するコンパイラーまたは JVM の最適化については知りませんがfinal、それらは確かに可能です。

もちろん、この規則には例外があります。たとえば、変数は if–else またはスイッチで割り当てることができます。そのような場合は、ダミー値が読み込まれる前に上書きされることが保証されている初期化よりも、「空白」宣言 (初期化のないもの) を使用することをお勧めします。

/* 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。2 つのスレッドが同時に処理を行っていますが、コレクションを同時に使用しても安全かどうかは指定していません。スレッド内で初期化すると、少なくとも最終的にstateは 1 つの要素が含まれると言えますが、それが 0 か 1 かはわかりません。

于 2008-11-15T18:52:21.690 に答える
0

インスタンス変数に関する私の個人的な経験則は、少なくともデフォルト値を使用して、次のいずれかでインスタンス変数を初期化することです。

  1. 脱皮時、すなわち

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

  2. コンストラクターで

それが実際にはインスタンス変数であり、オブジェクトの状態を表すものである場合、コンストラクターが終了するまでに完全に初期化されます。そうしないと、値を取得する前に変数にアクセスしようとする可能性があります。もちろん、これはデフォルト値を自動的に取得するプリミティブには適用されません。

静的(クラスレベル)変数の場合は、宣言または静的初期化子で初期化します。値を取得するために計算やその他の作業を行う場合は、静的初期化子を使用します。new Foo()変数を呼び出しているか、既知の値に設定しているだけの場合は、宣言で初期化します。

于 2008-11-15T19:37:47.137 に答える
0

ウィキブックから:

Java の変数のスコープには、次の 3 つの基本的な種類があります。

  • クラスのメソッド内で宣言され、そのメソッドが実行されている間だけ有効な (そしてストレージを占有する) ローカル変数。メソッドが呼び出されるたびに、変数の新しいコピーが使用されます。

  • クラス内ではあるがメソッドの外側で宣言されるインスタンス変数。対応するオブジェクトがメモリ内にある限り有効であり、ストレージを占有します。プログラムはクラスの複数のオブジェクトをインスタンス化でき、それぞれがすべてのインスタンス変数の独自のコピーを取得します。これは、オブジェクト指向プログラミングの基本的なデータ構造規則です。クラスは、特定のシステムの「オブジェクトのクラス」に固有のデータを保持するように定義され、各インスタンスは独自のデータを保持します。

  • クラス内で static として宣言された static 変数。メソッド外。そのクラスからインスタンス化されたオブジェクトの数に関係なく、そのような変数のコピーは 1 つだけです。

そうです、特に run() 内の ArrayList がローカルの場合、メモリ消費が問題になります。

于 2008-11-15T10:07:39.847 に答える
0

私はあなたの問題を完全には理解していません。

しかし、私が今理解している限り、パフォーマンス/メモリの利点はかなり小さいです。したがって、私は間違いなく簡単な面を好みます。

ですから、あなたに最も適した方法を実行してください。必要な場合にのみ、パフォーマンス/メモリの最適化に対処してください。

于 2008-11-15T10:12:40.943 に答える
-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 book の「Effective Java™ Second Edition」(ISBN-13: 978-0-321-35668-0) による と、
「遅延初期化を慎重に使用する」

于 2008-11-21T14:33:52.927 に答える