Java가 "Pass-by-Reference"또는 "Pass-by-Value"입니까?


6577

나는 항상 Java가 pass-by-reference 라고 생각했다 .

그러나 나는 그것이 아니라고 주장하는 두 개의 블로그 게시물 (예 : 이 블로그 )을 보았습니다 .

나는 그들이 만들고있는 차이점을 이해하지 못한다고 생각합니다.

설명은 무엇입니까?


705
나는이 문제에 대한 많은 혼란이 다른 사람들이 "참조"라는 용어에 대해 다른 정의를 가지고 있다는 사실과 관련이 있다고 생각합니다. C ++ 배경에서 온 사람들은 "참조"가 C ++에서 의미하는 것을 의미한다고 가정하고, C 배경에서 온 사람들은 "참조"가 해당 언어의 "포인터"와 동일해야한다고 가정합니다. Java가 참조로 전달한다고 말하는 것이 옳은지는 실제로 "참조"의 의미에 달려 있습니다.
중력

119
평가 전략 기사 에서 찾은 용어를 일관되게 사용하려고합니다 . 조건 밖으로 문서 포인트가 지역 사회에 의해 크게 변화에도 불구하고, 주목해야한다, 그것 의 의미 있음을 강조 call-by-value하고 call-by-reference매우 중요한 방법으로 다릅니다 . (필자는 개인적으로 사용하는 것을 선호 call-by-object-sharing이상 이러한 일 call-by-value[-of-the-reference]이 높은 수준의 의미를 설명하고와 충돌 생성하지 않는 한 call-by-value, 이다 기본이되는 구현을.)

59
@Gravity : 당신은 가서 거대한 게시판이나 다른 것에 댓글을 달 수 있습니까? 간단히 말해 전체 문제입니다. 그리고 그것은이 모든 것이 의미 론적임을 보여줍니다. 참조의 기본 정의에 동의하지 않으면이 질문에 대한 답변에 동의하지 않습니다 :)
MadConan

29
혼란은 "참조로 통과"와 "참조 의미론"이라고 생각합니다. Java는 참조 시맨틱으로 가치를 전달합니다.
spraff

11
@Gravity, C ++에서 온 사람들은 본능적으로 "참조"라는 용어에 대해 다른 직감을 설정한다는 점에서 절대적으로 정확 하지만 , 나는 몸이 "by"에 더 묻혀 있다고 개인적으로 믿는다. "Pass by"는 Java의 "Passing a"와는 완전히 다르다는 점에서 혼동됩니다. 그러나 C ++에서는 구어체로 그렇지 않습니다. C ++에서는 "참조 전달"이라고 말할 수 있으며 swap(x,y)테스트 를 통과하는 것으로 이해됩니다 .

답변:


5838

자바는 항상 가치가있다 . 불행히도, 우리는 객체의 값을 전달할 때 그 참조 를 전달 합니다. 초보자에게는 혼동 스럽습니다.

다음과 같이 진행됩니다.

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    Dog oldDog = aDog;

    // we pass the object to foo
    foo(aDog);
    // aDog variable is still pointing to the "Max" dog when foo(...) returns
    aDog.getName().equals("Max"); // true
    aDog.getName().equals("Fifi"); // false
    aDog == oldDog; // true
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // change d inside of foo() to point to a new Dog instance "Fifi"
    d = new Dog("Fifi");
    d.getName().equals("Fifi"); // true
}

위의 예에서는 aDog.getName()여전히을 반환 "Max"합니다. 값 aDog내에서 main기능이 변경되어 있지 fooDog "Fifi"객체 참조는 값으로 전달 될 때. 참조로 전달 된 경우 aDog.getName()in main은에 "Fifi"대한 호출 후 반환 됩니다 foo.

마찬가지로:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    Dog oldDog = aDog;

    foo(aDog);
    // when foo(...) returns, the name of the dog has been changed to "Fifi"
    aDog.getName().equals("Fifi"); // true
    // but it is still the same dog:
    aDog == oldDog; // true
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // this changes the name of d to be "Fifi"
    d.setName("Fifi");
}

위의 예제 에서 객체 이름이 내부에 설정 되었기 때문에 Fifi호출 후 개의 foo(aDog)이름입니다 foo(...). foo수행하는 모든 작업 d은 모든 실제적인 목적으로 수행 aDog되나 , 변수 자체 의 값을 변경할 수 는 없습니다aDog .


263
내부 세부 사항과 문제를 약간 혼동하지 않습니까? '객체에 대한 내부 포인터의 값'을 의미한다고 가정하면 '참조 전달'과 '참조 값 전달'사이에는 개념적 차이가 없습니다.
izb

383
그러나 미묘한 차이가 있습니다. 첫 번째 예를보십시오. 순전히 참조로 전달 된 경우 aDog.name은 "Fifi"입니다. 그것은 아닙니다-당신이 얻는 참조는 함수를 종료 할 때 덮어 쓴 경우 복원되는 값 참조입니다.
erlando

300
@Lorenzo : 아니요, Java에서는 모든 것이 가치에 의해 전달됩니다. 프리미티브는 값으로 전달되고 객체 참조는 값으로 전달됩니다. 객체 자체는 결코 메소드에 전달되지 않지만 객체는 항상 힙에 있으며 객체에 대한 참조 만 메소드에 전달됩니다.
Esko Luontola

294
객체 전달을 시각화하는 좋은 방법을 시도합니다. 풍선을 상상해보십시오. fxn을 호출하는 것은 풍선에 두 번째 줄을 묶고 fxn에 줄을 전달하는 것과 같습니다. parameter = new Balloon ()은 해당 문자열을 자르고 새 풍선을 만듭니다 (그러나 이것은 원래 풍선에는 영향을 미치지 않습니다). parameter.pop ()은 문자열을 따라 동일한 원래 풍선으로 이어지기 때문에 여전히 팝됩니다. Java는 값으로 전달되지만 전달 된 값은 깊지 않으며 최상위 레벨, 즉 기본 또는 포인터입니다. 객체가 완전히 복제되어 전달되는 값을 전달하는 깊은 값과 혼동하지 마십시오.
dhackner

150
혼란스러운 것은 객체 참조가 실제로 포인터라는 것입니다. 처음에 SUN은 포인터라고 불렀습니다. 그런 다음 마케팅은 "포인터"가 나쁜 단어라는 것을 알았습니다. 그러나 여전히 NullPointerException에서 "올바른"명명법을 볼 수 있습니다.
Falken 교수 계약은

3035

난 당신이 내 기사 를 참조한 것으로 나타났습니다 .

자바 스펙은 자바의 모든 것이 가치에 의한 것이라고 말한다. Java에는 "pass-by-reference"와 같은 것은 없습니다.

이것을 이해하는 열쇠는

Dog myDog;

입니다 하지 개가; 실제로 는 개를 가리키는 포인터 입니다.

그것이 의미하는 것은

Dog myDog = new Dog("Rover");
foo(myDog);

본질적으로 생성 된 객체 의 주소 를 메소드에 전달합니다 .Dogfoo

(필자는 Java 포인터가 직접적인 주소가 아니기 때문에 본질적으로 말하지만 그렇게 생각하는 것이 가장 쉽습니다)

Dog객체가 메모리 주소 42에 상주 한다고 가정합니다. 이는 우리가 메소드에 42를 전달한다는 의미입니다.

방법이 다음과 같이 정의 된 경우

public void foo(Dog someDog) {
    someDog.setName("Max");     // AAA
    someDog = new Dog("Fifi");  // BBB
    someDog.setName("Rowlf");   // CCC
}

무슨 일이 일어나고 있는지 보자.

  • 매개 변수 someDog는 42 값으로 설정됩니다
  • "AAA"라인에서
    • someDog받는 이어진다 Dog에 그 지점합니다 ( Dog주소 오브젝트 42)
    • 있음 Dog(주소 (42)의 하나) 최대에 자신의 이름을 변경하도록 요청
  • "BBB"줄에서
    • 새로운 Dog것이 만들어집니다. 그가 주소 74에 있다고 가정 해 봅시다.
    • 우리는 someDog74에 매개 변수 를 할당
  • "CCC"라인에서
    • someDog가 Dog가리키는 곳 ( Dog주소 74 의 객체)
    • 있음 Dog(주소 (74)의 하나) Rowlf에 자신의 이름을 변경하도록 요청
  • 우리는 돌아온다

이제 메소드 외부에서 일어나는 일에 대해 생각해 봅시다.

myDog변경 되었습니까 ?

열쇠가 있습니다.

그것이 실제 myDog가 아니라 포인터 라는 것을 명심 Dog하고 대답은 NO입니다. myDog여전히 값 42를가집니다. 여전히 원본을 가리키고 있습니다. Dog그러나 "AAA"줄 때문에 이름이 "Max"입니다. 여전히 같은 Dog; myDog의 값은 변경되지 않았습니다.

주소 를 따르고 끝에있는 것을 변경하는 것은 완벽하게 유효 합니다. 그러나 변수를 변경하지는 않습니다.

Java는 C와 똑같이 작동합니다. 포인터를 지정하고 포인터를 메소드에 전달하고 메소드의 포인터를 따라 지시 된 데이터를 변경할 수 있습니다. 그러나 해당 포인터가 가리키는 위치는 변경할 수 없습니다.

C ++, Ada, Pascal 및 참조 별 전달을 지원하는 기타 언어에서는 전달 된 변수를 실제로 변경할 수 있습니다.

Java가 참조 별 전달 시맨틱을 가지고 있다면, foo위에서 정의한 메소드가 BBB 라인에서 지정할 myDog때 가리키는 위치 가 변경 되었을 것 someDog입니다.

참조 매개 변수는 전달 된 변수의 별명으로 생각하십시오. 해당 별명이 지정되면 전달 된 변수도 지정됩니다.


164
이것이 바로 "자바에는 포인터가 없다"라는 일반적인 자제가 그렇게 오도하는 이유입니다.
Beska

134
당신은 틀 렸습니다. "myDog는 실제 Dog가 아니라 포인터라는 점을 명심하십시오. 대답은 NO입니다. myDog는 여전히 42라는 값을가집니다. 여전히 원래 Dog를 가리키고 있습니다." myDog의 값은 42이지만 이름 인수에는 // AAA 줄의 "Rover"가 아니라 "Max"가 포함됩니다.
Özgür

180
이런 식으로 생각하십시오. 누군가 "annArborLocation"이라는 종이에 MI의 Ann Arbor (내 고향, GO BLUE!) 주소가 있습니다. "myDestination"이라는 종이에 복사하십시오. "myDestination"으로 운전하여 나무를 심을 수 있습니다. 해당 위치에서 도시에 대한 내용을 변경했을 수 있지만 두 용지에 작성된 LAT / LON은 변경되지 않습니다. "myDestination"에서 LAT / LON을 변경할 수 있지만 "annArborLocation"은 변경하지 않습니다. 도움이 되나요?
Scott Stanchfield

43
@Scott Stanchfield : 약 1 년 전에 기사를 읽었으며 실제로 도움이되었습니다. 감사! 나는 약간의 추가를 겸손히 제안 할 수 있습니다. 실제로 CLU 언어의 평가 전략을 설명하기 위해 Barbara Liskov가 발명 한 "값별 호출"이라는이 형식을 설명하는 특정 용어가 있다고 언급해야합니다. 1974 년, 기사가 다루는 것과 같은 혼동을 피하기 위해 : 공유에 의한 호출 (때때로 객체 공유에 의한 호출 또는 단순히 객체에 의한 호출 )은 의미를 거의 완벽하게 설명합니다.
Jörg W Mittag

29
@Gevorg-C / C ++ 언어는 "포인터"의 개념을 소유하지 않습니다. 포인터를 사용하지만 C / C ++에서 허용하는 것과 동일한 유형의 포인터 조작을 허용하지 않는 다른 언어가 있습니다. 자바에는 포인터가있다. 그들은 단지 장난으로부터 보호됩니다.
Scott Stanchfield

1740

Java는 항상 참조가 아닌 value로 인수 전달 합니다.


예를 통해 이것을 설명하겠습니다 .

public class Main {

