Javaで参照により文字列を渡しますか?


161

私は次のことをすることに慣れていますC

void main() {
    String zText = "";
    fillString(zText);
    printf(zText);
}

void fillString(String zText) {
    zText += "foo";
}

そして出力は:

foo

ただし、Javaでは、これは機能しないようです。参照によって渡されるのStringはなく、オブジェクトがコピーされるためと思います。文字列は常に参照によって渡されるオブジェクトであると思いました。

ここで何が起こっているのですか?


2
それらが参照によって渡されたとしても(Javaで渡されるのは参照値のコピーですが、それは別のスレッドです)文字列オブジェクトは不変なので、とにかく機能しません
OscarRyz

8
Cコードではありません。
Alston、2014年

@Phil:これはC#でも機能しません。
Mangesh 16

回答:


196

次の3つのオプションがあります。

  1. StringBuilderを使用します。

    StringBuilder zText = new StringBuilder ();
    void fillString(StringBuilder zText) { zText.append ("foo"); }
  2. コンテナークラスを作成し、コンテナーのインスタンスをメソッドに渡します。

    public class Container { public String data; }
    void fillString(Container c) { c.data += "foo"; }
  3. 配列を作成します。

    new String[] zText = new String[1];
    zText[0] = "";
    
    void fillString(String[] zText) { zText[0] += "foo"; }

パフォーマンスの観点からは、通常、StringBuilderが最良のオプションです。


11
StringBuilderはスレッドセーフではないことに注意してください。マルチスレッド環境では、StringBufferクラスを使用するか、手動で同期を処理します。
BorisPavlović

12
@Boris Pavlovic-はい、ただし、同じStringBuilderが異なるスレッドで使用されていない限り、IMOが特定のシナリオで発生する可能性は低いので、問題にはなりません。メソッドが別のスレッドによって呼び出され、別のStringBuilderを受け取るかどうかは問題ではありません。
Ravi Wallau、

3番目のオプション(配列)が唯一の一般的なオプションであるため、私は最も好きです。boolean、intなどを渡すこともできます。このソリューションのパフォーマンスについてもう少し詳しく説明してもらえますか?最初の方がパフォーマンスの観点から優れていると主張します。
meolic

@meolic StringBuilders += "..."毎回新しいStringオブジェクトを割り当てないため、より高速です。実際、のようなJava s = "Hello " + nameコードを作成するStringBuilderと、コンパイラーはを作成してappend()2回呼び出すバイトコードを生成します。
アーロンディグラ

86

Javaでは何も参照渡しされません。すべてが値によって渡されます。オブジェクト参照は値で渡されます。さらに、文字列は不変です。したがって、渡された文字列に追加すると、新しい文字列を取得するだけです。戻り値を使用するか、代わりにStringBufferを渡すことができます。


2
これは概念であるという誤解ではありません
Patrick Cornelissen

41
うーん..いいえ、オブジェクトを参照渡しするのは誤解です。参照を値渡ししています。
Ed S.

30
はい、それは誤解です。それは巨大で広範囲にわたる誤解です。それは私が嫌うインタビューの質問につながります:(「Javaは引数を渡す方法」)。面接担当者のおよそ半分が実際には間違った答えを求めているように見えるため(「値によるプリミティブ、参照によるオブジェクト」)、私はそれを嫌っています。正しい答えを与えるには時間がかかり、それらのいくつかを混乱させるようです。CSMajorタイプのスクリーナーが大学での誤解を聞いていて、それを福音だと信じていたので、私はテックスクリーンをたたいたと思います。ふh。
CPerkins、2009

4
@CPerkins私がどれほど怒っているのか見当がつかない。それは私の血を沸騰させます。

6
すべてはすべての言語で値によって渡されます。それらの値のいくつかはたまたまアドレス(参照)です。
gerardw 2014年

52

起こっていることは、参照が値によって渡されることです。つまり、参照のコピーが渡されます。Javaでは何も参照によって渡されません。文字列は不変なので、その割り当てによって、参照のコピーが指す新しい文字列オブジェクトが作成されます。元の参照はまだ空の文字列を指しています。

