1

Java の非常に不可解な機能 (?) に出くわしました。

「new」キーワードを使用してメソッド引数を置き換えると、そのオブジェクトが別のスコープにシフトするようです。

import java.util.ArrayList;

public class Puzzle {
    public static void main(String[] args) {
        ArrayList<Integer> outer = new ArrayList<Integer>();
        outer.add(17);
        Puzzle.change(outer);
        outer.add(6);
        System.out.println(outer);

        // excpected output:
        // [23]
        // [23, 6]
        //
        // actual output:
        // [23]
        // [17, 7, 6]
    }

    public static void change(ArrayList<Integer> inner) {
        inner.add(7);
        inner = new ArrayList<Integer>();
        inner.add(23);
        System.out.println(inner);
    }
}

この奇妙さを説明できる人はいますか?代入でも同じような振る舞いに気づきました。

4

9 に答える 9

8

これは、Java 初心者にとって典型的なトリップアップです。問題の一部は、これを識別するための共通言語がないことです。パラメータは値で渡されますか? オブジェクトは参照渡しですか? 誰と話しているかによって、pass by value と pass by reference という言葉に対する反応が異なりますが、誰もが同じことを言いたがっています。

これを視覚化する方法は、Java 変数が 2 つのいずれかになることを理解することです。プリミティブまたはオブジェクトへの参照を含めることができます。その参照は、メモリ内の場所を指す数値と考えてください。これは、特定のメモリ位置を指していないという点で C++ の意味のポインタではなく、JVM がオブジェクトを見つけることができる特定のメモリ位置を検索できるようにするという点で、ハンドルのようなものです。しかし、次のように視覚化できます。

   ArrayList<Integer> outer = @1234; //The first reference where the ArrayList was created.

次に、次のパラメーターを使用して inner を呼び出します。

  Puzzle.change(@1234);

外部変数を渡すのではなく、@1234 の値を渡すことに注意してください。メソッドのパラメータとして、outer を変更することはできません。これが、値渡しと言う意味です。値は渡されますが、外部変数から切り離されています。

内側のパズル:

public static void change(ArrayList<Integer> inner) { // a new reference inner is created.
    //inner starts out as @1234
    inner.add(7);
    //now inner becomes @5678
    inner = new ArrayList<Integer>();
    //The object @5678 is changed.
    inner.add(23);
    //And printed.
    System.out.println(inner);
}

ただし、メソッドはそれを変更できなかったため、outer はまだ @1234 を指しています。outer 変数はなく、その内容があっただけです。ただし、変更メソッドは @1234 への参照で開始されたため、その場所にあるオブジェクトは実際にメソッドによって変更され、その結果は @1234 への他の参照に表示されます。

change メソッドが完了すると、オブジェクト @5678 は何も参照されないため、ガベージ コレクションの対象になります。

于 2009-09-24T14:53:12.883 に答える
6

これは、初心者向けの古典的な Java の質問の 1 つです。

内部パラメーターは値で渡されます (非プリミティブ オブジェクトの場合は参照です)。それに基づいて行動すると、外側のコードと同じオブジェクトに影響を与えるため、外側のメソッドに影響が見られます。

オブジェクトを新しいオブジェクトに置き換えると、同じではありません。新しいものに取り組むときは、以前のものを変更せず、外側のメソッドに影響を与えません。


更新: 多くのコメントの原因となった語彙の間違いで申し訳ありません。私は今私の答えを修正しました。その点は指摘されたと思いますが、今ではもっと明確になっています。

于 2009-09-24T14:08:01.573 に答える
6

Java では、すべての変数を実際のオブジェクトへのポインターと考えることができます。

何が起こるかは次のとおりです。

  1. リストを作成します。
  2. それに17を追加します。
  3. リストへの独自のポインターを持つ別の関数にリストを送信します。
  4. 最初のリストに 7 を追加します。
  5. 新しいリストを作成し、「内部」ポインターをそのリストに向けます。
  6. 新しいリストに 23 を追加します。
  7. 新しいリストを印刷して戻ります。
  8. 元の関数では、以前と同じオブジェクトを指しているポインターがまだあります。
  9. 最初のリストに 6 を追加します。
  10. 最初のリストを印刷します。
于 2009-09-24T14:13:26.183 に答える
2

参照はouter、メイン メソッドで作成された元の ArrayList を引き続き指しています。メソッドinner内で作成された新しい ArrayList を指すのは参照のみであり、この ArrayList は外部で参照されることはありません。changechange

これは予期される動作です。呼び出しスコープからアクセスするには、内側の ArrayList への参照を返す必要があります。

于 2009-09-24T14:08:14.677 に答える
1

あなたの「実際の出力」は完全に理にかなっています。change() メソッド内で inner を新しい値に再割り当てしたため、outer は影響を受けなくなりました。デバッガーで実行をトレースしてみると、何が起こっているのかがわかります。

于 2009-09-24T14:08:31.277 に答える
1

スコープをシフトしませんでした。新しい ArrayList をinnerパラメーターに割り当てただけです。これを防ぐパラメータを作っinnerてみてください。final

于 2009-09-24T14:08:49.260 に答える
1

私が理解している限り、これはパズルではなく、Java は値渡しのみをサポートしています。つまり、常に引数をコピーします。詳細はこちら

于 2009-09-24T14:08:59.870 に答える
1

メソッドをインライン化すると、次の同等のコードが得られます。

import java.util.ArrayList;

public class Puzzle {
    public static void main(String[] args) {
        ArrayList<Integer> outer = new ArrayList<Integer>();
        outer.add(17);

        ArrayList<Integer> inner = outer;
        inner.add(7);  // inner refers to same array list as outer

        inner = new ArrayList<Integer>();  // inner refers to new array list
        inner.add(23);
        System.out.println(inner);  // new list is printed

        outer.add(6);
        System.out.println(outer);  // outer list is printed
}

}

于 2009-09-24T14:37:30.670 に答える
0

「内部」は、「外部」が最初に参照する同じオブジェクトへの参照です (Java はメソッドのオブジェクト パラメータに参照渡しを使用するため、内部と外部の両方が同じオブジェクトを指します)。その後、メソッド内で「内部」を作成します。新しいオブジェクトを参照します。

Java で重要なことは、メイン メソッドで参照されている「アウター」が変更されないことです。メソッド呼び出しが完了すると、「inner」はスコープ内になくなり、最終的にガベージ コレクションが行われます。「outer」が指すオブジェクトはまだスコープ内にあります (outer がまだアクティブであるため)。

于 2009-09-24T14:08:54.740 に答える