     public static void main(String[] args) {
          Foo f = new Foo("f");
          changeReference(f); // It won't change the reference!
          modifyReference(f); // It will modify the object that the reference variable "f" refers to!
     }

     public static void changeReference(Foo a) {
          Foo b = new Foo("b");
          a = b;
     }

     public static void modifyReference(Foo c) {
          c.setAttribute("c");
     }

}

나는 이것을 단계적으로 설명 할 것이다 :

  1. ftype 이라는 이름 의 참조를 선언하고 속성 Foo이있는 새로운 유형의 객체를 할당 Foo합니다 "f".

    Foo f = new Foo("f");

    여기에 이미지 설명을 입력하십시오

  2. 메소드 측에서 Foo이름을 가진 유형의 참조 a가 선언되고 처음에 할당됩니다 null.

    public static void changeReference(Foo a)

    여기에 이미지 설명을 입력하십시오

  3. 메소드를 호출하면 changeReference참조 a에 인수로 전달 된 객체가 지정됩니다.

    changeReference(f);

    여기에 이미지 설명을 입력하십시오

  4. btype 이라는 이름 의 참조를 선언하고 속성 Foo이있는 새로운 유형의 객체를 할당 Foo합니다 "b".

    Foo b = new Foo("b");

    여기에 이미지 설명을 입력하십시오

  5. a = b속성이있는 객체 의 참조 a아닌 참조에 새로운 할당을합니다 .f"b"

    여기에 이미지 설명을 입력하십시오

  6. modifyReference(Foo c)method 를 호출 하면 참조 c가 생성되고 attribute를 가진 객체가 할당됩니다 "f".

    여기에 이미지 설명을 입력하십시오

  7. c.setAttribute("c");참조하는 객체의 속성을 변경하고이를 참조하는 객체 c와 동일합니다 f.

    여기에 이미지 설명을 입력하십시오

Java에서 인수로 객체를 전달하는 방법을 이해하기를 바랍니다. :)


82
+1 좋은 물건. 좋은 다이어그램. 나는 또한 간결한 페이지를 여기에서 찾았 습니다. 너의 요구).
DaveM

5
@ Eng.Fouad 좋은 설명이지만 a, 같은 객체를 가리키는 f(그리고 그 객체의 복사본을 절대로 얻지 않는 경우 f)를 사용하여 만든 객체의 변경 사항도 동일하게 a수정해야 f합니다. ), 어떤 시점 에서 객체 점 a의 자체 사본f가져와야합니다.
0x6C38

14
@MrD 'a'가 동일한 객체 'f'를 가리킬 때 'a'를 통해 해당 객체에 대한 모든 변경 사항도 'f'를 통해 관찰 할 수 있지만 'f'는 변경되지 않았습니다. 'f'는 여전히 동일한 객체를 가리 킵니다. 객체를 완전히 바꿀 수는 있지만 'f'가 가리키는 것을 바꿀 수는 없습니다. 어떤 이유로 어떤 사람들은 이해할 수없는 근본적인 문제입니다.
Mike Braun

트윗 담아 가기 지금 당신은 나를 혼란스럽게했습니다 : S. 방금 쓴 내용이 6.와 7.의 내용과 반대되는 것이 아닙니까?
사악한 세탁기

6
이것이 내가 찾은 최고의 설명입니다. 그런데 기본 상황은 어떻습니까? 예를 들어, 인수에는 int 유형이 필요합니다. 여전히 int 변수의 사본을 인수에 전달합니까?
allenwang

732

이것은 자바가 실제로 어떻게 작동하는지에 대한 통찰력을 줄 것입니다. Java가 다음 참조에서 참조로 전달하거나 값으로 전달하는 것에 대해서는 웃을 것입니다 :-)

1 단계 : 특히 다른 프로그래밍 언어에서 온 경우 'p' "_ _ _ _ _ _ _"로 시작하는 단어를 기억하십시오. Java와 'p'는 같은 책, 포럼 또는 txt로 작성할 수 없습니다.

2 단계는 Object를 메소드에 전달할 때 Object 자체가 아니라 Object 참조를 전달한다는 것을 기억하십시오.

  • 학생 : 스승님, Java가 참조로 전달한다는 의미입니까?
  • 마스터 : 메뚜기

이제 객체의 참조 / 변수가 무엇을 / 그 것인지 생각하십시오 :

  1. 변수는 JVM에서 메모리의 참조 된 오브젝트 (힙)에 도달하는 방법을 알려주는 비트를 보유합니다.
  2. 메소드에 인수를 전달할 때 참조 변수를 전달하지 않고 참조 변수의 비트 사본을 전달합니다 . 이 같은 것 : 3bad086a. 3bad086a는 전달 된 객체에 도달하는 방법을 나타냅니다.
  3. 따라서 3bad086a를 전달하여 참조 값입니다.
  4. 참조 자체가 아니라 참조 자체의 값이 아닌 참조 값을 전달합니다.
  5. 이 값은 실제로 복사되어 메소드에 제공 됩니다.

다음에서 (컴파일 / 실행하려고하지 마십시오 ...) :

1. Person person;
2. person = new Person("Tom");
3. changeName(person);
4.
5. //I didn't use Person person below as an argument to be nice
6. static void changeName(Person anotherReferenceToTheSamePersonObject) {
7.     anotherReferenceToTheSamePersonObject.setName("Jerry");
8. }

무슨 일이야?

  • 변수 person 은 1 번 줄에 만들어지며 처음에는 null입니다.
  • 새로운 Person Object가 2 번 줄에 생성되어 메모리에 저장되고 변수 person 에 Person 객체에 대한 참조가 제공됩니다. 즉, 주소입니다. 3bad086a를 가정 해 봅시다.
  • 객체의 주소를 보유한 변수 사람 은 3 번 라인의 함수로 전달됩니다.
  • 4 번 라인에서 침묵의 소리를들을 수 있습니다
  • 5 번 라인의 코멘트 확인
  • 메소드 local 변수 인 anotherReferenceToTheSamePersonObject 가 작성되어 6 번째 줄에 마법이 나타납니다.
    • 변수 / 레퍼런스 개인 은 비트 단위로 복사 되어 함수 내의 anotherReferenceToTheSamePersonObject에 전달됩니다 .
    • Person의 새 인스턴스가 작성되지 않습니다.
    • " person "과 " anotherReferenceToTheSamePersonObject "는 모두 같은 값 3bad086a를가집니다.
    • 이것을 시도하지 말고 person == anotherReferenceToTheSamePersonObject는 true입니다.
    • 두 변수 모두 참조의 동일 사본을 가지며 동일한 개인 오브젝트, 힙의 동일한 오브젝트 및 사본이 아닙니다.

그림은 천 단어의 가치가 있습니다.

값으로 전달

anotherReferenceToTheSamePersonObject 화살표는 변수 사람이 아닌 오브젝트를 향합니다!

당신이 그것을 얻지 못하면 저를 믿어 Java가 가치에 의해 전달 된다고 말하는 것이 낫다는 것을 기억하십시오 . 음, 참조 값으로 전달하십시오 . 글쎄, 훨씬 더 나은 값의 복사 값 이 더 좋습니다 ! ;)

이제 나를 미워하지만이 방법을 사용하면 메소드 인수에 대해 이야기 할 때 기본 데이터 유형과 객체를 전달하는 것에는 차이가 없습니다 .

항상 참조 값의 비트 사본을 전달합니다!

  • 기본 데이터 유형 인 경우이 비트에는 기본 데이터 유형 자체의 값이 포함됩니다.
  • 그것이 Object 인 경우 비트는 JVM에게 Object에 도달하는 방법을 알려주는 주소 값을 포함합니다.

메소드 내에서 참조 된 객체를 원하는만큼 수정할 수 있지만 아무리 노력해도 참조를 계속 전달하는 전달 된 변수를 수정할 수 없기 때문에 Java는 가치에 의해 전달됩니다 (p _ _ _ 아님) _ _ _ _) 무엇이든 상관없이 동일한 객체!


위의 changeName 함수는 전달 된 참조의 실제 내용 (비트 값)을 수정할 수 없습니다. 즉, changeName은 Person 개인이 다른 오브젝트를 참조하도록 만들 수 없습니다.


물론 짧게 자르고 Java가 가치에 의한 것이라고 말할 수 있습니다 !


5
당신의 평균 포인터를합니까? .. 내가 제대로 얻을에있는 경우 public void foo(Car car){ ... }, car로컬이며 foo그것은 개체의 힙의 위치를 포함? 따라서 car값을 변경 car = new Car()하면 힙에서 다른 객체를 가리 킵니까? 로 car속성 값을 변경하면 car.Color = "Red"지정된 힙의 객체 car가 수정됩니다. 또한 C #에서도 동일합니까? 답장 해주세요! 감사!
dpp

11
@domanokz 당신이 날 죽이고 있어요, 그 말을 다시 말하지 마세요! ;) '참조'라고 말하지 않고이 질문에 대답 할 수 있음에 유의하십시오. 그것은 용어 문제이며 'p는 그것을 악화시킵니다. Me와 Scot는 불행히도 이것에 대해 다른 견해를 가지고 있습니다. Java에서 어떻게 작동하는지 알았으므로 이제 값, 객체 공유, 변수 값별 복사를 전달하거나 다른 것을 생각해 낼 수 있습니다! 작동 방식과 Object 유형의 변수에있는 한 PO Box 주소 만 있으면 실제로 신경 쓰지 않습니다! ;)
Marsellus Wallace 2012

9
방금 참조를 전달한 것 같 습니까? Java는 여전히 복사 된 참조 기준 언어라는 사실을 강조합니다. 복사 된 참조라는 용어는 용어를 변경하지 않습니다. 두 개의 참조는 여전히 동일한 객체를 가리 킵니다. 이것은 순수 주의자의 주장이다.
John Strickler

3
이론적 인 9 번 줄에 들어가면 System.out.println(person.getName());무엇이 나타날까요? "톰"또는 "제리"? 이것이 혼란을 극복하는 데 도움이되는 마지막 것입니다.
TheBrenny

1
" 참조 의 가치 "는 마침내 저에게 그것을 설명했습니다.
Alexander Shubert

689

자바는 항상 예외없이, 값에 의해 전달되는 지금 .

그렇다면 누구나 이것으로 혼란 스러울 수 있고 Java가 참조로 전달된다고 생각하거나 참조로 전달하는 Java의 예가 있다고 생각합니까? 요점은 Java 가 어떤 상황에서도 객체 자체 의 값에 직접 액세스 할 수 없다는 것 입니다. 객체에 대한 유일한 액세스는 해당 객체에 대한 참조 를 통해서 입니다. Java 객체는 직접적이 아니라 항상 참조를 통해 액세스 되므로 필드와 변수 및 메소드 인수 에 대해 일반적 으로 객체대한 참조 일 때만 객체 인 것으로 이야기하는 것이 일반적 입니다.혼동은 명명법의 (엄격하게 말해서, 부정확 한) 변화에서 기인합니다.

따라서 메소드를 호출 할 때

  • 기본 인수 ( int, long등)의 경우 전달 기준 값은 기본 의 실제 값 입니다 (예 : 3).
  • 객체의 경우 전달 기준 값은 객체대한 참조 값입니다 .

당신이 doSomething(foo)있고 public void doSomething(Foo foo) { .. }두 Foos가 동일한 객체를 가리키는 참조 를 복사했다면 .

당연히, 값을 기준으로 객체에 대한 참조를 전달하는 것은 객체를 참조로 전달하는 것과 매우 유사합니다 (실제로는 구분할 수 없습니다).


7
프리미티브의 값은 String과 같이 변경할 수 없으므로 두 경우의 차이는 실제로 관련이 없습니다.
Paŭlo Ebermann

4
바로 그거죠. 관찰 가능한 JVM 동작을 통해 알 수있는 모든 것은 프리미티브가 참조로 전달되고 힙에있을 수 있습니다. 그들은 그렇지 않지만 실제로는 어떤 식으로도 관찰 할 수 없습니다.
중력

6
프리미티브는 불변인가? Java 7의 새로운 기능입니까?
user85421