これはどのオブジェクトでも同じです。つまり、メソッドで新しい値に設定します。以下の例は、何が起こっているかをより明確にするだけですが、文字列の連結は実際には同じことです。

void foo( object o )
{
    o = new Object( );  // original reference still points to old value on the heap
}

6
素晴らしい例!
Nickolas 2012

18

java.lang.Stringは不変です。

URLを貼り付けるのは嫌いですが、https://docs.oracle.com/javase/10/docs/api/java/lang/String.htmlは、Javaランドにいるかどうかを読んで理解するために不可欠です。


文字列が何であるかをSoren Johnsonが知らないのではないかと思います。それは問題ではなく、Javaの15年後にここで探していたものでもありませんでした。
AHH 2017

8

オブジェクトは参照渡しされ、プリミティブは値渡しされます。

文字列はプリミティブではなく、オブジェクトであり、オブジェクトの特殊なケースです。

これはメモリ節約のためです。JVMには、文字列プールがあります。作成されたすべての文字列について、JVMは同じ文字列が文字列プールに存在するかどうかを確認し、すでに存在する場合はそれをポイントしようとします。

public class TestString
{
    private static String a = "hello world";
    private static String b = "hello world";
    private static String c = "hello " + "world";
    private static String d = new String("hello world");

    private static Object o1 = new Object();
    private static Object o2 = new Object();

    public static void main(String[] args)
    {
        System.out.println("a==b:"+(a == b));
        System.out.println("a==c:"+(a == c));
        System.out.println("a==d:"+(a == d));
        System.out.println("a.equals(d):"+(a.equals(d)));
        System.out.println("o1==o2:"+(o1 == o2));

        passString(a);
        passString(d);
    }

    public static void passString(String s)
    {
        System.out.println("passString:"+(a == s));
    }
}

/ *出力* /

a==b:true
a==c:true
a==d:false
a.equals(d):true
o1==o2:false
passString:true
passString:false

==はメモリアドレス(参照)をチェックし、.equalsは内容(値)をチェックしています


3
完全に真実ではありません。Javaは常にVALUEで渡されます。トリックは、オブジェクトは値で渡される参照によって渡されるということです。(トリッキー、私は知っています)。これをチェックしてください:javadude.com/articles/passbyvalue.htm
adhg

1
@adhgあなたが書いた「値によって渡されるオブジェクトは参照によって渡される」<---それは意味不明です。オブジェクトには、値で渡される参照があることを意味します
barlop

2
-1「オブジェクトは参照渡し」<-FALSEと書いた。これが当てはまる場合、メソッドを呼び出して変数を渡すと、メソッドは変数に別のオブジェクトをポイント/参照させることができますが、そうすることはできません。すべてがJavaの値によって渡されます。また、メソッドの外でオブジェクトの属性を変更した場合の効果も確認できます。
barlop、2016年


7

Javaのすべての引数は値で渡されます。あなたが渡すとString関数に、渡された値がある Stringオブジェクトへの参照ができますが、その参照を変更することはできませんし、基本となるStringオブジェクトは不変です。

割り当て

zText += foo;

以下と同等です。

zText = new String(zText + "foo");

すなわち、それは(局所的に)パラメータを再割り当てzText新しいである新しいメモリ位置を指し、新たな基準としてStringの元の内容含まれるzText"foo"添付し。

元のオブジェクトは変更されず、main()メソッドのローカル変数はzText引き続き元の(空の)文字列を指します。

class StringFiller {
  static void fillString(String zText) {
    zText += "foo";
    System.out.println("Local value: " + zText);
  }

  public static void main(String[] args) {
    String zText = "";
    System.out.println("Original value: " + zText);
    fillString(zText);
    System.out.println("Final value: " + zText);
  }
}

プリント:

Original value:
Local value: foo
Final value:

文字列を変更したい場合は、前述のように使用するStringBuilderAtomicReference、ポインタの間接参照の追加レベルを提供するコンテナ(配列またはカスタムコンテナクラス)を使用できます。または、新しい値を返して割り当てるだけです。

class StringFiller2 {
  static String fillString(String zText) {
    zText += "foo";
    System.out.println("Local value: " + zText);
    return zText;
  }

