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





pass by (25)


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

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

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

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

설명은 무엇입니까?




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

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

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

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

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

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




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




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




저는 항상 이것을 "사본으로 전달"이라고 생각합니다. 프리미티브 또는 참조 값의 사본입니다. 원시적 인 경우는 값인 비트의 복사본이며 Object 인 경우 참조의 복사본입니다.

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의 출력 :

name = Maxx
name = Fido

프리미티브 래퍼 클래스와 문자열은 변경할 수 없으므로 이러한 유형을 사용하는 모든 예제는 다른 유형 / 객체와 동일하게 작동하지 않습니다.




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

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 를 할당 할 때 가리키고있는 곳에서 변경되었을 것입니다.

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




자바는 항상 가치에 의해 전달됩니다 . 불행하게도, 그들은 물체의 위치를 ​​"참조"라고 부르기로 결정했다. 객체의 값을 전달할 때 참조 를 전달합니다. 이것은 초보자에게 혼란 스럽습니다.

그것은 이렇게 간다.

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    // 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 
}

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 내의 값 aDog 는 객체 참조가 값에 의해 전달 될 때 Dog "Fifi" 함수 foo 에서 변경되지 않습니다. 참조에 의해 전달 된 경우 main 있는 aDog.getName()foo 호출 한 후 "Fifi" 반환합니다.

마찬가지로:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    foo(aDog);
    // when foo(...) returns, the name of the dog has been changed to "Fifi"
    aDog.getName().equals("Fifi"); // true
}

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

위의 예에서 Fififoo(aDog) 호출 한 후 dog의 이름입니다. 객체의 이름이 foo(...) 내부에 설정 되었기 때문입니다. food 수행하는 연산은 모든 실제적인 목적을 위해 aDog 자체에서 수행됩니다 ( dd = new Dog("Boxer") 와 같은 다른 Dog 인스턴스를 가리 키도록 변경된 경우 제외).




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

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

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

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는 매개 변수를 VALUE 및 값 전용으로 전달 합니다.

짧은 이야기를 짧게 자르기 :

C #에서 오는 경우 : "out"매개 변수가 없습니다.

파스칼에서 오는 사람들을 위해 : "var"매개 변수가 없습니다 .

즉, 객체 자체에서 참조를 변경할 수는 없지만 객체의 속성은 언제든지 변경할 수 있습니다.

해결 방법은 StringBuilder매개 변수를 대신 사용하는 것 String입니다. 그리고 항상 배열을 사용할 수 있습니다!




이렇게하면 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은 사람이 다른 사람을 참조하도록 만들 수 없습니다.

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




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

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

참조로 전달한다는 것은 호출 된 함수의 매개 변수가 호출자가 전달한 인수 (값이 아니라 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 등의 비트).




참조는 표현할 때 항상 사용하는 언어와 상관없이 값입니다.

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

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

핵심에 이르기까지 모든 언어의 표현을 표현하지 않고 참조를 전달하는 것은 기술적으로 불가능합니다 (즉시 값이 될 때).

변수 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 유형에 따라 달라집니다. 이는 참조 취소 프로세스를 우회 할 때까지 개발자에게 표시되지 않습니다. 따라서 참조 A는 가치 기준은 (언어 레벨에서) 처리 할 수있는 값이므로 표현.

이제 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예상했던 것보다 다른 메모리에 쓸 것이기 때문에 당신은 프로그램의 실행을 푸시 (아마도 segfault) 할 것이고, 당신은 실행 가능을 유지할 운명의 영역을 수정할 수도있다. 프로그램을 작성하고 실행하면 실행중인 코드가 수정됩니다 (Foo는 현재 실행되지 않습니다 47). 하지만 Foo의 가치47전역 적으로 변경되지 않았으며, 메소드 내부의 47사본 이기 때문에 메소드 내부의 메소드 만 변경 되었습니다.
  • case 5.와 6. 만약 당신 223이 메소드 내에서 수정하면 3. 나 4.와 같은 문제를 일으킨다. (포인터가 다시 나쁜 값을 가리키며 다시 포인터로 사용된다.)하지만 여전히 로컬이다. 문제는 223이 복사 된 것 입니다. 당신이 역 참조 할 수있다 그러나 경우 Ref2Foo(즉 223)에 도달하고 뾰족한 값을 수정 47, 말,하는 49, 그것은 푸에 영향을 미칠 것입니다 세계적으로 이 경우에는 방법의 사본을 가지고 있기 때문에, 223하지만, 참조는 47한 번만 존재하고 있음을 변경 to 49가 모든 Ref2Foo이중 참조 해제를 잘못된 값으로 유도 합니다.

중요하지 않은 세부 사항, 심지어 참조로 전달되는 언어조차도 값을 함수로 전달하지만 함수는 역 참조를 위해 값을 사용해야한다는 것을 알고 있습니다. 이 pass-the-reference-as-value는 실제적으로 쓸모가 없으므로 프로그래머가 숨길 뿐이며 전문 용어는 참조별로 전달됩니다 .

엄격한 pass-by-value 도 쓸모가 없습니다. 배열을 인수로 사용하여 메소드를 호출 할 때마다 100MB의 배열을 복사해야하므로 엄격하게 Java가 값으로 전달 될 수 없습니다. 모든 언어는이 거대한 배열에 대한 참조를 (값으로) 전달할 것이고 그 배열이 메소드 내에서 로컬로 변경 될 수 있거나 (자바처럼) 메소드가 전역으로 배열을 수정할 수있게한다면 copy-on-write 메커니즘을 사용한다. 호출자의 견해) 및 몇 가지 언어로 참조 자체의 값을 수정할 수 있습니다.

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




대조를 보여주기 위해 다음 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는 항상 인수를 참조가 아닌 값으로 전달합니다.

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 가리키는 객체와 동일한 객체를 변경합니다.

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




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

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

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

"Hah!" 가 인쇄됩니다 "Hah!" null 대신. 그 이유는 barbaz 의 값의 복사본이기 때문입니다.이 값은 "Hah!" 대한 참조 일뿐 "Hah!" . 이것이 실제 참조 자체라면 foobaznull 재정의했을 것입니다.




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

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




나는 "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는 값으로 전달합니다. 이것을 검증하는 아주 간단한 예입니다.

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



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

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

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



긴 이야기를 짧게하기 위해 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가 참조로 패스의 환상을 줄 수 있습니다. 아래 예를 사용하여 작동 원리를 알아 보겠습니다.

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 할 수 있습니다.

잘하면이 도움이됩니다.




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

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

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

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

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

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

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

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




예를 들어 다음과 같은 두 가지 방법이 있습니다.

Integer x = Integer.valueOf(str);
// or
int y = Integer.parseInt(str);

이러한 방법에는 약간의 차이가 있습니다.

  • valueOfjava.lang.Integer 의 새 인스턴스 또는 캐시 된 인스턴스를 반환합니다.
  • parseInt 는 원시적 인 int .

Short.valueOf / parseShort , Short.valueOf / parseLong 등 모든 경우에 동일하게 적용됩니다.







java methods parameter-passing pass-by-reference pass-by-value