4
포인터는 불변이고, 프리미티브는 일반적으로 변할 수 있습니다. 문자열은 또한 기본 요소가 아니며 객체입니다. 또한 String의 기본 구조는 변경 가능한 배열입니다. 그것에 관한 유일한 불변의 것은 길이입니다. 그것은 배열의 본질적인 특성입니다.
kingfrito_5005

3
이것은 논쟁의 의미 론적 특성을 지적하는 또 다른 대답입니다. 이 답변에 제공된 참조 정의는 Java를 "참조 별 전달"로 만듭니다. 저자는 본질적으로 "참조로 통과 (pass-by-by-pass)"에서 "실제로 예측할 수 없다"고 말함으로써 마지막 단락에서 많은 것을 인정한다. Java 구현을 이해하고 Java를 올바르게 사용하는 방법을 이해하려는 욕구 때문에 OP가 묻는 것이 의심됩니다. 실제로 구별 할 수 없다면 돌보는 데 아무런 요점이 없으며 그것에 대해 생각하는 것조차 시간 낭비가 될 것입니다.
Loduwijk

331

Java는 값으로 참조를 전달합니다.

따라서 전달되는 참조를 변경할 수 없습니다.


27
그러나 "인수로 전달 된 객체의 값을 변경할 수 없습니다"라는 말이 계속 반복되는 것은 분명히 잘못된 것입니다. 다른 객체를 참조하도록 할 수는 없지만 메소드를 호출하여 내용을 변경할 수 있습니다. IMO는 참조의 모든 이점을 잃고 추가 보장을 얻지 못함을 의미합니다.
Timmmm

34
"인수로 전달 된 객체의 값을 변경할 수 없습니다"라고 말한 적이 없습니다. Java 언어에 대한 진정한 설명 인 "메소드 인수로 전달 된 객체 참조의 값을 변경할 수 없습니다"라고 말할 것입니다. 분명히 객체의 상태를 변경할 수 있습니다 (불변하지 않는 한).
ScArcher2

20
실제로는 자바에서 객체를 전달할 수는 없습니다. 객체는 힙에 유지됩니다. 객체에 대한 포인터 를 전달할 수 있습니다 (호출 된 메소드의 스택 프레임에 복사 됨). 따라서 전달 된 값 (포인터)을 절대로 변경하지 않지만 자유롭게 따라갈 수 있으며 힙이 가리키는 것을 변경할 수 있습니다. 그것은 가치에 의한 것입니다.
Scott Stanchfield

10
방금 참조를 전달한 것 같 습니까? Java는 여전히 복사 된 참조 기준 언어라는 사실을 강조합니다. 복사 된 참조라는 용어는 용어를 변경하지 않습니다. 두 개의 참조는 여전히 동일한 객체를 가리 킵니다. 이것은 순수 주의자의 주장이다.
John Strickler

3
Java는 객체를 전달하지 않고 포인터의 값을 객체에 전달합니다. 새 변수에서 원래 객체의 메모리 위치에 대한 새 포인터를 만듭니다. 메소드에서이 포인터 변수의 값 (이가 가리키는 메모리 주소)을 변경하면 메소드 호출자에 사용 된 원래 포인터는 수정되지 않은 상태로 유지됩니다. 당신은 참조 그것이라고 그 사실 매개 변수를 호출하는 경우 사본 오브젝트 수단이 참조는 것을 지금은 거기에 자체하도록 아닌 원래 참조 원본 참조가 통과하여 값
theferrit32

238

나는 "기준 별 통과 대 가치 별"에 대해 논쟁하는 것이 도움이되지 않는다고 생각한다.

두 경우 모두 "자바는 통과 (참조 / 값)"라고 말하면 완전한 답을 제공 할 수 없습니다. 다음은 메모리에서 발생하는 상황을 이해하는 데 도움이되는 추가 정보입니다.

Java 구현을하기 전에 스택 / 힙에 대한 충돌 코스 : 식당의 플레이트 스택과 같이 값이 규칙적으로 멋지게 스택으로 올라가거나 내려갑니다. 힙의 메모리 (동적 메모리라고도 함)가 우연히 발생하고 구성이 해제되었습니다. JVM은 가능한 한 공간을 찾고 더 이상 사용하지 않는 변수를 확보합니다.

괜찮아. 먼저, 로컬 프리미티브가 스택으로 이동합니다. 따라서이 코드 :

int x = 3;
float y = 101.1f;
boolean amIAwesome = true;

결과는 다음과 같습니다.

스택의 프리미티브

객체를 선언하고 인스턴스화 할 때 실제 객체는 힙에서 진행됩니다. 스택에 무슨 일이? 힙에서 오브젝트의 주소입니다. C ++ 프로그래머는 이것을 포인터라고 부르지 만 일부 Java 개발자는 "포인터"라는 단어에 반대합니다. 도대체 무엇이. 객체의 주소가 스택에 있는지 확인하십시오.

이렇게 :

int problems = 99;
String name = "Jay-Z";

ab * 7ch aint one!

배열은 객체이므로 힙에도 적용됩니다. 그리고 배열의 객체는 어떻습니까? 그들은 자신의 힙 공간을 얻고 각 객체의 주소는 배열 내부로 들어갑니다.

JButton[] marxBros = new JButton[3];
marxBros[0] = new JButton("Groucho");
marxBros[1] = new JButton("Zeppo");
marxBros[2] = new JButton("Harpo");

마르크스 형제

그렇다면 메소드를 호출 할 때 무엇이 ​​전달됩니까? 객체를 전달하는 경우 실제로 전달하는 것은 객체의 주소입니다. 일부는 주소의 "값"이라고 말하고 일부는 객체에 대한 참조 일뿐입니다. 이것이 "참조"와 "가치"지지자들 사이의 거룩한 전쟁의 기원입니다. 당신이 부르는 것은 전달되는 것이 객체의 주소라는 것을 이해하는 것만 큼 중요하지 않습니다.

private static void shout(String name){
    System.out.println("There goes " + name + "!");
}

public static void main(String[] args){
    String hisName = "John J. Jingleheimerschmitz";
    String myName = hisName;
    shout(myName);
}

하나의 문자열이 작성되고이를위한 공간이 힙에 할당되고 문자열의 주소가 스택에 저장되고 식별자 hisName가 제공됩니다. 두 번째 문자열의 주소가 첫 번째와 동일하므로 새 문자열이 작성되지 않으며 새 힙 공간이 할당되지 않지만 스택에 새 식별자가 작성됩니다. 그런 다음 shout()새로운 스택 프레임이 생성되고 새로운 식별자 name가 생성되고 이미 존재하는 문자열의 주소가 할당됩니다.

라 다 디 다 다 다

그래서 가치, 참조? 당신은 "감자"라고 말합니다.


7
그러나 함수가 참조가있는 주소로 변수를 변경하는 것처럼 보이는 좀 더 복잡한 예를 따라야합니다.
브라이언 피터슨

34
사람들은 실제 문제가 아니기 때문에 스택 대 힙의 "실제 문제를 중심으로 춤을 추지" 않습니다 . 최선의 구현 세부 사항이며 최악의 경우 완전히 잘못되었습니다. ( "탈출 분석을"구글과 대량의 객체는 아마도 프리미티브 포함하는 객체가 스택에 사는 것이 아주 가능합니다. 하지 않는 진짜 문제는 스택에 살고 있습니다.) 정확하게 참조 형식과 값 형식의 차이 특히, 참조 형 변수의 값은 참조하는 객체가 아니라 참조입니다.
cHao

8
Java가 객체가 메모리의 어디에 존재하는지 실제로 보여줄 필요가 없다는 점에서 "구현 세부 사항"입니다. 실제로 해당 정보가 유출되는 것을 피하기 위해 결정된 것 같습니다 . 객체를 스택에 넣을 수 있으며 결코 알 수 없습니다. 당신이 걱정한다면, 당신은 잘못된 것에 집중하고 있습니다.이 경우에는 실제 문제를 무시하는 것입니다.
cHao

10
그리고 "프리미티브가 스택에 간다"는 말이 맞지 않습니다. 기본 지역 변수 는 스택에 있습니다. (물론 최적화되지 않은 경우) 로컬 참조 변수도 마찬가지 입니다. 그리고 객체 내에 정의 된 기본 멤버는 객체가 어디에 있든 살 수 있습니다.
cHao

9
여기에 의견에 동의하십시오. 스택 / 힙은 부수적 인 문제이며 관련이 없습니다. 일부 변수는 스택에 있고 일부는 정적 메모리 (정적 변수)에 있고 힙 (모든 객체 멤버 변수)에 많이 있습니다. 이러한 변수 중 어느 것도 참조로 전달할 수 없습니다. 호출 된 메소드에서 인수로 전달 된 변수의 값을 변경할 수 없습니다. 따라서 Java에는 참조로 전달이 없습니다.
fishinear

195

대비를 보여주기 위해 다음 C ++Java를 비교하십시오. 스 니펫을 .

C ++ : 참고 : 잘못된 코드-메모리 누수! 그러나 그것은 요점을 보여줍니다.

void cppMethod(int val, int &ref, Dog obj, Dog &objRef, Dog *objPtr, Dog *&objPtrRef)
{
    val = 7; // Modifies the copy
    ref = 7; // Modifies the original variable
    obj.SetName("obj"); // Modifies the copy of Dog passed
    objRef.SetName("objRef"); // Modifies the original Dog passed
    objPtr->SetName("objPtr"); // Modifies the original Dog pointed to 
                               // by the copy of the pointer passed.
    objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                   // leaving the original object alone.
    objPtrRef->SetName("objRefPtr"); // Modifies the original Dog pointed to 
                                    // by the original pointer passed. 
    objPtrRef = new Dog("newObjPtrRef"); // Modifies the original pointer passed
}

int main()
{
    int a = 0;
    int b = 0;
    Dog d0 = Dog("d0");
    Dog d1 = Dog("d1");
    Dog *d2 = new Dog("d2");
    Dog *d3 = new Dog("d3");
    cppMethod(a, b, d0, d1, d2, d3);
    // a is still set to 0
    // b is now set to 7
    // d0 still have name "d0"
    // d1 now has name "objRef"
    // d2 now has name "objPtr"
    // d3 now has name "newObjPtrRef"
}

자바에서는

public static void javaMethod(int val, Dog objPtr)
{
   val = 7; // Modifies the copy
   objPtr.SetName("objPtr") // Modifies the original Dog pointed to 
                            // by the copy of the pointer passed.
   objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                  // leaving the original object alone.
}

public static void main()
{
    int a = 0;
    Dog d0 = new Dog("d0");
    javaMethod(a, d0);
    // a is still set to 0
    // d0 now has name "objPtr"
}

Java에는 두 가지 유형의 전달 만 있습니다. 내장 유형의 값 및 오브젝트 유형의 포인터 값.


7
+1 또한 Dog **objPtrPtrC ++ 예제에 추가 하여 포인터가 가리키는 것을 수정할 수 있습니다.
Amro

1
그러나 이것은 Java에서 주어진 질문에 대한 답변이 아닙니다. 사실 Java의 모든 메소드는 Pass By Value에 지나지 않습니다.
tauitdnmd

2
이 예제는 자바가 C에서 포인터와 매우 동등한 것을 사용한다는 것을 증명합니다. C의 값으로 전달하는 것은 기본적으로 읽기 전용입니까? 결국,이 전체 스레드는 이해할 수 없습니다
amdev

179

Java는 값으로 객체에 대한 참조를 전달합니다.


이것이 가장 좋은 대답입니다. 짧고 맞습니다.
착용

166

