Java에서 참조로 문자열 전달?


161

나는 다음을 수행하는 데 익숙하다 C.

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

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

그리고 출력은 다음과 같습니다

foo

그러나 Java에서는 작동하지 않는 것 같습니다. String객체는 reference에 의해 전달되는 대신 복사 되기 때문에 가정합니다 . Strings는 객체이며 항상 참조로 전달된다고 생각했습니다.

무슨 일이야?


2
참조로 전달 된 경우에도 (Java에서 전달되는 것은 참조 값의 사본이지만 다른 스레드 임) String 객체는 변경할 수 없으므로 작동하지 않습니다.
OscarRyz

8
C 코드가 아닙니다.
Alston

@ 필 : 이것은 C #에서도 작동하지 않습니다.
Mangesh

답변:


196

세 가지 옵션이 있습니다.

  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 클래스를 사용하거나 수동으로 동기화를 관리하십시오.
Boris Pavlović

12
@Boris Pavlovic-예, 그러나 주어진 시나리오에서 IMO가 없을 가능성이있는 다른 스레드에서 동일한 StringBuilder를 사용하지 않는 한 문제가되지 않습니다. 메소드가 다른 스레드에 의해 호출되어 다른 StringBuilder를 수신하는지는 중요하지 않습니다.
Ravi Wallau

세 번째 옵션 (배열)이 유일한 것이기 때문에 가장 좋습니다. 부울, int 등을 전달할 수도 있습니다.이 솔루션의 성능에 대해 좀 더 자세히 설명해 주시겠습니까? 첫 번째 솔루션이 성능 관점에서 더 낫다고 주장합니다.
meolic

@meolic StringBuilders += "..."매번 새로운 String 객체를 할당하지 않기 때문에 더 빠릅니다 . 실제로와 같은 Java 코드를 작성할 때 s = "Hello " + name컴파일러는 a를 작성하고 두 번 StringBuilder호출 하는 바이트 코드를 생성합니다 append().
Aaron Digulla

86

Java에서는 아무것도 참조로 전달되지 않습니다 . 모든 것이 value에 의해 전달됩니다 . 객체 참조는 값으로 전달됩니다. 또한 문자열은 변경할 수 없습니다 . 따라서 전달 된 문자열에 추가하면 새 문자열이 생깁니다. 반환 값을 사용하거나 대신 StringBuffer를 전달할 수 있습니다.


2
이것은 오해가 아닙니다. 그것은 개념입니다
Patrick Cornelissen

41
음, 참조로 객체를 전달한다는 것은 오해입니다. 값으로 참조를 전달합니다.
Ed S.

30
예, 오해입니다. 거대하고 광범위한 오해입니다. 그것은 내가 싫어하는 인터뷰 질문으로 이어진다 ( "Java는 어떻게 인수를 전달 하는가"). 면접관의 대략 절반이 실제로 잘못된 답을 원하고있는 것 ( "가치 기준, 기준 기준")을 싫어하기 때문에 나는 그것을 싫어한다. 정답은주는 데 시간이 더 걸리고 일부는 혼동되는 것 같습니다. CSMajor 유형의 스크리너가 대학에서 오해를 듣고 복음이라고 믿었 기 때문에 기술 화면을 틀렸다고 맹세합니다. 페.
CPerkins

4
@CPerkins 당신은 그것이 얼마나 화가 나는지 전혀 모른다. 그것은 내 피를 끓게 만든다.

6
모든 것이 모든 언어로 가치를 전달합니다. 이러한 값 중 일부는 주소 (참조)입니다.
gerardw

52

일어나고있는 것은 참조가 값으로 전달된다는 것입니다. 즉, 참조 의 사본 이 전달됩니다. java의 어떤 것도 참조로 전달되지 않으며 문자열은 변경할 수 없으므로 해당 지정은 참조 사본이 가리키는 새 문자열 객체를 만듭니다 . 원래 참조는 여전히 빈 문자열을 가리 킵니다.

이것은 어떤 객체에서도 동일합니다. 즉, 메소드에서 새로운 값으로 설정합니다. 아래 예제는 진행 상황을보다 명확하게하지만 문자열 연결은 실제로 동일합니다.

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

6
훌륭한 예입니다!
Nickolas


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를 썼습니다. 이것이 사실이라면 메소드를 호출하고 변수를 전달할 때 메소드가 변수를 다른 객체를 가리 키거나 참조하게 만들 수는 있지만 그렇게 할 수는 없습니다. 모든 것은 자바에서 가치에 의해 전달됩니다. 그리고 메소드 외부에서 객체의 속성을 변경하면 여전히 효과가 나타납니다.
barlop

여기에 대한 답변을 참조하십시오 stackoverflow.com/questions/40480/…
barlop

7

Java의 모든 인수는 값으로 전달됩니다. a 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:

문자열을 수정하려는 경우 추가 용도 로 포인터 간접 지정을 제공하는 언급 된 용도 StringBuilder나 일부 컨테이너 (배열 또는 AtomicReference사용자 지정 컨테이너 클래스)를 사용할 수 있습니다. 또는 새 값을 반환하고 할당하십시오.

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 항목 "Favor immutability"를 참조하십시오 .

그러나 언급했듯이, StringBuilder종종 루프 내부에서 할 일이 많으면을 사용하여 성능을 향상시킬 수 있습니다 StringBuilder.

그러나 Strings가능 StringBuilders하다면 변경 불가능한 것이 아니라 변경 불가능한 것을 전달 하십시오. 코드를보다 쉽게 ​​읽고 유지 관리 할 수 ​​있습니다. final메소드 매개 변수를 새 값으로 재 할당 할 때 경고하도록 매개 변수 작성 및 IDE 구성을 고려하십시오 .