  public static void main(String[] args) {
    String zText = "";
    System.out.println("Original value: " + zText);
    zText = fillString(zText);
    System.out.println("Final value: " + zText);
  }
}

プリント:

Original value:
Local value: foo
Final value: foo

これはおそらく一般的なケースで最もJavaに似たソリューションです- 効果的なJavaアイテムの「不変を好む」を参照してください。

ただし、前述のように、StringBuilder多くの場合、パフォーマンスが向上します。特にループ内に追加する処理が多い場合は、を使用しますStringBuilder

ただし、可能であればStrings、変更可能ではなく不変を渡すStringBuildersようにしてください。コードが読みやすくなり、保守しやすくなります。パラメータを作成しfinal、メソッドパラメータを新しい値に再割り当てするときに警告するようにIDEを構成することを検討してください。


6
あなたは「厳密に言えば」それがほとんど無関係であるかのように言う-それは問題の中心にある。Javaが実際に参照渡しを行っていたとしても、問題にはなりません。「参照によってJavaがオブジェクトを渡す」という神話を広めないようにしてください -(正しい)「参照は値によって渡される」モデルの説明は、IMOの方がはるかに役立ちます。
Jon Skeet、

ねえ、私の前のコメントはどこに行ったの?:-)前に言ったように、そしてジョンが追加したように、ここでの実際の問題は、参照が値によって渡されることです。それは非常に重要であり、多くの人々は意味の違いを理解していません。
Ed S.

1
けっこうだ。Javaプログラマーとして、参照によって渡されるものを10年以上実際に見たことがなかったので、私の言語はずさんになりました。しかし、そうです、私は特にCプログラミングの視聴者に対してもっと注意を払うべきでした。
デビッドモールズ

5

文字列はJavaの不変オブジェクトです。次のように、StringBuilderクラスを使用して、達成しようとしているジョブを実行できます。

public static void main(String[] args)
{
    StringBuilder sb = new StringBuilder("hello, world!");
    System.out.println(sb);
    foo(sb);
    System.out.println(sb);

}

public static void foo(StringBuilder str)
{
    str.delete(0, str.length());
    str.append("String has been modified");
}

別のオプションは、次のように、スコープ変数としてStringを使用してクラスを作成すること(非常に非推奨)です。

class MyString
{
    public String value;
}

public static void main(String[] args)
{
    MyString ms = new MyString();
    ms.value = "Hello, World!";

}

public static void foo(MyString str)
{
    str.value = "String has been modified";
}

1
文字列はJavaのプリミティブ型ではありません。
VWeber 2014

本当ですが、私の注意を何に向けようとしているのですか?
Fadi Hanna AL-Kass 14

Java Stringがプリミティブになるのはいつからですか。そう!参照によって渡される文字列。
thedp 2014

2
「不変オブジェクト」とは、私が言おうとしていたものです。なんらかの理由で、@ VWeberがコメントを書き込んだ後、私は自分の回答を読み直しませんでしたが、今、彼が言おうとしていることがわかりました。私の悪い。修正された回答
Fadi Hanna AL-Kass 14

2

答えは簡単です。Javaでは文字列は不変です。したがって、 'final'修飾子(またはC / C ++では 'const')を使用するようなものです。したがって、割り当て後は、以前のように変更することはできません。

あなたは変えることができる値に文字列のポイントをしていますが、この文字列が現在指してされている実際の値を変更することはできません。