기본적으로 Object 매개 변수를 다시 할당해도 인수에 영향을 미치지 않습니다 (예 :

private void foo(Object bar) {
    bar = null;
}

public static void main(String[] args) {
    String baz = "Hah!";
    foo(baz);
    System.out.println(baz);
}

"Hah!"대신 인쇄됩니다 null. 이것이 작동하는 이유 는에 대한 참조 인의 bar값의 사본 이기 때문 입니다 . 실제 참조 자체라면로 다시 정의 했을 것 입니다.baz"Hah!"foobaznull


8
오히려 bar는 참조 baz (또는 baz 별명)의 사본이며 처음에는 동일한 객체를 가리 킵니다.
MaxZoom

String 클래스와 다른 모든 클래스 사이에 약간의 차이가 있습니까?
Mehdi Karamosly

152

나는 아무도 바바라 Liskov를 아직 언급하지 않았다는 것을 믿을 수 없다. 그녀는 1974 년에 CLU를 디자인 할 때 동일한 용어 문제에 부딪 쳤고 "값이있는 값에 의한 호출"이라는 특정 경우에 대해 공유 (객체 공유에 의한 호출객체에 의한 호출 이라고도 함) 라는 호출 이라는 용어를 발명했습니다. 참조 ".


3
나는 명명법에서이 구별을 좋아한다. 불행히도 Java는 객체를 공유하여 호출을 지원하지만 값으로 호출하지는 않습니다 (C ++과 마찬가지로). Java는 복합 데이터 유형이 아닌 기본 데이터 유형에 대해서만 값별 호출을 지원합니다.
Derek Mahar

2
나는 우리가 추가 용어가 필요하다고 생각하지 않습니다. 그것은 특정 유형의 가치에 대한 가치에 의한 것입니다. "원시 호출"을 추가하면 설명이 추가됩니까?
Scott Stanchfield


전역 컨텍스트 객체를 공유하거나 다른 참조가 포함 된 컨텍스트 객체를 전달하여 참조로 전달할 수 있습니까? 나는 여전히 가치를 지니고 있지만 적어도 참조에 액세스 할 수있어 수정하고 다른 것을 가리킬 수 있습니다.
YoYo

1
@Sanjeev : Call-by-object-sharing은 특별한 값별 전달 사례입니다. 그러나 많은 사람들이 자바 (그리고 파이썬, 루비, ECMAScript, 스몰 토크와 같은 유사한 언어)는 참조로 통과한다고 주장합니다. 나는 그것을 가치별로 전달하는 것을 선호하지만, 객체 별 공유는 그것이 가치에 의한 것이 아니라고 주장하는 사람들조차도 동의 할 수있는 합리적인 용어 인 것 같습니다 .
Jörg W Mittag

119

문제의 핵심은 단어이다 참조 발현이 단어의 일반적인 의미에서 완전히 다른 의미 뭔가 "참조에 의해 전달" 참조 자바한다.

일반적으로 Java 참조 는 객체에 대한 참조를 의미 합니다 . 그러나 기술 용어 는 프로그래밍 언어 이론에서 참조 / 값으로 전달됩니다 . 변수를 보유하는 메모리 셀 에 대한 참조 는 완전히 다릅니다.


7
@Gevorg-그렇다면 "NullPointerException"은 무엇입니까?
핫 릭

5
@Hot : 불행히도 Java가 명확한 용어로 해결되기 전의 예외적 인 이름의 예외. c #에서 의미 적으로 동등한 예외를 NullReferenceException이라고합니다.
JacquesB

4
Java 용어에서 "참조"를 사용하는 것이 이해를 방해하는 영향 인 것 같습니다.
핫 릭

나는 이것을 소위 "객체에 대한 포인터"라고 부르는 것을 배웠다. 이것은 모호성을 감소시켰다.
moronkreacionz

86

Java에서는 모든 것이 참조이므로 Point pnt1 = new Point(0,0);다음과 같은 것이 있으면 Java는 다음을 수행합니다.

  1. 새 Point 객체를 만듭니다.
  2. 새로운 포인트 참조를 작성하고 해당 참조 초기화 (참조) 지점을 이전에 만든 Point 객체에.
  3. 여기에서 Point 객체 수명을 통해 pnt1 참조를 통해 해당 객체에 액세스합니다. 따라서 Java에서는 참조를 통해 객체를 조작한다고 말할 수 있습니다.

여기에 이미지 설명을 입력하십시오

Java는 메소드 인수를 참조로 전달하지 않습니다. 그것들은 가치에 의해 전달됩니다. 이 사이트 에서 예제를 사용 합니다 .

public static void tricky(Point arg1, Point arg2) {
  arg1.x = 100;
  arg1.y = 100;
  Point temp = arg1;
  arg1 = arg2;
  arg2 = temp;
}
public static void main(String [] args) {
  Point pnt1 = new Point(0,0);
  Point pnt2 = new Point(0,0);
  System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y); 
  System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
  System.out.println(" ");
  tricky(pnt1,pnt2);
  System.out.println("X1: " + pnt1.x + " Y1:" + pnt1.y); 
  System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);  
}

프로그램의 흐름 :

Point pnt1 = new Point(0,0);
Point pnt2 = new Point(0,0);

두 개의 서로 다른 참조가 연결된 두 개의 다른 Point 객체를 만듭니다. 여기에 이미지 설명을 입력하십시오

System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y); 
System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
System.out.println(" ");

예상되는 결과는 다음과 같습니다.

X1: 0     Y1: 0
X2: 0     Y2: 0

이 줄에서 '가치 통과'는 연극으로 들어갑니다 ...

tricky(pnt1,pnt2);           public void tricky(Point arg1, Point arg2);

참고 문헌 pnt1pnt2되는 값에 의해 전달 까다로운 방법, 지금 당신의 참조 수단 pnt1pnt2자신이 copies이름 arg1arg2낭포 pnt1arg1 포인트를 동일한 개체에. (동일에 대한 pnt2arg2) 여기에 이미지 설명을 입력하십시오

에서 tricky방법 :

 arg1.x = 100;
 arg1.y = 100;

여기에 이미지 설명을 입력하십시오

tricky방법의 다음

Point temp = arg1;
arg1 = arg2;
arg2 = temp;

여기에서는 먼저 temp참조 와 같은 위치를 가리키는 새 점 참조를 작성 arg1합니다. 그런 다음 참조 arg1를 같은 위치 로 가리 키 도록 참조 를 이동 arg2합니다. 마지막으로 같은 곳 arg2가리 킵니다temp .

여기에 이미지 설명을 입력하십시오

여기에서의 범위 tricky방법은 사라지고 당신이 참조에 더 이상 액세스 할 수 없습니다 : arg1, arg2, temp. 그러나 중요한 점은이 때 당신의 삶에서 '이러한 참조와 함께 할 모든 것이 영구적가있는 개체 영향을 미칠 것입니다 에있다.

따라서 method 실행 후으로 tricky돌아 가면 main다음과 같은 상황이 발생합니다. 여기에 이미지 설명을 입력하십시오

이제 프로그램을 완전히 실행하면 다음과 같습니다.

X1: 0         Y1: 0
X2: 0         Y2: 0
X1: 100       Y1: 100
X2: 0         Y2: 0

7
"Java에서 모든 것이 참조입니다"라고 말하면 Java에서 모든 객체 가 참조로 전달됩니다. 기본 데이터 유형 은 참조로 전달되지 않습니다.
Eric

주요 방법으로 교환 된 값을 인쇄 할 수 있습니다. a trick1 arg1.x = 1; arg1.y = 1; arg2.x = 2; arg2.y = 2;이 pnt2 refrence를 보유하고있는 arg1과 pnt1 참조를 보유하고있는 arg2로서, 다음과 같은 진술을 추가하십시오.X1: 2 Y1: 2 X2: 1 Y2: 1
Shahid Ghafoor

85

Java는 항상 참조로 전달되지 않고 값으로 전달됩니다.

우선, 우리는 가치에 의한 통과와 참조에 의한 통과가 무엇인지 이해해야합니다.

값으로 전달은 전달 된 실제 매개 변수 값의 메모리에 사본을 작성 함을 의미합니다. 이것은 실제 매개 변수 컨텐츠의 사본입니다 .

참조로 전달 (주소로 전달이라고도 함)은 실제 매개 변수의 주소 사본이 저장됨을 의미합니다 .

때로는 Java가 참조로 통과의 환상을 줄 수 있습니다. 아래 예제를 사용하여 작동 방식을 살펴 보겠습니다.

public class PassByValue {
    public static void main(String[] args) {
        Test t = new Test();
        t.name = "initialvalue";
        new PassByValue().changeValue(t);
        System.out.println(t.name);
    }

    public void changeValue(Test f) {
        f.name = "changevalue";
    }
}

class Test {
    String name;
}

이 프로그램의 출력은 다음과 같습니다.

changevalue

단계별로 이해하자 :

Test t = new Test();

우리 모두 알고 있듯이 힙에 객체를 만들고 참조 값을 다시 t로 반환합니다. 예를 들어, t 값이 0x100234(실제 JVM 내부 값을 모르는 경우, 이것은 단지 예일뿐)이라고 가정하십시오.

첫 일러스트

new PassByValue().changeValue(t);

참조 t를 함수에 전달하면 객체 테스트의 실제 참조 값을 직접 전달하지 않지만 t의 사본을 작성한 다음 함수에 전달합니다. value전달 되므로 변수의 실제 참조가 아닌 변수의 사본을 전달합니다. t의 값이이라고 말 했으므로 0x100234t와 f는 모두 같은 값을 가지므로 동일한 객체를 가리킬 것입니다.

두 번째 그림

참조 f를 사용하여 함수에서 내용을 변경하면 객체의 기존 내용이 수정됩니다. 그렇기 때문에 우리는 output을 얻었고 changevalue함수에서 업데이트되었습니다.

이를보다 명확하게 이해하려면 다음 예를 고려하십시오.

public class PassByValue {
    public static void main(String[] args) {
        Test t = new Test();
        t.name = "initialvalue";
        new PassByValue().changeRefence(t);
        System.out.println(t.name);
    }

    public void changeRefence(Test f) {
        f = null;
    }
}

class Test {
    String name;
}

이건 던져 NullPointerException? 아니요, 참조 사본 만 전달하기 때문입니다. 참조로 전달하는 경우 NullPointerException아래와 같이을 던질 수 있습니다.

세 번째 그림

잘하면 이것이 도움이 될 것입니다.


71

어떤 언어를 사용하든 관계없이 참조는 항상 표현 된 값입니다.

상자 외부를 살펴보면 어셈블리 또는 일부 저수준 메모리 관리를 살펴 보겠습니다. CPU 레벨 에서 메모리 또는 CPU 레지스터 중 하나에 쓰면 어떤 것에 대한 참조 도 즉시 값이 됩니다. (그래서 포인터 가 좋은 정의입니다. 동시에 목적이있는 값입니다).

메모리의 데이터에는 위치가 있으며 해당 위치에는 값 (바이트, 단어 등)이 있습니다. Assembly에는 특정 위치 (일명 변수)에 Name 을 제공하는 편리한 솔루션이 있지만 코드를 컴파일 할 때 어셈블러는 단순히 Name을 대체합니다. 브라우저가 도메인 이름을 IP 주소로 바꾸는 것처럼 을 지정된 위치로 바꿉니다.

핵심은 기술적으로 표현하지 않고 어떤 언어로든 참조를 전달하는 것은 기술적으로 불가능합니다 (즉시 가치가 될 때).

변수 Foo가 있고 위치 가 메모리에서 47 번째 바이트이고 그 이 5라고 가정 해 봅시다 . 메모리에서 223 번째 바이트에 있는 또 다른 변수 Ref2Foo 가 있으며 그 값은 47입니다.이 Ref2Foo는 기술 변수 일 수 있습니다 프로그램에서 명시 적으로 생성하지 않았습니다. 다른 정보없이 5와 47 만 보면 두 개의 값만 표시 됩니다. 당신이 그들을 참조로 사용하는 경우에 도달하기 위해 5우리는 여행해야합니다 :

(Name)[Location] -> [Value at the Location]
---------------------
(Ref2Foo)[223]  -> 47
(Foo)[47]       -> 5

이것이 점프 테이블의 작동 방식입니다.

Foo 값으로 메소드 / 함수 / 프로 시저를 호출 하려면 언어 와 여러 메소드 호출 모드 에 따라 메소드에 변수를 전달할 수있는 몇 가지 방법이 있습니다.

  1. 5는 CPU 레지스터 중 하나 (예 : EAX)에 복사됩니다.
  2. 5는 스택에 PUSHd를 가져옵니다.
  3. 47은 CPU 레지스터 중 하나에 복사됩니다
  4. 47 스택에 밀어 넣습니다.
  5. 223은 CPU 레지스터 중 하나로 복사됩니다.
  6. 223은 스택에 PUSHd를 가져옵니다.

