java 아규먼트 자바는 "참조에 의한 전달"인가 "가치에 의한 전달"인가?




자바 아규먼트 (24)

나는 항상 자바가 참조에 의한 것이라고 생각했다.

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

나는 그들이하는 구별을 이해하지 못한다고 생각한다.

설명은 무엇입니까?


긴 이야기를 짧게하기 위해 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는 참조로 값을 전달합니다.

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


아무도 Barbara Liskov를 아직 언급하지 않았다는 것을 나는 믿을 수 없다.그녀는 1974 년 CLU를 설계 할 때, 그녀는이 같은 용어의 문제로 달리고, 그녀는 용어 발명 공유하여 전화를 (또한으로 알려진 객체 공유하여 전화객체에 의해 호출 값이고 값으로 호출 "이 특정 사건에 대한) 참조 ".


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

때로는 자바에서 같은 패턴을 사용하고 싶지만 그렇게 할 수는 없습니다. 적어도 직접적으로. 대신 다음과 같이 할 수 있습니다.

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에서 사용할 수 없습니다.


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

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는 내장 유형에 대한 값과 오브젝트 유형에 대한 포인터 값의 두 가지 유형 만 전달합니다.


Java는 가치에 의한 호출입니다.

작동 원리

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

  • 원시 데이터 형식 인 경우 이러한 비트는 원시 데이터 형식 자체의 값을 포함하므로 메서드 내부의 header 값을 변경하면 변경 내용이 외부에 반영되지 않습니다.

  • Foo foo = new Foo () 와 같은 객체 데이터 유형 이면 객체의 주소 사본이 파일 바로 가기처럼 전달 됩니다. C : \ desktop에 텍스트 파일 abc.txt 가 있다고 가정하고 다음과 같이 단축키를 만든다고 가정합니다. C : \ desktop \ abc.txt 에서 파일에 액세스하고 ''라고 쓰고 파일을 닫은 다음 바로 가기에서 파일을 다시 열 때이 파일을 C : \ desktop \ abc- 바로 가기 안에 넣으십시오 . 쓰기 '는 프로그래머가 배울 수있는 가장 큰 온라인 커뮤니티' 총 파일 변경이 될 것입니다 '스택 오버플로는 프로그래머가 배우기에 가장 큰 온라인 커뮤니티입니다.어떤이, 우리가 추측 할 수는 파일, 우리가 같은 파일에 액세스하고 때마다 열 곳에서 중요하지 않습니다 의미 에 저장된 파일 및 가정하자 foo는 같은 123hd7h (같은 원래 주소 C : 바탕 화면 \의 abc.txt \ ) 주소 및 234jdid ( C : \ desktop \ abc-shortcut 같은 복사 된 주소) 실제로 .. 파일의 원래 주소가 들어 있습니다.) 그래서 더 나은 이해를 위해 바로 가기 파일을 만들고 느낌 ...


내가 원래 포스터와 같은 인상을 받았던 것처럼 구별 또는 기억하는 방식은 다음과 같습니다. Java는 항상 가치에 의해 전달됩니다. Java의 모든 객체 (Java에서는 기본 요소를 제외한 모든 객체)는 참조입니다. 이러한 참조는 값으로 전달됩니다.


자바에서는 모든 것이 참조이므로, Point pnt1 = new Point(0,0);자바가 다음과 같은 것을 할 때 :

  1. 새 Point 객체를 만듭니다.
  2. 새 Point 참조를 만들고 이전에 작성한 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

이 라인에서 'pass-by-value'가 연극에 들어갑니다.

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;

여기에서 먼저 새 tempReference를 작성하여 참조 와 같은 장소를 가리 킵니다arg1 . 그럼 당신은 참조를 이동 arg1하는 지적 처럼 같은 장소에 arg2대한 참조입니다. 마지막 arg2됩니다 지적 처럼 같은 장소에 temp.

여기에서의 범위 tricky방법은 사라지고 당신은 참조에 더 이상 액세스 할 수 없습니다 : arg1, arg2, temp. 그러나 중요한 점은 '인생'에있을 때이 참조로하는 모든 일이 자신이 가리키는 대상에 영구적으로 영향을 미친다는 것 입니다.

그래서 메소드를 실행 한 후 tricky, 돌아 오면 main다음과 같은 상황이 발생합니다 :

이제 프로그램의 완전 실행은 다음과 같습니다.

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

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

Java 인자는 모두 값에 의해 전달됩니다 (참조는 메소드에서 사용될 때 복사됩니다).

원시적 형의 경우, Java의 동작은 간단합니다. 값은 원시 형의 다른 인스턴스에 카피됩니다.

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

이 동작은 기본 유형과 다르게 나타날 수 있습니다. 복사 된 object-variable에 같은 주소가 포함되어 있기 때문에 (동일한 객체에) 객체의 내용 / 멤버 가 메서드 내에서 수정되어 나중에 외부에 액세스되어 Object 그 자체가 참조로 전달되었습니다.

"String"객체 는 "객체가 참조로 전달됨"이라는 도시 전설 의 완벽한 반 대용 사례입니다 .

결과적으로 메서드 내에서 결코 인수로 전달 된 String의 값을 업데이트 할 수 없습니다.

String 객체는 final로 선언 된 배열로 문자를 포함 하며 수정할 수 없습니다. Object의 주소 만 "new"를 사용하여 다른 주소로 대체 ​​될 수 있습니다. "new"를 사용하여 변수를 업데이트하면 변수가 초기에 값으로 전달되어 복사되므로 외부에서 Object에 액세스 할 수 없습니다.


나는 "pass-by-reference 대 pass-by-value"가 매우 도움이되지 않는다고 주장한다.

"Java는 pass-by-whatever (참조 / 값)입니다."라고 말하면, 어느 경우에도 완전한 대답을 제공하지 못합니다. 다음은 메모리에서 일어나는 일을 이해하는 데 도움이되는 몇 가지 추가 정보입니다.

Java 구현을 시작하기 전에 스택 / 힙에 대한 과정을 크래시합니다. 값은 카페테리아에있는 여러 개의 플레이트와 같이 정돈 된 방식으로 스택에서 이동합니다. 힙 (동적 메모리라고도 함)의 메모리는 무의미하고 혼란 스럽습니다. JVM은 가능한 한 공간을 찾고 사용하는 변수가 더 이상 필요하지 않으므로 공간을 확보합니다.

괜찮아. 먼저 로컬 프리미티브가 스택에 저장됩니다. 그래서이 코드 :

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

결과는 다음과 같습니다.

개체를 선언하고 인스턴스화 할 때. 실제 오브젝트는 힙을 사용합니다. 스택에 어떤 변화가 있습니까? 힙에있는 개체의 주소입니다. C ++ 프로그래머는이를 포인터라고 부르지 만 일부 Java 개발자는 "포인터"라는 단어에 반대합니다. 도대체 무엇이. 개체의 주소가 스택에 있음을 알면됩니다.

이렇게 :

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

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

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 주어진다. 두 번째 String의 주소가 첫 번째 문자열과 동일하기 때문에 새로운 String이 생성되지 않는다. 새로운 힙 공간은 할당되지 않지만 스택에 새로운 식별자가 생성됩니다. 그런 다음 shout() 을 호출합니다. 새 스택 프레임이 만들어지고 새 식별자 인 name 이 만들어지고 이미있는 String의 주소가 지정됩니다.

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


네 가지 예를 통해 나의 이해를 설명하려고 노력하겠습니다. 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 '느낌을 가지고있다.

어떤 사람들은 원시 타입과 'String'은 '값으로 전달'이고 객체는 '참조로 전달'이라고 말합니다.

그러나이 예에서 우리는 값으로 전달하는 것이 사실임을 이해할 수 있습니다. 여기에서 참조를 값으로 전달한다는 것을 명심하십시오. ie : 참조는 값으로 전달됩니다. 그것이 변화 할 수있는 이유이며, 여전히 지역 범위 이후에도 적용됩니다. 그러나 원래의 범위를 벗어난 실제 참조는 변경할 수 없습니다. 이것이 의미하는 바는 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]