6
마치 "비밀하게 말하기"는 거의 관련이없는 것처럼 말하지만 문제의 중심에 있습니다. Java가 실제로 참조로 전달 했다면 문제가되지 않습니다. 제발 IMO는이 (올바른) "참조가 값에 의해 전달된다"모델이 훨씬 더 도움이 될 것입니다 설명 - "자바 참조로 객체를 전달합니다"의 신화를 전파하지 않도록하려고합니다.
Jon Skeet

이봐, 내 이전 의견은 어디로 갔어? :-) 앞서 말했듯이, Jon이 추가 한 것처럼 여기서 실제 문제는 참조가 값으로 전달된다는 것입니다. 그것은 매우 중요하며 많은 사람들이 의미상의 차이를 이해하지 못합니다.
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");
}

또 다른 옵션은 다음과 같이 문자열을 범위 변수로 사용하지 않는 클래스를 만드는 것입니다.

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

맞아,하지만 내 관심을 끌려고 정확히 무엇을하려고합니까?
Fadi Hanna AL-Kass

언제 Java String이 원시적입니까?! 그래서! 참조로 전달 된 문자열
thedp

2
'불변의 객체'는 내가 말하려는 것입니다. 나는 어떤 이유로 @VWeber가 의견을 작성한 후 내 대답을 다시 읽지 않았지만 이제는 그가 말하려는 것을 알았습니다. 내 잘못이야. 답변 수정
Fadi Hanna AL-Kass

2

대답은 간단합니다. 자바에서 문자열은 변경할 수 없습니다. 따라서 'final'수정 자 (또는 C / C ++에서 'const')를 사용하는 것과 같습니다. 따라서 일단 할당되면 자신이 한 것처럼 변경할 수 없습니다.

당신은 변경할 수 있습니다 값에 문자열 점을, 그러나 당신은이 문자열이 현재 지적되는 실제 값을 변경할 수 없습니다.

즉. String s1 = "hey". 당신은 만들 수 있고 s1 = "woah", 그것은 괜찮습니다.하지만 실제로 문자열의 기본 값 (이 경우 "hey")을 plusEquals 등 (예 :)을 사용하여 할당 된 다른 것으로 변경할 수는 없습니다 s1 += " whatup != "hey whatup".

이를 위해서는 StringBuilder 또는 StringBuffer 클래스 또는 기타 변경 가능한 컨테이너를 사용한 다음 .toString ()을 호출하여 객체를 다시 문자열로 변환하십시오.

참고 : 문자열은 종종 해시 키로 사용되므로 불변 인 이유의 일부입니다.


변경 가능 또는 불변 여부는 관련이 없습니다. =참조에 대해서는 클래스에 관계없이 가리키는 객체에 영향을 미치지 않습니다.
newacct

2

문자열은 Java의 특수 클래스입니다. "Shell Safe"는 "String 인스턴스가 생성되면 String 인스턴스의 내용은 절대 변경되지 않습니다"를 의미합니다.

여기에 무슨 일이 일어나고 있는지

 zText += "foo";

먼저, Java 컴파일러는 zText String 인스턴스의 값을 가져온 다음 "foo"를 추가하는 zText 값을 갖는 새로운 String 인스턴스를 작성합니다. zText가 가리키는 인스턴스가 변경되지 않은 이유를 알 수 있습니다. 완전히 새로운 인스턴스입니다. 실제로 String "foo"조차도 새로운 String 인스턴스입니다. 따라서이 명령문에 대해 Java는 두 개의 String 인스턴스를 작성합니다. 하나는 "foo"이고 다른 하나는 zText append "foo"의 값입니다. 규칙은 간단합니다. String 인스턴스의 값은 절대 변경되지 않습니다.

fillString 메소드의 경우 StringBuffer를 매개 변수로 사용하거나 다음과 같이 변경할 수 있습니다.

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

...이 코드에 따라 'fillString'을 정의하려면 실제로 더 적절한 이름을 지정해야합니다!
Stephen C

예. 이 메소드의 이름은 "appendString"으로 지정해야합니다.
DeepNightTwo


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";

여기서 이전 참조는 "HelloHi"값을 가리키는 새 참조로 대체됩니다.

그러나 가변성을 가져 오기 위해 StringBuilder와 StringBuffer가 있습니다.

StringBuilder s = 새로운 StringBuilder (); s.append ( "Hi");

이것은 새로운 값 "Hi"를 동일한 참조에 추가합니다. //


0

Aaron Digulla는 지금까지 가장 좋은 답변을 받았습니다. 그의 두 번째 옵션의 변형은 commons lang 라이브러리 버전 3 이상의 래퍼 또는 컨테이너 클래스 MutableObject를 사용하는 것입니다.

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

컨테이너 클래스의 선언을 저장합니다. 단점은 commons lang lib에 대한 종속성입니다. 그러나 lib에는 많은 유용한 기능이 있으며 내가 작업 한 거의 모든 큰 프로젝트가 그것을 사용했습니다.


0

Java에서 참조로 오브젝트 (String 포함)를 전달하기 위해 주변 어댑터의 구성원으로 전달할 수 있습니다. 제네릭 솔루션은 다음과 같습니다.

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");
    }
}

이제 위의 코드를 실행하면 hashcodesString 변수 s로 주소가 어떻게 변경 되는지 확인할 수 있습니다 . changetos가 변경되면 함수의 변수 s에 새 객체가 할당됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.