기존 값 의 사본 인 값 위의 모든 경우 에 이제 값을 처리하는 수신 방법이 결정됩니다. 메소드 내에 "Foo"를 작성할 때, EAX에서 읽거나 자동으로 역 참조 되거나 이중 역 참조되는 경우 프로세스는 언어 작동 방식 및 / 또는 Foo 유형에 따라 다릅니다. 이는 역 참조 프로세스를 우회 할 때까지 개발자에게 숨겨져 있습니다. 따라서 참조가치입니다 기준은 (언어 레벨에서) 처리 할 수있는 값이므로 표현.

이제 Foo를 메소드에 전달했습니다.

  • 1과 2의 경우 Foo ( Foo = 9) 를 변경 하면 값의 사본이 있으므로 로컬 범위에만 영향을줍니다. 방법 내부에서 우리는 메모리에서 원래 Foo가 어디에 있는지 확인할 수 없습니다.
  • 3과 4의 경우 기본 언어 구문을 사용하고 Foo ( Foo = 11)를 변경하면 언어에 따라 Foo가 전체적으로 변경 될 수 있습니다 (예 : Java 또는 Pascal의 procedure findMin(x, y, z: integer;var m 과 같은 언어에 따라 다름 : integer);). 언어 당신이 역 참조 프로세스를 우회 할 수 있습니다 경우, 당신은 변경할 수 있습니다 47,하는 말 49. 그 시점에서 Foo는 로컬 포인터 를 변경했기 때문에 읽은 경우 변경된 것으로 보입니다 . 그리고 메소드 ( Foo = 12) 내 에서이 Foo를 수정한다면 예상과 다른 메모리에 쓸 수 있기 때문에 프로그램 실행 (일명 segfault)을 FUBAR 할 것입니다. 실행 파일을 보유 할 예정인 영역을 수정할 수도 있습니다 프로그램을 작성하고 코드를 작성하면 실행 코드가 수정됩니다 (Foo is at not 47). 그러나 Foo의 가치47메소드 47의 사본이기도 하기 때문에 메소드 내부의 글로벌 변수 만 변경되지 않았습니다 .
  • 5 및 6의 경우 223메소드 내에서 수정하면 3 또는 4에서와 동일한 신체 상해를 만듭니다 (포인터, 현재 잘못된 값을 가리키고 다시 포인터로 사용됨). 223이 복사 되었으므로 문제가 발생했습니다 . 그러나 역 참조 할 수있는 Ref2Foo(즉 223,) 지정된 값에 도달하고 수정하는 경우 47, 49Foo는 전역 적으로 Foo에 영향을 미칩니다 .이 경우 메소드는 사본을 223 받았지만 참조 47는 한 번만 존재하기 때문에 변경됩니다. 에 49모든 이어질 것입니다 Ref2Foo잘못된 값을 두 번 역 참조입니다.

중요하지 않은 세부 사항을 참고하면 참조로 전달하는 언어조차도 값을 함수에 전달하지만 해당 함수는 역 참조 목적으로 사용해야한다는 것을 알고 있습니다. 이 값으로 전달은 실제로 쓸모없고 용어는 참조 로만 전달 되기 때문에 프로그래머에게 숨겨져 있습니다 .

엄격한 값으로 전달하는 것도 쓸모가 없습니다. 즉, 배열을 인수로 사용하여 메소드를 호출 할 때마다 100MB 배열을 복사해야하므로 Java를 값으로 엄격하게 전달할 수 없습니다. 모든 언어는이 거대한 배열에 대한 참조를 값으로 전달하고 해당 배열을 메소드 내부에서 로컬로 변경하거나 메소드가 (Java 가하는 것처럼) 메소드를 전역 적으로 수정하도록 허용하면 쓰기시 복사 메커니즘을 사용합니다. 발신자의 관점과 일부 언어를 사용하면 참조 자체의 값을 수정할 수 있습니다.

그래서 짧은 자바 자신의 용어로, 자바는 패스에 의해 가치값이 중 하나가 될 수있는 진정한 가치 또는 (A)의 표현이다 참조 .


1
참조에 의한 전달 언어에서, 전달되는 것 (참조)은 일시적입니다. 수신자는 복사 할 "추정"되지 않습니다. Java에서 배열 전달은 "객체 식별자"입니다. 생성 된 24601 번째 개체가 배열 일 때 "개체 # 24601"이라고하는 종이와 같습니다. 수신자는 원하는 곳 어디에서나 "개체 # 24601"을 복사 할 수 있으며 "개체 # 24601"이라고 적힌 종이를 가지고있는 사람은 모든 배열 요소를 사용하여 원하는 작업을 수행 할 수 있습니다. 전달 된 비트 패턴은 실제로 "개체 # 24601"이라고 말하지는 않지만 ...
supercat

... 핵심 요점은 배열을 식별하는 object-id의 수신자가 원하는 곳 어디에서나 해당 object-id를 저장하고 원하는 사람에게 줄 수 있으며, 모든 수신자가 배열을 액세스하거나 수정할 수 있다는 것입니다 원한다. 반대로, 배열이 그러한 것을 지원하는 Pascal과 같은 언어로 참조로 전달되면 호출 된 메소드는 배열로 원하는 것을 수행 할 수 있지만 코드를 배열을 수정하는 방식으로 참조를 저장할 수는 없습니다. 돌아온 후.
supercat