すなわち。String s1 = "hey"。あなたは作ることができs1 = "woah"、それは完全に大丈夫ですが、plusEqualsなどを使用して割り当てられた後、文字列の基礎となる値(この場合は「hey」)を別のものに実際に変更することはできません(つまり、s1 += " whatup != "hey whatup")。

これを行うには、StringBuilderクラス、StringBufferクラス、またはその他の可変コンテナーを使用してから、.toString()を呼び出してオブジェクトを文字列に変換し直します。

注:文字列はハッシュキーとしてよく使用されるため、文字列が不変である理由の1つです。


変更可能か不変かは関係ありません。=参照では、クラスに関係なく、参照先のオブジェクトに影響を与えることはありません。
newacct 2014

2

StringはJavaの特別なクラスです。これはスレッドセーフであり、「Stringインスタンスが作成されると、Stringインスタンスの内容は変更されません」という意味です。

ここで何が起こっているのですか

 zText += "foo";

最初に、JavaコンパイラーはzText Stringインスタンスの値を取得してから、値が「foo」を付加したzTextである新しいStringインスタンスを作成します。これで、zTextが指すインスタンスが変更されない理由がわかります。それは完全に新しいインスタンスです。実際、String "foo"でさえ新しいStringインスタンスです。したがって、このステートメントでは、Javaは2つのStringインスタンスを作成します。1つは「foo」、もう1つはzTextの値「app」を追加したものです。ルールは単純です。Stringインスタンスの値は変更されません。

メソッドfillStringの場合、パラメータとしてStringBufferを使用するか、次のように変更できます。

String fillString(String zText) {
    return zText += "foo";
}

...ただし、このコードに従って 'fillString'を定義する場合は、より適切な名前を付ける必要あります。
スティーブンC

はい。このメソッドには、「appendString」などの名前を付ける必要があります。
DeepNightTwo 2009


1

これはStringBufferを使用します

public class test {
 public static void main(String[] args) {
 StringBuffer zText = new StringBuffer("");
    fillString(zText);
    System.out.println(zText.toString());
 }
  static void fillString(StringBuffer zText) {
    zText .append("foo");
}
}

StringBuilderをさらに使用する

public class test {
 public static void main(String[] args) {
 StringBuilder zText = new StringBuilder("");
    fillString(zText);
    System.out.println(zText.toString());
 }
  static void fillString(StringBuilder zText) {
    zText .append("foo");
}
}

1

文字列はJavaでは不変です。既存の文字列リテラル/オブジェクトを変更/変更することはできません。

文字列s = "Hello"; s = s + "hi";

ここで、以前の参照sは、値 "HelloHi"を指す新しい参照sに置き換えられます。

ただし、可変性を持たせるために、StringBuilderとStringBufferを用意しています。

StringBuilder s = new StringBuilder(); s.append( "こんにちは");

これにより、新しい参照 "Hi"が同じ参照に追加されます。//


0

これまでのところ、Aaron Digullaが最良の答えを持っています。彼の2番目のオプションのバリエーションは、commons langライブラリバージョン3+のラッパーまたはコンテナークラスMutableObjectを使用することです。

void fillString(MutableObject<String> c) { c.setValue(c.getValue() + "foo"); }

コンテナクラスの宣言を保存します。欠点は、commons lang libへの依存です。しかし、libには非常に多くの便利な機能があり、私が取り組んだほとんどすべての大規模なプロジェクトで使用されています。


0

Javaでオブジェクト(文字列を含む)を参照渡しする場合、周囲のアダプターのメンバーとして渡すことができます。ジェネリックのソリューションはここにあります:

import java.io.Serializable;

public class ByRef<T extends Object> implements Serializable
{
    private static final long serialVersionUID = 6310102145974374589L;

    T v;

    public ByRef(T v)
    {
        this.v = v;
    }

    public ByRef()
    {
        v = null;
    }

    public void set(T nv)
    {
        v = nv;
    }

    public T get()
    {
        return v;
    }

// ------------------------------------------------------------------

    static void fillString(ByRef<String> zText)
    {
        zText.set(zText.get() + "foo");
    }

    public static void main(String args[])
    {
        final ByRef<String> zText = new ByRef<String>(new String(""));
        fillString(zText);
        System.out.println(zText.get());
    }
}

0

もっと好奇心の強い人のために

class Testt {
    static void Display(String s , String varname){
        System.out.println(varname + " variable data = "+ s + " :: address hashmap =  " + s.hashCode());
    }

    static void changeto(String s , String t){
        System.out.println("entered function");
        Display(s , "s");
        s = t ;
        Display(s,"s");
        System.out.println("exiting function");
    }

    public static void main(String args[]){
        String s =  "hi" ;
        Display(s,"s");
        changeto(s,"bye");
        Display(s,"s");
    }
}

このコードを実行hashcodesすると、String変数sでアドレスがどのように変化するかを確認できます。changetosが変更されると、新しいオブジェクトが関数の変数sに割り当てられます

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.