Java는 항상 예외없이 예외없이 전달됩니다.

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

그래서, 메소드를 호출 할 때

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

따라서 doSomething(foo)public void doSomething(Foo foo) { .. } 두 개의 Foos가 같은 객체를 가리키는 참조 를 복사했습니다.

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


많은 사람들이 이전에 언급했듯이 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의 참조를 변경하려는 경우 로컬 범위에서만 변경되며이 방법 외부에서는 변경되지 않습니다.


아니요, 참고로 통과하지 않습니다.

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

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


나는 당신이 나의 기사 를 참조하는 것을 보았습니다.

Java Spec은 Java의 모든 것이 가치에 의해 전달된다고 말합니다. 자바에서는 "참조에 의한 통과"와 같은 것이 없다.

이것을 이해하는 열쇠는

Dog myDog;

개가 아닙니다 . 실제로는 Dog에 대한 포인터 입니다.

그것이 의미하는 바는 당신이 가질 때입니다.

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

기본적으로 생성 된 Dog 객체의 주소foo 메소드에 전달합니다.

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

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 (주소 42의 Dog 객체)
    • Dog (42 번지에있는 개)는 그의 이름을 맥스로 바꿀 것을 요청받습니다.
  • 라인 "BBB"
    • 새로운 Dog 만들어집니다. 그가 주소 74에 있다고 가정 해 봅시다.
    • 파라미터 someDog 를 74에 대입합니다.
  • 라인 "CCC"
    • someDog가 가리키는 Dog (주소 74의 Dog 객체)
    • Dog (주소 74에있는 Dog )는 그의 이름을 Rowlf로 바꿀 것을 요청받습니다.
  • 그 다음, 우리는 돌아 간다.

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