실제로, 파스칼 당신은 할 수 있습니다 , 모든 변수의 주소를 얻기가 복사 로컬에 의해 참조-통과 또는 수, addr가, 지정되지 않은 않는 @유형을하지, 당신은 (로컬로 복사 것들을 제외) 나중에 참조하는 변수를 수정할 수 있습니다. 그러나 나는 당신이 그렇게 할 이유를 알지 못합니다. 귀하의 예제 (개체 # 24601)에서 그 종이 슬립은 참조이며, 그 목적은 메모리에서 배열을 찾는 데 도움이되며 배열 데이터는 포함하지 않습니다. 프로그램을 다시 시작하면 내용이 이전 실행과 동일하더라도 동일한 배열에서 다른 object-id를 얻을 수 있습니다.
karatedog

"@"연산자는 표준 파스칼의 일부가 아니라 일반적인 확장으로 구현되었다고 생각했습니다. 표준의 일부입니까? 내 요점은 진정한 지나가는 언어를 사용하는 언어로, 임시 객체에 대한 비 임시 포인터를 생성 할 수있는 능력이 없다는 것입니다. 참조는 수신자가 "속임수"가 아니라면 이후에도 유일한 참조를 보유한다는 것을 알 수 있습니다. Java에서이를 달성하는 유일한 안전한 방법은 임시 객체를 만드는 것입니다.
supercat

...을 캡슐화 AtomicReference하고 참조 또는 대상을 노출시키지 않지만 대신 대상에 작업을 수행하는 메소드를 포함합니다. 객체가 전달 된 코드가 반환되면 AtomicReference[작성자가 직접 참조를 유지 한] 코드가 무효화되고 포기되어야합니다. 적절한 의미를 제공하지만 느리고 불안정합니다.
supercat

70

자바는 가치에 의한 호출

작동 원리

  • 항상 참조 값의 비트 사본을 전달합니다!

  • 그것이 원시 데이터 유형이라면,이 비트들은 원시 데이터 유형 자체의 값을 포함합니다. 따라서 우리가 메소드 내부의 헤더 값을 변경하면 외부의 변경 사항을 반영하지 않습니다.

  • Foo foo = new Foo () 와 같은 객체 데이터 유형 인 경우이 경우 객체의 주소 사본이 파일 바로 가기처럼 전달 됩니다 .C : \ desktop에 텍스트 파일 abc.txt 가 있고 바로 가기를 만든다고 가정합니다. 같은 파일을 C : \ desktop \ abc-shortcut에 넣고 C : \ desktop \ abc.txt 에서 파일에 액세스하고 'Stack Overflow'를 작성 하고 파일을 닫은 다음 바로 가기에서 파일을 다시 열면 write '는 프로그래머가 배울 수있는 가장 큰 온라인 커뮤니티입니다.' 다음 총 파일 변경은 'Stack Overflow는 프로그래머가 배울 수있는 가장 큰 온라인 커뮤니티입니다'입니다.즉, 파일을 열 때마다 동일한 파일에 액세스 할 때마다 할 수 있습니다파일로 Foo 하고 foo가 123hd7h ( C : \ desktop \ abc.txt 와 같은 원래 주소 ) 주소와 234jdid ( 실제로 파일의 원래 주소를 포함하는 C : \ desktop \ abc-shortcut 과 같은 복사 된 주소 )에 저장된다고 가정 하십시오. 이해하기 쉽도록 바로 가기 파일과 느낌을 만드십시오.


66

이것을 다루는 훌륭한 답변이 이미 있습니다. c ++의 Pass-by-reference와 Java의 Pass-by-value 사이의 동작을 대조 하는 매우 간단한 예제 (컴파일 할) 를 공유하여 작은 기여를하고 싶었습니다 .

몇 가지 사항 :

  1. "참조"라는 용어는 두 가지 의미로 오버로드됩니다. Java에서는 단순히 포인터를 의미하지만 "Pass-by-Preference"의 맥락에서 전달 된 원래 변수에 대한 핸들을 의미합니다.
  2. 자바는 가치를 전달합니다 입니다. Java는 C의 자손입니다 (다른 언어 중에서도). C 이전에는 FORTRAN 및 COBOL과 같은 일부 (전부는 아님) 언어가 PBR을 지원했지만 C는 지원하지 않았습니다. PBR은 이러한 다른 언어가 서브 루틴 내에서 전달 된 변수를 변경할 수 있도록했습니다. 동일한 기능을 수행하기 위해 (즉, 함수 내 변수 값 변경) C 프로그래머는 변수에 대한 포인터를 함수로 전달했습니다. Java와 같이 C에서 영감을 얻은 언어는이 아이디어를 빌려 왔으며 Java가 포인터를 References라고 부른다는 점을 제외하고 C와 마찬가지로 메소드에 포인터를 계속 전달합니다. 다시, 이것은 "Pass-By-Reference"와 "Reference"라는 단어의 다른 사용입니다.
  3. C ++에서는 "&"문자 (C 및 C ++에서 "변수의 주소"를 나타내는 데 사용되는 것과 동일한 문자 임)를 사용하여 참조 매개 변수를 선언하여 참조로 전달할 수 있습니다. 예를 들어, 참조로 포인터를 전달하면 매개 변수와 인수가 동일한 객체를 가리키는 것이 아닙니다. 오히려 그들은 같은 변수입니다. 하나가 다른 주소로 설정되거나 null로 설정되면 다른 주소도 설정됩니다.
  4. 아래의 C ++ 예제 에서 reference 로 null 종료 문자열 에 대한 포인터 를 전달합니다 . 그리고 아래의 Java 예제에서 Java 참조를 문자열 (문자열에 대한 포인터와 동일)에 값으로 전달합니다. 주석의 출력을 확인하십시오.

참조 예제로 C ++ 전달 :

using namespace std;
#include <iostream>

void change (char *&str){   // the '&' makes this a reference parameter
    str = NULL;
}

int main()
{
    char *str = "not Null";
    change(str);
    cout<<"str is " << str;      // ==>str is <null>
}

값 예제로 Java 전달 "Java 참조"

public class ValueDemo{

    public void change (String str){
        str = null;
    }

     public static void main(String []args){
        ValueDemo vd = new ValueDemo();
        String str = "not null";
        vd.change(str);
        System.out.println("str is " + str);    // ==> str is not null!!
                                                // Note that if "str" was
                                                // passed-by-reference, it
                                                // WOULD BE NULL after the
                                                // call to change().
     }
}

편집하다

여러 사람들이 내 예제를 보지 않거나 c ++ 예제를 얻지 못했음을 나타내는 의견을 작성했습니다. 연결이 어디에 있는지 확실하지 않지만 c ++ 예제를 추측하는 것은 분명하지 않습니다. 패스 바이 레퍼런스가 파스칼에서 더 깨끗해 보인다고 생각하기 때문에 파스칼에 동일한 예제를 게시하고 있지만 잘못 될 수 있습니다. 나는 사람들을 더 혼란스럽게 할 수 있습니다. 내가하지 희망.

파스칼에서, 참조에 의해 전달 된 매개 변수를 "var 매개 변수"라고합니다. 아래 setToNil 프로 시저에서 'ptr'매개 변수 앞에 오는 키워드 'var'에 유의하십시오. 이 프로 시저에 포인터가 전달되면 reference 로 전달 됩니다 . 이 절차는 ptr을 nil로 설정하면 (파스칼은 NULL), 인수를 nil로 설정하므로 Java에서는이를 수행 할 수 없습니다.

program passByRefDemo;
type 
   iptr = ^integer;
var
   ptr: iptr;

   procedure setToNil(var ptr : iptr);
   begin
       ptr := nil;
   end;

begin
   new(ptr);
   ptr^ := 10;
   setToNil(ptr);
   if (ptr = nil) then
       writeln('ptr seems to be nil');     { ptr should be nil, so this line will run. }
end.

편집 2

Ken Arnold의 "THE Java Programming Language" , James Gosling (Java를 발명 한 사람) 및 David Holmes의 2 장, 섹션 2.6.5 에서 발췌 한 내용

메소드에 대한 모든 매개 변수는 "값으로"전달됩니다 . 즉, 메소드의 매개 변수 변수 값은 인수로 지정된 호출자의 사본입니다.

그는 객체에 대해 같은 점을 지적합니다. . .

매개 변수가 객체 참조 인 경우 객체 자체가 아니라 "값별"로 전달 되는 것은 객체 참조 입니다.

그리고 같은 섹션의 끝을 향하여 그는 자바가 가치에 의해서만 전달되고 결코 참조에 의해 전달되지 않는다는 것에 대해 더 넓은 진술을합니다.

Java 프로그래밍 언어 는 객체를 참조로 전달하지 않습니다. 그 값에 의해 객체 참조를 전달한다 . 동일한 참조의 두 사본이 동일한 실제 객체를 참조하므로 하나의 참조 변수를 통해 작성된 변경 사항은 다른 참조 변수를 통해 볼 수 있습니다. 을 기준으로 mode- pass를 전달하는 매개 변수는 정확히 하나 뿐이며 일을 단순하게 유지하는 데 도움이됩니다.

이 책의 섹션은 Java에서 매개 변수 전달과 참조 별 값과 값별의 차이점에 대한 훌륭한 설명을 제공하며 Java 작성자가 작성합니다. 나는 아직도 당신이 확신하지 않는 경우, 누군가가 그것을 읽을 것을 권장합니다.

두 모델의 차이점은 매우 미묘하다고 생각합니다. 실제로 참조로 전달하는 곳에서 프로그래밍을 수행하지 않으면 두 모델이 다른 곳을 놓치기 쉽습니다.

나는 이것이 논쟁을 해결하기를 희망하지만 아마 그렇지 않을 것입니다.

편집 3

이 게시물에 조금 집착했을 수도 있습니다. 아마도 Java 제작자가 실수로 잘못된 정보를 퍼뜨렸다 고 생각하기 때문일 것입니다. 포인터에 "reference"라는 단어를 사용하는 대신 dingleberry와 같은 다른 것을 사용했다면 아무런 문제가 없었을 것입니다. "자바는 딩글 베리를 참조가 아닌 가치에 따라 전달한다"고 말할 수 있으며, 아무도 혼동하지 않을 것입니다.

이것이 바로 Java 개발자 만이이 문제를 해결하는 이유입니다. 그들은 "참조"라는 단어를보고 그것이 의미하는 바를 정확히 알고 있다고 생각하기 때문에 반대 주장을 고려할 필요조차 없습니다.

어쨌든, 나는 오래된 게시물에서 댓글을 보았습니다. 풍선 비유는 내가 정말 좋아했습니다. 그래서 클립 아트를 붙여서 요점을 설명하기 위해 만화 세트를 만들기로 결심했습니다.

값으로 참조 전달-참조에 대한 변경 사항은 호출자의 범위에 반영되지 않지만 객체의 변경 사항은 반영됩니다. 참조가 복사 되었으나 원본과 복사본이 모두 동일한 개체를 참조하기 때문입니다. 값으로 객체 참조 전달

참조로 전달 - 참조 사본이 없습니다. 단일 참조는 호출자와 호출되는 함수 모두에 의해 공유됩니다. 참조 또는 객체 데이터에 대한 변경 사항은 호출자의 범위에 반영됩니다. 참조로 전달

편집 4

Java에서 매개 변수 전달의 저수준 구현을 설명하는이 주제에 대한 게시물을 보았습니다. 추상적 인 아이디어를 구체화하기 때문에 위대하고 매우 유용하다고 생각합니다. 그러나 나에게는 문제 의 기술적 구현에 대한 것보다 언어 사양에 설명 된 동작 에 대한 질문이 더 있습니다. 다음은 Java 언어 사양 섹션 8.4.1 에서 발췌 한 것입니다 .

메소드 또는 생성자가 호출되면 (§15.12) 실제 인수 표현식의 값은 메소드 또는 생성자의 본문을 실행하기 전에 선언 된 각 유형으로 새로 작성된 매개 변수를 초기화합니다. DeclaratorId에 나타나는 식별자는 공식 매개 변수를 참조하기 위해 메소드 또는 생성자의 본문에서 간단한 이름으로 사용될 수 있습니다.

즉, java는 메소드를 실행하기 전에 전달 된 매개 변수의 사본을 작성합니다. 대학에서 컴파일러를 공부 대부분의 사람들처럼, 내가 사용하는 "드래곤 도서" 입니다 컴파일러 책. 1 장의 "값별 호출"및 "참조 별 호출"에 대한 자세한 설명이 있습니다. 값별 설명은 Java 스펙과 정확히 일치합니다.

90 년대에 컴파일러를 연구했을 때, 나는 1986 년부터 책의 첫 번째 판을 사용했는데,이 책은 약 9 년에서 10 년 전에 Java를 미리 작성했습니다. 그러나 저는 2007 년부터 제 2 판 의 사본을 보았습니다 . "매개 변수 전달 메커니즘"이라고 라벨이 붙은 1.6.6 절은 매개 변수 전달을 아주 잘 설명합니다. 다음은 Java를 언급 한 "값별 호출"이라는 제목 아래의 발췌문입니다.

값별 호출에서는 실제 매개 변수가 평가되거나 (표현식 인 경우) 복사됩니다 (변수 인 경우). 값은 호출 된 프로 시저의 해당 형식 매개 변수에 속하는 위치에 배치됩니다. 이 메소드는 C 및 Java에서 사용되며 대부분의 다른 언어뿐만 아니라 C ++에서도 일반적인 옵션입니다.


4
솔직히 말해서 Java는 기본 유형에 대해서만 값으로 전달된다는 말을 통해이 답변을 단순화 할 수 있습니다. Object에서 상속받은 모든 것은 효과적으로 참조로 전달되며, 참조는 전달하는 포인터입니다.
스쿠버 스티브

1
@JuanMendes, 방금 C ++을 예로 사용했습니다. 다른 언어로 예를들 수 있습니다. "참조로 전달"이라는 용어는 C ++이 존재하기 오래 전에 존재했습니다. 매우 구체적인 정의를 가진 교과서 용어입니다. 그리고 정의상 Java는 참조로 전달되지 않습니다. 원하는 경우이 방법으로이 용어를 계속 사용할 수 있지만 사용법은 교과서 정의와 일치하지 않습니다. 포인터에 대한 포인터가 아닙니다. "Pass By Reference"를 허용하기 위해 언어에서 제공하는 구문입니다. 나의 예를 면밀히 살펴보십시오. 그러나 나의 말을 받아들이지 말고 직접 찾으십시오.
Sanjeev

2
@AutomatedMike Java를 "참조로 전달"로 설명하는 것도 오해의 소지가 있다고 생각합니다. 참조는 포인터에 지나지 않습니다. Sanjeev가 가치를 썼 듯이 참조 및 포인터는 Java 제작자가 사용하는 것과 상관없이 고유 한 의미를 갖는 교과서 용어입니다.
Jędrzej Dudkiewicz 2016 년

1
@ArtanisZeratul 요점은 미묘하지만 복잡하지는 않습니다. 내가 올린 만화를보세요. 나는 따라 가기가 매우 쉽다고 느꼈다. Java가 가치로 전달된다는 것은 단순한 의견이 아닙니다. 값별 전달에 대한 교과서 정의는 사실입니다. 또한 "항상 가치가 있다고 생각하는 사람들"에는 Java 개발자 인 James Gosling과 같은 컴퓨터 과학자들이 포함됩니다. 내 게시물의 "편집 2"아래에있는 그의 책 인용문을 참조하십시오.
Sanjeev

1
자바 (및 JS와 같은 다른 언어)에는 "참조로 전달"이라는 훌륭한 대답이 없습니다.
새로운 폴더

56

내가 아는 한 Java는 가치에 의한 호출 만 알고 있습니다. 이는 사본으로 작업 할 기본 데이터 유형과 객체에 대한 참조 사본으로 작업 할 객체를 의미합니다. 그러나 나는 함정이 있다고 생각한다. 예를 들어, 이것은 작동하지 않습니다 :

public static void swap(StringBuffer s1, StringBuffer s2) {
    StringBuffer temp = s1;
    s1 = s2;
    s2 = temp;
}


public static void main(String[] args) {
    StringBuffer s1 = new StringBuffer("Hello");
    StringBuffer s2 = new StringBuffer("World");
    swap(s1, s2);
    System.out.println(s1);
    System.out.println(s2);
}

스왑 기능에서는 기본 참조에 영향을 미치지 않는 사본을 사용하기 때문에 World Hello가 아닌 Hello World에 채워집니다. 그러나 객체가 변경 불가능한 경우 다음과 같이 변경할 수 있습니다.

public static void appendWorld(StringBuffer s1) {
    s1.append(" World");
}

public static void main(String[] args) {
    StringBuffer s = new StringBuffer("Hello");
    appendWorld(s);
    System.out.println(s);
}

명령 행에 Hello World가 채워집니다. StringBuffer를 String으로 변경하면 String이 변경되지 않기 때문에 Hello 만 생성됩니다. 예를 들면 다음과 같습니다.

public static void appendWorld(String s){
    s = s+" World";
}

public static void main(String[] args) {
    String s = new String("Hello");
    appendWorld(s);
    System.out.println(s);
}

그러나 String과 함께 사용할 수 있도록 String 래퍼를 만들 수 있습니다.

class StringWrapper {
    public String value;

    public StringWrapper(String value) {
        this.value = value;
    }
}

public static void appendWorld(StringWrapper s){
    s.value = s.value +" World";
}

public static void main(String[] args) {
    StringWrapper s = new StringWrapper("Hello");
    appendWorld(s);
    System.out.println(s.value);
}

편집 : 나는 또한 StringBuffer와 같이 불변의 객체로 할 수없는 원래 객체를 수정할 수 있기 때문에 두 개의 문자열을 "추가"할 때 StringBuffer를 사용해야하는 이유라고 생각합니다.


스왑 테스트의 경우 +1- 참조전달하는 것과 값 으로 참조전달하는 것을 구별하는 가장 간단하고 관련성있는 방법 일 것입니다 . 당신은 쉽게 기능을 쓸 수 IFF swap(a, b)(1) 스왑 것을 ab발신자의 POV에서, (2) 형식에 얽매이지 정적 입력이 허용하는 범위입니다 (다른 유형을 사용하여 의미를 더의 선언 된 유형의 변화보다 더 아무것도 필요하지 않습니다 ab) 그리고 (3) 호출자가 명시 적으로 포인터 나 이름을 전달할 필요가 없다면, 언어는 참조에 의한 전달을 지원합니다.
cHao

".. 원시 데이터 유형의 경우 사본으로 작업하고 객체의 경우 객체에 대한 참조 사본으로 작업합니다"-완벽하게 작성되었습니다!
Raúl

55

아니요, 참조로 전달되지 않습니다.

Java는 Java 언어 사양에 따라 값으로 전달됩니다.

메소드 또는 생성자가 호출되면 (§15.12) 실제 인수 표현식의 값은 메소드 또는 생성자의 본문을 실행하기 전에 선언 된 각 유형으로 새로 작성된 매개 변수를 초기화 합니다. DeclaratorId에 나타나는 식별자는 공식 매개 변수 를 참조하기 위해 메서드 또는 생성자의 본문에서 간단한 이름으로 사용될 수 있습니다 .


52

네 가지 예를 통해 이해를 설명하려고 노력하겠습니다. Java는 값으로 전달되며 참조로 전달되지 않습니다.

/ **

값으로 전달

Java에서 모든 매개 변수는 값으로 전달됩니다. 즉 메소드 인수 지정은 호출자에게 표시되지 않습니다.

* /

예 1 :

public class PassByValueString {
    public static void main(String[] args) {
        new PassByValueString().caller();
    }

    public void caller() {
        String value = "Nikhil";
        boolean valueflag = false;
        String output = method(value, valueflag);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'value' and 'valueflag'
         */
        System.out.println("output : " + output);
        System.out.println("value : " + value);
        System.out.println("valueflag : " + valueflag);

    }

    public String method(String value, boolean valueflag) {
        value = "Anand";
        valueflag = true;
        return "output";
    }
}

결과

output : output
value : Nikhil
valueflag : false

예 2 :

/ ** * * 값으로 전달 * * /

public class PassByValueNewString {
    public static void main(String[] args) {
        new PassByValueNewString().caller();
    }

    public void caller() {
        String value = new String("Nikhil");
        boolean valueflag = false;
        String output = method(value, valueflag);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'value' and 'valueflag'
         */
        System.out.println("output : " + output);
        System.out.println("value : " + value);
        System.out.println("valueflag : " + valueflag);

    }

    public String method(String value, boolean valueflag) {
        value = "Anand";
        valueflag = true;
        return "output";
    }
}

결과

output : output
value : Nikhil
valueflag : false

예 3 :

/ **이 'Pass By Value'에는 'Pass By Reference'라는 느낌이 있습니다.

어떤 사람들은 기본 유형과 '문자열'이 '값으로 전달'이고 객체가 '참조로 전달'이라고 말합니다.

그러나이 예에서 우리는 실제로 가치에 의한 전달이라는 것을 이해할 수 있습니다. 여기서는 참조로서 가치를 전달한다는 점을 명심하십시오. 즉, 참조는 값으로 전달됩니다. 그렇기 때문에 로컬 범위 이후에도 변경이 가능합니다. 그러나 실제 범위를 벗어난 실제 참조는 변경할 수 없습니다. 그 의미는 PassByValueObjectCase2의 다음 예제에서 설명합니다.

* /

public class PassByValueObjectCase1 {

    private class Student {
        int id;
        String name;
        public Student() {
        }
        public Student(int id, String name) {
            super();
            this.id = id;
            this.name = name;
        }
        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        @Override
        public String toString() {
            return "Student [id=" + id + ", name=" + name + "]";
        }
    }

    public static void main(String[] args) {
        new PassByValueObjectCase1().caller();
    }

    public void caller() {
        Student student = new Student(10, "Nikhil");
        String output = method(student);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'student'
         */
        System.out.println("output : " + output);
        System.out.println("student : " + student);
    }

    public String method(Student student) {
        student.setName("Anand");
        return "output";
    }
}

결과

output : output
student : Student [id=10, name=Anand]

예 4 :

/ **

Example3 (PassByValueObjectCase1.java)에 언급 된 것 외에도 실제 범위를 벗어난 실제 참조를 변경할 수 없습니다. "

참고 :에 대한 코드를 붙여 넣지 않았습니다 private class Student. 에 대한 클래스 정의 Student는 Example3과 동일합니다.

* /

public class PassByValueObjectCase2 {

    public static void main(String[] args) {
        new PassByValueObjectCase2().caller();
    }

    public void caller() {
        // student has the actual reference to a Student object created
        // can we change this actual reference outside the local scope? Let's see
        Student student = new Student(10, "Nikhil");
        String output = method(student);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'student'
         */
        System.out.println("output : " + output);
        System.out.println("student : " + student); // Will it print Nikhil or Anand?
    }

    public String method(Student student) {
        student = new Student(20, "Anand");
        return "output";
    }

}

결과

output : output
student : Student [id=10, name=Nikhil]

49

Java에서는 참조를 통해 전달할 수 없으며, 분명한 방법 중 하나는 메소드 호출에서 둘 이상의 값을 리턴하려는 경우입니다. C ++에서 다음 코드를 고려하십시오.

void getValues(int& arg1, int& arg2) {
    arg1 = 1;
    arg2 = 2;
}
void caller() {
    int x;
    int y;
    getValues(x, y);
    cout << "Result: " << x << " " << y << endl;
}

때로는 Java에서 동일한 패턴을 사용하려고하지만 할 수는 없습니다. 적어도 직접적으로는 아닙니다. 대신 다음과 같이 할 수 있습니다.

void getValues(int[] arg1, int[] arg2) {
    arg1[0] = 1;
    arg2[0] = 2;
}
void caller() {
    int[] x = new int[1];
    int[] y = new int[1];
    getValues(x, y);
    System.out.println("Result: " + x[0] + " " + y[0]);
}

이전 답변에서 설명했듯이 Java에서는 배열에 대한 포인터를 값으로 전달 getValues합니다. 메소드가 배열 요소를 수정하기 때문에 충분하지만, 일반적으로 요소 0에 반환 값이 포함될 것으로 예상됩니다. 분명히 코드를 구성하지 않아도되거나 반환 값을 포함하거나 설정할 수있는 클래스 구성과 같은 다른 방법으로이 작업을 수행 할 수 있습니다. 그러나 위의 C ++에서 사용 가능한 간단한 패턴은 Java에서 사용할 수 없습니다.


48

나는이 답변을 스펙의 세부 사항을 추가하기 위해 기여할 것이라고 생각했습니다.

첫째, 참조로 전달하는 것과 값으로 전달하는 것의 차이점은 무엇입니까?

참조로 전달하면 호출 된 함수의 매개 변수가 호출자의 전달 된 인수 (값이 아니라 ID-변수 자체)와 동일 함을 의미합니다.

값으로 전달은 호출 된 함수의 매개 변수가 호출자의 전달 된 인수의 사본이됨을 의미합니다.

또는 wikipedia에서 참조로 전달 주제

참조 별 호출 평가 (참조 별 전달이라고도 함)에서 함수는 값의 사본이 아니라 인수로 사용되는 변수에 대한 암시 적 참조를받습니다. 이는 일반적으로 함수가 인수로 사용 된 변수 (호출자가 볼 수있는 변수)를 수정할 수 있음을 의미합니다.

그리고 가치에 의한 주제

값별 호출에서 인수 표현식이 평가되고 결과 값이 함수 [...]의 해당 변수에 바인딩됩니다. 함수 또는 프로 시저가 매개 변수에 값을 지정할 수있는 경우 로컬 사본 만 [...]으로 지정됩니다.

둘째, 메소드 호출에서 Java가 무엇을 사용하는지 알아야합니다. Java 언어 사양의 상태

메소드 또는 생성자가 호출되면 (§15.12) 실제 인수 표현식의 값은 새로 작성된 매개 변수를 초기화합니다. 메소드 또는 생성자의 본문을 실행하기 전에 선언 된 각 유형으로 합니다.

따라서 인수의 값을 해당 매개 변수에 할당합니다.

논증의 가치는 무엇입니까?

참조 유형을 고려해 보겠습니다. Java Virtual Machine Specification 상태

클래스 유형, 배열 유형 및 인터페이스 유형의 세 가지 참조 유형이 있습니다. 해당 값은 동적으로 생성 된 클래스 인스턴스, 배열 또는 인터페이스를 구현하는 클래스 인스턴스 또는 배열에 대한 참조입니다.

Java 언어 사양 도 상태

참조 값 (종종 단지 참조)은 이러한 객체에 대한 포인터 이며 객체가없는 특수 null 참조입니다.

인수 (일부 참조 유형)의 값은 객체에 대한 포인터입니다. 변수, 참조 유형 리턴 유형의 메소드 호출 및 인스턴스 작성 표현식 ( new ...)은 모두 참조 유형 값으로 해석됩니다.

그래서

public void method (String param) {}
...
String var = new String("ref");
method(var);
method(var.toString());
method(new String("ref"));

모두 String인스턴스 에 대한 참조 값을 메소드의 새로 작성된 매개 변수에 바인드합니다 param. 이것이 바로 값별 정의가 설명하는 것입니다. 따라서 Java는 값을 전달합니다 .

참조를 따라 메소드를 호출하거나 참조 된 오브젝트의 필드에 액세스 할 수 있다는 사실은 대화와 전혀 관련이 없습니다. 통과 기준의 정의는

이는 일반적으로 함수가 인수로 사용 된 변수 (호출자가 볼 수있는 변수)를 수정할 수 있음을 의미합니다.

Java에서 변수를 수정하면 변수를 다시 할당해야합니다. Java에서는 메소드 내에서 변수를 다시 지정하면 호출자에게 알리지 않습니다. 변수가 참조하는 객체를 수정하는 것은 완전히 다른 개념입니다.


기본 값은 Java 가상 머신 사양 ( 여기) 에도 정의되어 있습니다 . 유형의 값은 해당 정수 또는 부동 소수점 값이며 적절하게 인코딩됩니다 (8, 16, 32, 64 등 비트).


42

Java에서는 참조 만 전달되고 값으로 전달됩니다.

Java 인수는 모두 값으로 전달됩니다 (메서드가 메소드를 사용할 때 참조가 복사 됨).

기본 유형의 경우 Java 동작은 간단합니다. 기본 유형의 다른 인스턴스에 값이 복사됩니다.

객체의 경우 동일합니다. 객체 변수는 "new"키워드를 사용하여 생성 된 객체의 주소 만 보유하는 포인터 (버킷) 이며 기본 유형처럼 복사됩니다.

동작은 기본 유형과 다르게 나타날 수 있습니다. 복사 된 객체 변수에 동일한 주소 (같은 객체)가 포함되어 있기 때문입니다. 객체의 내용 / 멤버 는 여전히 메소드 내에서 수정되고 나중에 외부에 액세스하여 (포함) 객체 자체가 참조로 전달되었다는 환상을 줄 수 있습니다.

"문자열"개체 는 "범례가 참조로 전달된다"는 도시의 전설에 대한 좋은 예입니다 .

실제로 메소드를 사용하면 인수로 전달 된 문자열의 값을 업데이트 할 수 없습니다.

String 객체는 final로 선언 된 배열에 의해 수정 될 수없는 문자를 보유합니다 . "새"를 사용하여 오브젝트 주소 만 다른 주소로 대체 ​​될 수 있습니다. 변수를 업데이트하기 위해 "new"를 사용하면 변수가 처음에 값으로 전달되어 복사되었으므로 외부에서 오브젝트에 액세스 할 수 없습니다.


프리미티브와 관련하여 객체와 byVal에 의해 byRef입니까?
Mox

@mox를 읽어주십시오 : 객체는 참조로 전달되지 않습니다. 이것은 ledgend입니다 : String a = new String ( "unchanged");
user1767316

1
@Aaron "참조로 전달"은 "Java에서"참조 "라고하는 유형의 멤버 인 값을 전달 함을 의미하지 않습니다. "참조"의 두 가지 용도는 다른 것을 의미합니다.
philipxy

2
상당히 혼란스러운 답변. 참조로 전달되는 메소드에서 문자열 오브젝트를 변경할 수없는 이유는 String 오브젝트가 의도적으로 변경 될 수없고 다음과 같은 작업을 수행 할 수 없기 때문 strParam.setChar ( i, newValue )입니다. 즉, 문자열은 다른 것과 마찬가지로 값으로 전달되며 String은 기본이 아닌 유형이므로 해당 값은 new로 만든 것에 대한 참조이며 String.intern ()을 사용하여 확인할 수 있습니다 .
zakmck

1
대신 param = "another string"(new String ( "another string")과 동일)을 통해 문자열을 변경할 수 없습니다. param의 참조 값 (이제 "다른 문자열"을 가리킴)은 메소드의 신체. 그러나 다른 객체의 경우 클래스 인터페이스가 허용하는 경우 param.changeMe ()를 수행 할 수 있으며 param의 참조 값 자체에도 불구하고 param이 그것을 가리키고 있기 때문에 참조되는 최상위 레벨 객체가 변경된다는 점이 다릅니다 (C 용어로 주소가 지정된 위치)는 메소드에서 다시 팝업 할 수 없습니다.
zakmck

39

독창적 인 포스터와 같은 인상을 받았던 기억이나 방식은 다음과 같습니다. Java는 항상 가치가 있습니다. Java의 모든 객체 (Java의 경우 기본 요소를 제외한 모든 것)는 참조입니다. 이러한 참조는 값으로 전달됩니다.


2
나는 당신의 두 번째-마지막 문장이 매우 오도되는 것을 발견했습니다. "Java의 모든 객체가 참조"인 것은 사실이 아닙니다. 참조 인 객체에 대한 참조 일뿐 입니다.
Dawood ibn Kareem

37

많은 사람들이 전에 언급했듯이 Java는 항상 가치를 전달합니다.

다음은 차이점을 이해하는 데 도움이되는 또 다른 예입니다 ( 클래식 스왑 예 ).

public class Test {
  public static void main(String[] args) {
    Integer a = new Integer(2);
    Integer b = new Integer(3);
    System.out.println("Before: a = " + a + ", b = " + b);
    swap(a,b);
    System.out.println("After: a = " + a + ", b = " + b);
  }

  public static swap(Integer iA, Integer iB) {
    Integer tmp = iA;
    iA = iB;
    iB = tmp;
  }
}

인쇄물:

전 : a = 2, b = 3
후 : a = 2, b = 3

이것은 iA와 iB가 전달 된 참조의 값이 같은 새로운 로컬 참조 변수이기 때문에 발생합니다 (각각 a와 b를 가리킴). 따라서 iA 또는 iB의 참조를 변경하려고하면이 방법 이외의 로컬 범위에서만 변경됩니다.


32

자바는 가치를 지니고있다. 이를 검증하는 매우 간단한 예입니다.

public void test() {
    MyClass obj = null;
    init(obj);
    //After calling init method, obj still points to null
    //this is because obj is passed as value and not as reference.
}
private void init(MyClass objVar) {
    objVar = new MyClass();
}

4
이것이 Java가 가치를 지니는 것을 보는 가장 명확하고 간단한 방법입니다. 에 대한 참조가 아닌 obj( null) 의 값 이에 전달 init되었습니다 obj.
David Schwartz

31

나는 항상 그것을 "통과로 통과"로 생각합니다. 프리미티브 또는 참조 값의 사본입니다. 프리미티브 인 경우 값인 비트의 복사본이며, 객체 인 경우 참조의 복사본입니다.

public class PassByCopy{
    public static void changeName(Dog d){
        d.name = "Fido";
    }
    public static void main(String[] args){
        Dog d = new Dog("Maxx");
        System.out.println("name= "+ d.name);
        changeName(d);
        System.out.println("name= "+ d.name);
    }
}
class Dog{
    public String name;
    public Dog(String s){
        this.name = s;
    }
}

Java PassByCopy의 출력 :

이름 = Maxx
이름 = Fido

기본 랩퍼 클래스 및 문자열은 변경할 수 없으므로 해당 유형을 사용하는 모든 예제는 다른 유형 / 객체와 동일하게 작동하지 않습니다.


5
"패스 바이 패스"는 값별 패스의 의미 입니다.
TJ Crowder

@TJCrowder. "복사 별 패스"는 Java 목적으로 동일한 개념을 표현하는 데 더 적합한 방법 일 수 있습니다. 의미 상으로는 참조로 전달하는 것과 유사한 아이디어를 구체화하는 것이 매우 어렵 기 때문에 Java에서 원하는 것입니다.
마이크 설치류

@ mikerodent-죄송합니다, 나는 그것을 따르지 않았습니다. :-) "값별 패스"및 "참조 별 패스"는 이러한 개념의 올바른 기술 용어입니다. 우리는 새로운 것을 만들기보다는 그것들을 사용해야합니다 (사람들이 필요할 때 정의를 제공함). 위에서 나는 "패스 바이 패스 (pass by copy)"는 "값별 패스"가 의미하는 것이지만 실제로는 "패스 바이 패스 (pass by copy)"가 무엇을 의미하는지 명확하지 않습니다. 가치? (예 : 객체 참조) 객체 자체? 그래서 저는 예술 용어를 고수합니다. :-)
TJ Crowder

28

다른 언어와는 달리 Java에서는 값으로 전달 및 참조로 전달을 선택할 수 없으며 모든 인수는 값으로 전달됩니다. 메소드 호출은 두 가지 유형의 값, 즉 기본 값 (예 : int 및 double 값의 사본) 및 오브젝트에 대한 참조 사본의 사본을 메소드에 전달할 수 있습니다.

메소드가 기본 유형 매개 변수를 수정하는 경우, 매개 변수 변경은 호출 메소드의 원래 인수 값에 영향을 미치지 않습니다.

객체에 관해서는 객체 자체가 메소드에 전달 될 수 없습니다. 따라서 객체의 참조 (주소)를 전달합니다. 이 참조를 사용하여 원본 객체를 조작 할 수 있습니다.

Java가 객체를 생성하고 저장하는 방법 : 객체 를 생성 할 때 객체의 주소를 참조 변수에 저장합니다. 다음 진술을 분석해 봅시다.

Account account1 = new Account();

“Account account1”은 참조 변수의 유형 및 이름이고“=”는 할당 연산자이며“new”는 시스템에서 필요한 공간을 요청합니다. 객체를 생성하는 new 키워드 오른쪽의 생성자는 new 키워드에 의해 암시 적으로 호출됩니다. 할당 된 연산자 ( "클래스 인스턴스 생성 표현식"이라는 표현식 인 올바른 값의 결과)의 주소는 assign 연산자를 사용하여 왼쪽 값 (이름과 유형이 지정된 참조 변수)에 할당됩니다.