myDog 바뀌 었습니까?

열쇠가 있습니다.

myDog 는 실제 Dog 가 아니라 포인터 임을 명심하십시오. 대답은 NO입니다. myDog 의 값은 여전히 ​​42입니다. 여전히 원래 Dog 가리키고 있습니다 (단, 줄 "AAA"로 인해 그 이름은 "Max"임 - 여전히 동일한 Dog, myDog 의 값은 변경되지 않았습니다).

그것은 주소를 따르고 그것의 끝에 무엇이 바뀌는 것이 완벽하게 유효합니다; 그러나 변수를 변경하지는 않습니다.

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

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

Java가 pass-by-reference 의미론을 가지고 있다면 위에서 정의한 foo 메소드는 myDog 이 BBB 라인에서 someDog 를 할당 할 때 가리키고있는 곳에서 변경되었을 것입니다.

전달 된 변수의 별칭으로 참조 매개 변수를 생각해보십시오. 별칭이 할당되면 전달 된 변수도 할당됩니다.


일부 게시물에 약간의 수정.

C는 참조에 의한 전달을 지원하지 않습니다. 그것은 항상 가치에 의해 전달됩니다. C ++은 참조로 전달을 지원하지만 기본값은 아니며 매우 위험합니다.

Java에서 값이 무엇인지는 중요하지 않습니다. 객체의 원시 또는 주소 (대략), 항상 값으로 전달됩니다.

Java 객체가 참조로 전달되는 것처럼 "동작"하는 경우, 이는 변경 가능성의 속성이며 메커니즘 전달과는 전혀 관련이 없습니다.

왜 이렇게 많은 혼란 스러울 지 모르겠다. 아마도 많은 자바 프로그래머들이 공식적으로 훈련받지 못했고 실제로 메모리에서 진행되고있는 것을 이해하지 못했을 것이다.


문제의 핵심은 단어이다 참조 표현 "참조에 의해 전달"에서이 단어의 일반적인 의미는 전혀 다른 것을 의미 참조 자바를.

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


사양에 대한 자세한 내용을 추가하려면이 답변을 제공해야한다고 생각했습니다.

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

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

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

또는 위키 피 디아에서, 참조 전달에 관한 주제

참조 별 호출 (call-by-reference) 평가 (참조 전달)는 함수가 값의 복사본이 아닌 인수로 사용 된 변수에 대한 암시 적 참조를받습니다. 이것은 일반적으로 함수가 인수로 사용 된 변수 (호출자가 볼 수있는)를 수정 (즉, 할당) 할 수 있음을 의미합니다.

그리고 가치에 의해 지나치다는 주제로

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

둘째, 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 Virtual Machine Specification에도 정의되어 here . 유형의 값은 해당 정수 또는 부동 소수점 값으로 적절하게 인코딩됩니다 (8, 16, 32, 64 등의 비트).


Java는 값에 따라 오브젝트에 대한 참조를 전달합니다.


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

example 통해 설명해 드리겠습니다.

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. Foo 유형의 f 라는 참조를 선언하고이를 "f" 속성을 가진 Foo 유형의 새 객체에 지정합니다.

    Foo f = new Foo("f");
    

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

    public static void changeReference(Foo a)
    

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

    changeReference(f);
    

  4. Foo 유형의 b 라는 참조를 선언하고이를 "b" 속성을 가진 Foo 유형의 새 객체에 지정합니다.

    Foo b = new Foo("b");
    

  5. a = b 는 속성이 "b" 인 오브젝트에 NOT f 라는 참조를 다시 지정합니다.

  6. modifyReference(Foo c) 메서드를 호출하면 참조 인 c 가 생성되고 특성 "f" 가있는 객체에 할당됩니다.

  7. c.setAttribute("c"); c 가리키는 객체의 속성을 변경하고, f 가리키는 객체와 동일한 객체를 변경합니다.

자바에서 인자를 객체로 전달하는 방법을 이해했으면 좋겠습니다. :)


이렇게하면 Java가 실제로 어떻게 동작하는지에 대한 통찰력을 얻을 수 있습니다. 자바에 대한 다음 논의에서 참조로 전달하거나 값으로 전달하면 웃을 것입니다 .-)

1 단계는 'p'로 시작하는 단어를 지우십시오. "_ _ _ _ _ _ _"특히 다른 프로그래밍 언어에서 온 경우 자바와 'p'는 같은 책, 포럼 또는 심지어 txt로 쓰여질 수 없습니다.