객체의 참조는 값으로 전달되지만 메소드는 객체의 참조 사본을 사용하여 공용 메소드를 호출하여 참조 된 객체와 상호 작용할 수 있습니다. 매개 변수에 저장된 참조는 인수로 전달 된 참조의 사본이므로 호출 된 메소드의 매개 변수 및 호출 메소드의 인수는 메모리의 동일한 오브젝트를 참조합니다.

배열 객체 자체 대신 배열에 대한 참조를 전달하면 성능상의 이유로 의미가 있습니다. Java의 모든 것이 값으로 전달되므로 배열 객체가 전달되면 각 요소의 사본이 전달됩니다. 대형 어레이의 경우 시간이 낭비되고 요소 사본을 저장하는 데 상당한 스토리지가 소비됩니다.

아래 이미지에서 우리는 두 가지 참조 변수가 있음을 알 수 있습니다 (C / C ++에서는 포인터라고 불리우며이 용어는이 기능을 이해하기가 더 쉽다고 생각합니다). 기본 변수와 참조 변수는 스택 메모리에 저장됩니다 (아래 이미지의 왼쪽). array1 및 array2 참조 변수 "point"(C / C ++ 프로그래머가 호출 할 때) 또는 a 및 b 배열 각각에 대한 힙 메모리의 오브젝트 (이러한 참조 변수가 보유한 값은 오브젝트의 주소 임) (아래 이미지의 오른쪽) .

값별 예제 1로 전달

array1 참조 변수의 값을 reverseArray 메소드의 인수로 전달하면 참조 변수가 메소드에 작성되고 해당 참조 변수가 동일한 배열 (a)을 가리 키기 시작합니다.

public class Test
{
    public static void reverseArray(int[] array1)
    {
        // ...
    }

    public static void main(String[] args)
    {
        int[] array1 = { 1, 10, -7 };
        int[] array2 = { 5, -190, 0 };

        reverseArray(array1);
    }
}

값별 예제 2로 전달

우리가 말하면

array1[0] = 5;

reverseArray 메서드에서는 배열 a를 변경합니다.

우리는 array를 가리키는 reverseArray 메소드 (array2)에 또 다른 참조 변수를 가지고 있습니다. c. 우리가 말하면

array1 = array2;

reverseArray 메서드에서 reverseArray 메서드의 참조 변수 array1은 배열 a를 가리 키지 않고 배열 c (두 번째 이미지의 점선)를 가리 키기 시작합니다.

참조 변수 array2의 값을 reverseArray 메서드의 반환 값으로 반환하고이 값을 main 메서드의 참조 변수 array1에 할당하면 main의 array1이 배열 c를 가리 키기 시작합니다.

이제 한 번에 한 모든 일을 씁시다.

public class Test
{
    public static int[] reverseArray(int[] array1)
    {
        int[] array2 = { -7, 0, -1 };

        array1[0] = 5; // array a becomes 5, 10, -7

        array1 = array2; /* array1 of reverseArray starts
          pointing to c instead of a (not shown in image below) */
        return array2;
    }

    public static void main(String[] args)
    {
        int[] array1 = { 1, 10, -7 };
        int[] array2 = { 5, -190, 0 };

        array1 = reverseArray(array1); /* array1 of 
         main starts pointing to c instead of a */
    }
}

여기에 이미지 설명을 입력하십시오

이제 reverseArray 메서드가 끝났으므로 참조 변수 (array1 및 array2)가 사라졌습니다. 즉, 이제 주 메서드 array1 및 array2에는 각각 c 및 b 배열을 가리키는 두 개의 참조 변수 만 있습니다. 참조 변수가 객체 (배열)를 가리 키지 않습니다. 따라서 가비지 수집에 적합합니다.

main의 array2 값을 array1에 할당 할 수도 있습니다. array1은 b를 가리 키기 시작합니다.


27

나는에 대한 질문이 종류에 전념 스레드 생성 한 모든 프로그래밍 언어 여기를 .

자바도 언급되어있다 . 다음은 간단한 요약입니다.

  • Java는 값으로 매개 변수를 전달합니다.
  • "값별"은 Java에서 매개 변수를 메소드에 전달하는 유일한 방법입니다.
  • 매개 변수로 제공된 오브젝트의 메소드를 사용하면 참조가 원래 오브젝트를 가리키는대로 오브젝트가 변경됩니다. (그 방법 자체가 일부 값을 변경하는 경우)

27

간단히 말해 Java 객체에는 매우 독특한 속성이 있습니다.

일반적으로, 자바 (기본 유형이 int, bool, char, double값으로 직접 전달되는 등). 그런 다음 Java에는 객체 (에서 파생되는 모든 것 java.lang.Object)가 있습니다. 실제로 객체는 항상 참조 (터치 할 수없는 포인터 인 참조)를 통해 처리됩니다. 즉, 참조가 일반적으로 흥미롭지 않기 때문에 실제로 객체는 참조로 전달됩니다. 그러나 참조 자체가 값으로 전달 될 때 가리키는 오브젝트를 변경할 수 없음을 의미합니다.

이게 이상하고 혼란스럽게 들립니까? C 구현이 참조로 전달하고 값으로 전달하는 방법을 고려해 봅시다. C에서 기본 규칙은 값으로 전달됩니다. void foo(int x)값으로 int를 전달합니다. void foo(int *x)는 원하지 int a않지만 int :에 대한 포인터 foo(&a)입니다. &변수 주소를 전달하기 위해 연산자 와 함께 이것을 사용 합니다.

이것을 C ++로 가져 가면 참조가 있습니다. 참조는 기본적으로 (이 문맥에서) 방정식의 포인터 부분을 숨기는 구문 설탕입니다. void foo(int &x)에 의해 호출됩니다 foo(a). 여기서 컴파일러 자체는 참조임을 알고 있고 비 참조의 주소를 a전달해야합니다. Java에서 객체를 참조하는 모든 변수는 실제로 참조 유형이므로 C ++와 같이 세밀한 제어 (및 복잡성)없이 대부분의 의도와 목적을 위해 참조로 호출해야합니다.


Java 객체와 그 참조에 대한 나의 이해에 매우 가깝다고 생각합니다. Java의 오브젝트는 참조 사본 (또는 별명)에 의해 메소드에 전달됩니다.
MaxZoom
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.