두 번째 단계는 Object를 메서드로 전달할 때 Object 자체가 아니라 Object 참조를 전달한다는 것을 기억하십시오.

  • Student : Master, Java가 참조로 전달된다는 의미입니까?
  • 마스터 : 메뚜기, 아니.

이제 개체의 참조 / 변수가하는 일을 생각해보십시오.

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

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

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 객체는 # 2 행에서 생성되고 메모리에 저장되며, 변수 person 은 Person 객체에 대한 참조를받습니다. 즉, 그 주소입니다. 3bad086a라고 가정 해 봅시다.
  • Object의 주소를 포함하는 변수 person 은 라인 # 3의 함수에 전달됩니다.
  • 4 번 줄에서는 침묵의 소리를들을 수 있습니다.
  • 5 번 줄에 주석 달기
  • 메서드 로컬 변수 인 anotherReferenceToTheSamePersonObject 가 만들어지고 그 다음에는 # 6 행의 마법 이옵니다 .
    • 변수 / 참조 은 비트 단위로 복사되고 함수 내에서 anotherReferenceToTheSamePersonObject에 전달됩니다.
    • Person의 새 인스턴스는 작성되지 않습니다.
    • " person "과 " anotherReferenceToTheSamePersonObject "는 모두 3bad086a 와 동일한 값을 유지합니다.
    • 이 것을 시도하지 말고 person == anotherReferenceToTheSamePersonObject가 true가됩니다.
    • 두 변수 모두 참조에 대해 동일한 사본을 가지고 있으며 둘 다 동일한 개인 오브젝트, 힙의 SAME 오브젝트 및 사본이 아 U니다.

그림은 천 단어의 가치가있다 :

다른 ReferencesToTheSamePersonObject 화살표는 오브젝트쪽으로 향하고 가변 사람쪽으로 향하지 않습니다!

당신이 그것을 얻지 못한다면 나는 단지 나를 신뢰하고 자바가 가치에 의한 것임을 말하는 것이 낫다는 것을 기억하십시오. 음, 기준값으로 전달하십시오 . 오, 더 나은 것은 패스 바이 바이 변이 가치입니다! ;)

이제 나를 미워하되,이 점을 고려할 때 메소드 인자에 대해 말할 때 원시 데이터 유형과 객체를 전달하는 것 사이에는 차이가 없다는 점에 유의하십시오.

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

  • 기본 데이터 형식 인 경우이 비트는 기본 데이터 형식 자체의 값을 포함합니다.
  • 오브젝트의 경우, 비트는 JVM에 오브젝트에 도달하는 방법을 알려주는 주소 값을 포함합니다.

Java는 메서드 내에서 참조 된 Object를 원하는만큼 수정할 수 있기 때문에 값으로 전달됩니다. 그러나 아무리 노력해도 참조 된 변수를 수정할 수 없으므로 (p _ _ _가 아닌) _ _ _ _) 같은 개체 상관없이!

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

당연히 당신은 그것을 짧게자를 수 있고 자바가 가치에 의한 것이라고 말할 수 있습니다 !


내가 아는 한, 자바는 가치에 의한 호출만을 알고있다. 즉, 원시 데이터 유형의 경우 사본으로 작업하고 객체에 대해서는 객체에 대한 참조 복사본으로 작업합니다. 그러나 나는 몇몇 함정이 있다고 생각한다; 예를 들어,이 작동하지 않습니다 :

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

스왑 함수에서는 메인의 참조에 영향을주지 않는 copys를 사용하기 때문에 Hello World가 아닌 World Hello가 채워집니다. 그러나 객체가 변경되지 않는 경우 다음과 같이 변경할 수 있습니다.

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를 사용하는 이유라고 생각한다.


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 값이 같다고 말했기 때문에 0x100234t와 f는 같은 값을 가지므로 동일한 객체를 가리킬 것입니다.

참조를 사용하여 함수에서 아무 것도 변경하지 않으면 객체의 기존 내용을 수정합니다. 그래서 우리는 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아래와 같이 a를 throw 할 수 있습니다.

잘하면이 도움이됩니다.


나는 here어떤 프로그래밍 언어 에 대해서도 이런 종류의 질문에 헌신적 인 글을 남겼다 .here

Java도 언급 됩니다. 요약은 다음과 같습니다.

  • Java는 값에 따라 매개 변수를 전달합니다.
  • "값에 의한"매개 변수를 메서드에 전달하는 유일한 방법입니다
  • 매개 변수로 주어진 객체의 메서드를 사용하면 참조가 원래 객체를 가리키는 것처럼 객체가 변경됩니다. (메소드 자체가 일부 값을 변경하는 경우)






pass-by-value