Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/356.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java是“按引用传递”还是“按值传递”?_Java_Methods_Parameter Passing_Pass By Reference_Pass By Value - Fatal编程技术网

Java是“按引用传递”还是“按值传递”?

Java是“按引用传递”还是“按值传递”?,java,methods,parameter-passing,pass-by-reference,pass-by-value,Java,Methods,Parameter Passing,Pass By Reference,Pass By Value,我一直认为Java使用按引用传递 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); chan

我一直认为Java使用按引用传递

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的博客文章,说Java使用传递值

我想我不明白他们的区别


解释是什么?

Java总是按值传递,从来没有例外

那么,怎么会有人对此感到困惑,认为Java是通过引用传递的,或者认为他们有一个Java作为通过引用传递的例子呢?关键的一点是,Java在任何情况下都不提供对对象本身值的直接访问。对对象的唯一访问是通过对该对象的引用。因为Java对象总是通过引用来访问,而不是直接访问,所以通常将字段、变量和方法参数称为对象,而从字面上讲,它们只是对对象的引用。这种混淆源于这种严格意义上的不正确的命名变化

因此,在调用方法时

对于基元参数int、long等,传递值是基元的实际值,例如3。 对于对象,传递值是对对象的引用的值。 因此,如果有doSomethingfoo和public void doSomethingfoo foo{..},那么这两个foo复制了指向相同对象的引用


当然,按值传递对对象的引用看起来很像,在实践中与按引用传递对象没有区别。

Java按值传递引用

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

因此,您无法更改传入的引用。

基本上,重新指定对象参数不会影响参数,例如

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

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

会打印出来的哈!而不是null。之所以这样做,是因为bar是baz值的副本,它只是对Hah!的引用!。如果它是实际的引用本身,那么foo会将baz重新定义为null。

Java将引用按值传递给对象。

Java总是按值传递。不幸的是,当我们处理对象时,我们实际上是在处理被称为引用的对象句柄,这些句柄也是通过值传递的。这种术语和语义很容易使许多初学者感到困惑

事情是这样的:

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
}
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]);
}
在上面的示例中,aDog.getName仍将返回Max。当对象引用按值传递时,函数foo中的值aDog在main中不会随Dog fififi而改变。如果它是通过引用传递的,那么main中的aDog.getName将在调用foo后返回Fifi

同样地:

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是调用fooaDog后狗的名字,因为对象的名字是在foo内设置的。。。。foo对d执行的任何操作都是在aDog上执行的,但是不可能更改变量aDog本身的值。

长话短说,对象具有一些非常特殊的属性

一般来说,Java有int、bool、char、double等基本类型,它们直接通过值传递。然后,Java拥有从Java.lang.Object派生的所有对象。对象实际上总是通过一个引用来处理的,引用是一个你不能触摸的指针。这意味着实际上,对象是通过引用传递的,因为引用通常不感兴趣。但是,这确实意味着您不能更改指向哪个对象,因为引用本身是通过值传递的

这听起来是不是既奇怪又令人困惑?让我们考虑一下C是如何实现按引用传递和按值传递的。在C语言中,默认约定是按值传递。void fooint x通过值传递一个int。void fooint*x是一个函数,它不需要int a,而是指向int:foo&a的指针。可以将其与&运算符一起使用来传递变量地址


把这个带到C++,我们有参考文献。在这个上下文中,引用基本上是隐藏表达式指针部分的语法糖:void fooint&x由fooa调用,其中编译器本身知道它是一个引用,应该传递非引用a的地址。在java中,所有引用对象的变量实际上都是引用类型,实际上,通过引用来强制调用大多数目的和目的,而没有细粒度的控制和复杂性,例如C++,

或者,也许我记得,我曾经和最初的海报有着相同的印象,那就是:Java总是按值传递。Java中的所有对象,除原语外的任何对象都是引用。这些引用是按值传递的。

正如许多人之前提到的

下面是另一个有助于您理解差异的示例:

印刷品:

之前:a=2,b=3 之后:a=2,b=3

这是因为iA和iB是新的局部引用变量,它们分别指向a和b的传递引用的值相同
伊利。因此,尝试更改iA或iB的引用只会在本地范围内更改,而不会超出此方法。

我始终认为这是一个传递副本。它是值的副本,可以是原语或引用。如果它是一个原语,则它是作为值的位的副本;如果它是一个对象,则它是引用的副本

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 名称=Fido


原始包装器类和字符串是不可变的,因此任何使用这些类型的示例都不会与其他类型/对象一样工作。

我为任何编程语言创建了一个专门讨论此类问题的线程

。以下是简短的总结:

Java按值传递它的参数 在java中,按值是将参数传递给方法的唯一方法 使用作为参数提供的对象中的方法将改变 对象作为引用指向的对象 原始对象。如果 方法本身改变了一些值
我刚注意到你提到了

Java规范说Java中的一切都是按值传递的。Java中没有通过引用传递的东西

理解这一点的关键是

Dog myDog;
不是狗;它实际上是一个指向狗的指针。在Java中使用术语reference非常容易引起误解,这也是造成大多数混淆的原因。他们所称的引用行为/感觉更像我们在大多数其他语言中所称的指针

这意味着,当你

Dog myDog = new Dog("Rover");
foo(myDog);
实际上,您将创建的Dog对象的地址传递给foo方法

我之所以这么说,主要是因为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,它指向地址42处的Dog对象 地址42的那条狗被要求把他的名字改成Max 在BBB线 一只新狗诞生了。假设他在74号地址 我们将参数someDog指定给74 在CCC线 someDog被跟踪到Dog,它指向地址74处的Dog对象 地址74的那条狗被要求把名字改成罗夫 然后,我们回来 现在让我们考虑一下在方法之外会发生什么:

我的狗变了吗

这是钥匙

记住myDog是指针,而不是实际的狗,答案是否定的;它仍然指向原来的狗,但请注意,由于线AAA,它的名字现在是马克斯-仍然是相同的狗;myDog的值没有改变

跟随地址并更改地址末尾的内容是完全有效的;但是,这不会改变变量

Java的工作原理与C完全相同。您可以分配一个指针,将指针传递给一个方法,跟随方法中的指针,并更改指向的数据。但是,调用方将看不到您对指针指向的位置所做的任何更改。在具有按引用传递语义的语言中,方法函数可以更改指针,调用方将看到该更改

< >在C++、艾达、Pascal和支持传递引用的其他语言中,实际上可以更改所传递的变量。< /P> 如果Java具有按引用传递语义,那么我们上面定义的foo方法将改变myDog在BBB行上分配someDog时指向的位置


将引用参数视为传入变量的别名。分配别名时,传入的变量也是如此。

为了显示对比度,请比较以下内容和代码段:

在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只有两种类型的传递:通过值传递内置类型,通过指针值传递对象类型。

问题的关键在于表达式pass by reference中的单词reference与Java中单词reference的通常含义完全不同


通常在Java中,引用是指对对象的引用。但是编程语言理论中的技术术语“按引用传递/值”指的是对保存变量的内存单元的引用,这是完全不同的。

在Java中永远不能按引用传递,其中一种显而易见的方法是,当您希望从方法调用返回多个值时。考虑C++中的以下代码:< /P>
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中,您将数组指针作为值传递给getValues。这就足够了,因为该方法随后修改数组元素,按照惯例,您希望元素0包含返回值。显然,您可以通过其他方式来实现这一点,例如构造代码使其不必这样做,或者构造一个可以包含返回值或允许设置返回值的类。
但是,上面的C++中没有的简单模式在java java中是不可用的。

据我所知,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);
}
这将填充Hello World而不是World Hello,因为在交换函数中使用的copy对main中的引用没有影响。但如果对象不是不可变的,则可以更改它,例如:

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);
}
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);
}
这将在命令行上填充Hello World。若将StringBuffer更改为String,它将只生成Hello,因为String是不可变的。例如:

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);
}
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);
}
但是,您可以像这样为字符串创建一个包装器,使其能够与字符串一起使用:

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的原因,因为你可以修改原始对象,而不能修改字符串等不可变对象。

我不相信还没有人提到Barbara Liskov。当她在1974年设计CLU时,她遇到了同样的术语问题,她发明了“通过共享调用”这个术语,也称为“通过对象共享调用”和“通过对象调用”,用于这种特定情况下的“通过值调用”,其中值是一个引用。

这将让您了解Java是如何真正工作的,在下一次讨论Java通过引用传递或通过值传递时,您只需笑一笑:-

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;
    }
}
第一步请把以“p”开头的单词从你的脑海中抹去,特别是如果你来自其他编程语言。Java和“p”不能写在同一本书、论坛甚至txt中

第二步请记住,当您将一个对象传递给一个方法时,您传递的是对象引用,而不是对象本身

学生:大师,这是否意味着Java是通过引用传递的? 主人:蚱蜢,不。 现在想想对象的引用/变量是什么:

变量包含告诉JVM如何到达内存堆中引用对象的位。 向方法传递参数时,传递的不是引用变量,而是引用变量中位的副本。类似这样的东西:3bad086a。3bad086a表示到达传递对象的方法。 所以你只需要传递3bad086a,它就是引用的值。 传递的是引用的值,而不是引用本身,也不是对象。 该值实际上被复制并提供给该方法。 在以下情况下,请不要尝试编译/执行此…:

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。 在第2行中创建了一个新的Person对象,该对象存储在内存中,变量Person被赋予对Person对象的引用。就是它的地址。比如说3bad086a。 持有对象地址的变量person被传递给第3行中的函数。 在第四行你可以听到寂静之声 检查第5行的注释 创建了一个方法局部变量——另一个引用thesamepersonObject——然后在第6行出现了魔力: 变量/reference person被逐位复制,并传递给函数中的另一个ReferenceToTheSamepersonObject。 不会创建Person的新实例。 person对象的person和其他引用都具有相同的3bad086a值。 不要尝试此操作,但person==另一个引用MePersonObject将为true。 这两个变量都有相同的引用副本,它们都引用同一个人对象,堆上的同一对象,而不是副本。 一幅画胜过千言万语:

请注意,另一个引用TheSamepersonObject箭头指向对象,而不是指向变量person

如果您没有得到它,那么请相信我,记住最好说Java是按值传递的。那么,通过参考值传递。哦,更好的是传递变量值的副本

现在请随意讨厌我,但请注意,在讨论方法参数时,传递基本数据类型和对象之间没有区别

您总是传递引用值位的副本

如果是基本数据类型,这些位将包含基本数据类型本身的值。 如果它是一个对象,那么这些位将包含告诉JVM如何到达该对象的地址值。 Java是按值传递的,因为在一个方法中,您可以随意修改被引用的对象,但无论您如何努力,您都无法修改传递的变量,该变量将始终引用同一个对象,而不管发生什么

上面的changeName函数将永远无法修改传递引用的实际内容和位值。换句话说,changeName不能使Person引用另一个对象

当然你可以把它缩短一点
我只是说Java是按值传递的

Java总是按值传递参数,而不是按引用传递参数

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;
    }
}
让我通过一个例子来解释这一点:

公共班机{ 公共静态无效字符串[]args{ Foo f=新的Foo f; changeReferencef;//它不会更改引用! modifyReferencef;//它将修改引用变量f引用的对象! } 公共静态void changeferencefo a{ Foob=新Foob; a=b; } 公共静态void modifyreferencefo c{ c、 setAttributec; } } 我将分步骤解释这一点:

声明名为f的Foo类型的引用,并为其分配一个具有属性f的Foo类型的新对象

在方法方面,声明了一个名为a的Foo类型的引用,并将其初始赋值为null

在调用方法changeReference时,将为引用a分配作为参数传递的对象

changeReference(f);
声明名为b的Foo类型的引用,并为其分配一个具有属性b的Foo类型的新对象

a=b对属性为b的对象的引用a(而不是f)进行新赋值

在调用ModifyReferenceFo c方法时,将创建一个引用c,并为该对象指定属性f

c、 setAttributec;将更改引用c指向它的对象的属性,并且它是引用f指向它的同一个对象


我希望您现在了解了在Java中作为参数传递对象的工作原理:

不,它不是按引用传递

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语言规范,Java是按值传递的:

当调用方法或构造函数§15.12时,实际参数表达式的值会在执行方法或构造函数主体之前初始化新创建的参数变量(每种声明类型)。DeclaratorId中出现的标识符可以用作方法或构造函数主体中的简单名称,以引用


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

我觉得争论通过引用传递还是通过值传递并没有多大帮助

如果您说Java是通过任何引用/值传递的,那么无论哪种情况,您都没有提供完整的答案。这里有一些额外的信息,希望有助于理解内存中发生了什么

在开始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,因为第二个字符串的地址与第一个字符串的地址相同,因此不会创建新字符串,也不会分配新堆空间,但会在堆栈上创建新标识符。然后我们调用shout:创建一个新的堆栈帧,创建一个新的标识符name,并将已经存在的字符串的地址分配给它


那么,价值,参考?你说土豆。

Java总是按值传递,而不是按引用传递

首先,我们需要了解什么是按值传递和按引用传递

通过值传递意味着您正在内存中复制传入的实际参数值。这是实际参数内容的副本

Pass-by-reference也称为Pass-by-address,意思是存储实际参数地址的副本

有时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;
}
该程序的输出为:

让我们一步一步地理解:

Test t = new Test();
正如我们都知道的那样,它会发生变化 在堆中创建一个对象,并将引用值返回给t。例如,假设t的值为0x100234,我们不知道实际的JVM内部值,这只是一个示例

new PassByValue().changeValue(t);
将引用t传递给函数时,它不会直接传递对象测试的实际引用值,但会创建一个t的副本,然后将其传递给函数。因为它是按值传递的,所以它传递的是变量的副本,而不是它的实际引用。因为我们说过t的值是0x100234,所以t和f都将具有相同的值,因此它们将指向相同的对象

如果使用引用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,如下所示:


希望这会有所帮助。

在java中,一切都是参考,所以当您有以下内容时: 点pnt1=新点0,0;Java有以下功能:

创建新的点对象 创建新的点参照,并将该参照初始化为以前创建的点对象上的点参照。 从这里开始,通过点对象生命周期,您将通过pnt1访问该对象 参考所以我们可以说,在Java中,通过对象的引用来操作对象。 Java不通过引用传递方法参数;它按值传递它们。我将使用以下示例:

程序流程:

Point pnt1 = new Point(0,0);
Point pnt2 = new Point(0,0);
创建两个不同的点对象,并关联两个不同的参照。

正如预期的那样,产出将是:

X1: 0     Y1: 0
X2: 0     Y2: 0
在这一行中,“按价值传递”进入游戏

引用pnt1和pnt2通过值传递给这个棘手的方法,这意味着现在您的引用pnt1和pnt2的副本名为arg1和arg2。因此pnt1和arg1指向同一个对象。pnt2和arg2的情况相同

在棘手的方法中:

接下来是棘手的方法

在这里,您首先创建新的临时点引用,它将指向与arg1引用相同的位置。然后将引用arg1移动到与arg2引用相同的位置。 最后,arg2将指向与temp相同的位置

从这里开始,这个棘手的方法的作用域消失了,您不再有权访问这些引用:arg1、arg2、temp。但重要的是,当这些引用处于“生命”状态时,您对它们所做的一切都将永久影响它们所指向的对象

所以,在执行了method-tricky之后,当您返回main时,会出现以下情况:

所以现在,程序的完全执行将是:

X1: 0         Y1: 0
X2: 0         Y2: 0
X1: 100       Y1: 100
X2: 0         Y2: 0
无论使用何种语言,引用在表示时始终是一个值。 为了获得一个开箱即用的视图,让我们看看汇编或一些低级内存管理。在CPU级别,对任何内容的引用如果写入内存或某个CPU寄存器,就会立即成为一个值。这就是为什么指针是一个很好的定义。它是一种价值,同时也有其目的

内存中的数据有一个位置,在这个位置上有一个值字节,字,等等。在汇编中,我们有一个方便的解决方案,为某个位置(也称为变量)命名,但在编译代码时,汇编程序只是用指定的位置替换名称,就像浏览器用IP地址替换域名一样

从本质上讲,在任何语言中,当引用立即成为一个值时,如果不表示它,就不可能传递对它的引用

假设我们有一个变量Foo,它的位置在内存中的第47个字节,它的值是5。我们还有另一个变量Ref2Foo,它在内存中的第223个字节,它的值是47。此Ref2Foo可能是一个技术变量,而不是由程序显式创建的。如果只看5和47而不看任何其他信息,则只会看到两个值。 如果您使用它们作为参考,那么要达到5,我们必须旅行:

(Name)[Location] -> [Value at the Location]
---------------------
(Ref2Foo)[223]  -> 47
(Foo)[47]       -> 5
这就是跳转表的工作原理

如果我们想用Foo的值调用方法/函数/过程,有几种可能的方法将变量传递给方法,具体取决于语言及其几种方法调用模式:

5被复制到一个CPU寄存器,即EAX。 5被推到堆栈中。 47被复制到一个CPU寄存器 47推到堆栈上。 223被复制到一个CPU寄存器。 223被推到堆栈中。 在任何情况下,都会创建一个值(现有值的副本),现在由接收方法来处理它。当您在方法内部编写Foo时,它要么从EAX读取,要么自动取消引用,或者双重取消引用,过程取决于语言的工作方式和/或Foo的类型。这对开发人员是隐藏的,直到她 绕过解引用过程。因此,引用在表示时是一个值,因为引用是一个必须在语言级别处理的值

现在,我们已将Foo传递给该方法:

在案例1中。二,。如果更改Foo-Foo=9,则只会影响本地范围,因为您有该值的副本。从方法内部,我们甚至无法确定原始Foo在内存中的位置。 在案例3中。四,。如果使用默认语言构造并更改Foo Foo=11,则可能会根据语言全局更改Foo,即Java或类似Pascal的过程findMinx,y,z:integer;var m:integer;。但是,如果语言允许您绕过取消引用过程,您可以将47更改为49。在这一点上,如果您阅读Foo,它似乎已经被更改,因为您已经更改了指向它的本地指针。如果您要在Foo=12方法中修改这个Foo,您可能会禁用程序的执行。SEGFULT因为您将写入一个与预期不同的内存,所以您甚至可以修改一个注定要保存可执行程序的区域,写入该区域将修改运行代码Foo现在不在47。但是Foo的值47在全局范围内没有变化,只有方法内部的值,因为47也是方法的副本。 在案例5中。六,。如果在方法内部修改223,则会造成与第3条相同的混乱。或4。一个指针,指向一个现在坏了的值,它再次被用作指针,但这仍然是一个局部问题,因为223被复制了。但是,如果您能够取消引用Ref2Foo,即223,达到并修改指向的值47,比如说,修改为49,它将全局影响Foo,因为在这种情况下,方法获得了223的副本,但引用的47只存在一次,将其更改为49将导致每次Ref2Foo双重取消引用到错误的值。 挑剔无关紧要的细节,即使是按引用传递的语言也会将值传递给函数,但这些函数知道它们必须使用它来解引用。这个传递引用为值的方法对程序员来说是隐藏的,因为它实际上是无用的,而且术语只是传递引用

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;
    }
}
严格的按值传递也是无用的,这意味着每次调用以数组为参数的方法时都必须复制100 MB的数组,因此Java不能严格按值传递。每种语言都会将对这个巨大数组的引用作为一个值传递,如果该数组可以在方法内部本地更改,则使用写时复制机制,或者允许该方法像Java那样从调用方的角度全局修改数组,并且一些语言允许修改引用本身的值


简而言之,用Java自己的术语来说,Java是传递值,值可以是:实值或表示引用的值

我想我应该提供这个答案,以便从规范中添加更多细节

首先,

通过引用传递意味着被调用函数的参数将是 与调用方传递的参数相同的不是值,而是标识 -变量本身

传递值表示被调用函数的参数将是 调用方传递的参数

changeReference(f);
或者来自维基百科

通过引用调用的评估也被称为 通过引用传递,函数接收对 变量用作参数,而不是其值的副本。这 通常意味着功能可以修改,即分配给 变量用作调用者将看到的参数

在“按值调用”中,对参数表达式求值,然后 结果值绑定到函数[…]中的相应变量。 如果函数或过程能够为其 参数,仅为其本地副本分配[…]

其次,我们需要知道Java在方法调用中使用了什么。美国

当调用§15.12中的方法或构造函数时 实际参数表达式初始化新创建的参数 每个声明类型的变量,在执行 方法或构造函数

因此,它将参数的值赋值或绑定到相应的参数变量

这个论点的价值是什么

让我们考虑引用类型,状态

引用类型有三种:类类型、数组类型、, 和接口类型。它们的值是动态引用的 创建的类实例、数组或类实例或数组 分别实现接口

报告还指出

引用值通常只是引用是指向这些对象的指针,以及一个特殊的空引用,它不引用任何对象

某个引用类型的参数的值是指向对象的指针。请注意,一个变量,一个引用类型为返回类型的方法调用 ,以及一个新的实例创建表达式。。。全部解析为引用类型值

所以

将字符串实例引用的值绑定到方法新创建的参数param。这正是传递值定义所描述的。因此,Java是按值传递的

您可以跟随引用来调用方法或访问被引用对象的字段,这一事实与对话完全无关。通过引用传递的定义是

这通常意味着功能可以修改,即分配给 变量用作调用者将看到的参数

在Java中,修改变量意味着重新分配它。在Java中,如果在方法中重新分配变量,调用者将不会注意到它。修改变量引用的对象是一个完全不同的概念


原语值也在Java虚拟机规范中定义。该类型的值是相应的整数或浮点值,适当编码为8、16、32、64等位。

让我通过四个示例来解释我的理解。Java是按值传递的,而不是按引用传递的

/**

传递值

在Java中,所有参数都是按值传递的,即调用方看不到分配方法参数

*/

例1:

结果

例2:

/** * *传递值 * */

结果

例3:

/** 这个“传递值”有一种“传递引用”的感觉

有些人说基本类型和“字符串”是“按值传递” 对象是“按引用传递”

但从这个例子中,我们可以理解它实际上只是传递值, 请记住,这里我们将引用作为值传递。 ie:引用是通过值传递的。 这就是为什么我们能够改变,并且在局部范围之后仍然保持不变。 但我们不能在原始范围之外更改实际引用。 下面的PassByValueObjectCase2示例演示了这意味着什么

*/

结果

例4:

/**

除了示例3 PassByValueObjectCase1.java中提到的内容之外,我们不能在原始范围之外更改实际引用

注意:我不是在为私立班学生粘贴代码。学生的班级定义与例3相同

*/

结果

在Java中,仅传递引用并按值传递:

Java参数都是按值传递的。方法使用时复制引用:

对于基元类型,Java行为很简单: 该值将复制到基元类型的另一个实例中

对于对象,这是相同的: 对象变量是指针桶,只包含使用new关键字创建的对象地址,并像原语类型一样进行复制

该行为可能与基本类型不同:因为复制的对象变量包含同一对象的相同地址。 对象的内容/成员仍可能在方法内修改,然后在外部访问,从而产生包含对象本身通过引用传递的错觉

字符串对象似乎是城市图例的一个很好的反例,说明对象是通过引用传递的:

实际上,使用方法,您将永远无法更新作为参数传递的字符串的值:

字符串对象,由声明为final的数组保存不能修改的字符。 只有对象的地址可以被另一个使用new的地址替换。
使用new更新变量将不允许从外部访问对象,因为变量最初是按值传递并复制的。

Java是按值传递的堆栈内存

工作原理

让我们首先了解java存储基本数据类型和对象数据类型的位置

基本数据类型本身和对象引用存储在堆栈中。 对象本身存储在堆中

这意味着,堆栈内存存储基本数据类型以及 对象的地址

您总是传递引用值位的副本

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;
    }
}
如果是基元数据类型,那么这些复制的位包含基元数据类型本身的值,这就是为什么当我们在方法内部更改参数值时,它不会反映外部的更改

如果它是一个对象数据类型,比如Foo Foo=new Foo,那么在本例中,对象地址的副本会像文件快捷方式一样传递,假设我们在C:\desktop上有一个文本文件abc.txt,假设我们为同一个文件创建了快捷方式,并将其放在C:\desktop\abc快捷方式中,因此当您从C:\desktop\abc.txt访问该文件并写入“堆栈溢出”并关闭该文件,然后再次从快捷方式打开该文件,然后写入“是程序员可以访问的最大在线社区”了解“然后总文件更改将为”Stac k Overflow是程序员需要学习的最大的在线社区,这意味着无论从何处打开文件,每次我们访问同一个文件时,在这里,我们可以假设Foo是一个文件,并假设Foo存储在123hd7hor原始地址,如C:\desktop\abc.txt地址和234jdid复制地址,如C:\desktop\abc快捷方式,其中实际包含文件的原始地址。。 因此,为了更好地理解,请创建快捷方式文件并进行体验


与其他一些语言不同,Java不允许您在按值传递和按引用传递之间进行选择。所有参数都是按值传递的。方法调用可以将两种类型的值传递给方法,即原语值的副本,例如int和double的值以及对象引用的副本

当方法修改基元类型参数时,对该参数的更改对调用方法中的原始参数值没有影响

对于对象,对象本身不能传递给方法。因此,我们传递对象的引用地址。我们可以使用此引用操纵原始对象

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如何创建和存储对象:当我们创建对象时,我们将对象的地址存储在引用变量中。让我们分析一下下面的陈述

Account account1 = new Account();
“Account account1”是引用变量的类型和名称,“=”是赋值运算符,“new”要求系统提供所需的空间量。关键字new右侧创建对象的构造函数被关键字new隐式调用。右值的已创建objectresult的地址(称为类实例创建表达式的表达式)被分配给左值,左值是一个引用变量,具有使用assign运算符指定的名称和类型

尽管对象的引用是按值传递的,但方法仍然可以通过使用对象引用的副本调用其公共方法来与被引用对象交互。由于参数中存储的引用是作为参数传递的引用的副本,因此被调用方法中的参数和调用方法中的参数引用内存中的同一对象

出于性能原因,将引用传递给数组而不是数组对象本身是有意义的。因为Java中的所有内容都是按值传递的,如果传递了数组对象, 将传递每个元素的副本。对于大型阵列,这将浪费时间和资源 元素副本的大量存储空间

在下图中,您可以看到我们有两个引用变量,它们在C/C++中被称为指针,我认为这个术语更容易理解这个特性。在主要方法中。原语和引用变量保存在堆栈内存左侧,如下图所示。array1和array2引用变量分别指向C/C++程序员所称的或对a和b数组的引用,这些数组是这些引用变量所持有的对象值,这些值是堆内存右侧对象的地址,如下图所示

如果我们将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);
    }
}
所以,如果我们说

array1[0] = 5;
在reverseArray方法中,它将在数组a中进行更改

我们在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方法已经结束,它的参考变量ray1和array2已经消失了。这意味着我们现在在主方法array1和array2中只有两个参考变量,它们分别指向c和b数组。没有引用变量指向对象数组a。因此,它符合垃圾收集的条件


您还可以在main中将array2的值分配给array1。array1将开始指向b。

已经有很好的答案涵盖了这一点。我想通过分享一个非常简单的例子来做一个小小的贡献,它将编译对比C++中的按引用和java中的逐值行为。 有几点:

术语reference是一个有两种不同含义的重载。在Java中,它只是指一个指针,但在按引用传递的上下文中,它表示传入的原始变量的句柄。 Java是按值传递的。在其他语言中,Java是C语言的后代。在C之前,FORTRAN和COBOL等一些但并非所有早期语言都支持PBR,但C不支持。PBR allo 我们使用这些其他语言对子程序中传递的变量进行更改。为了完成同样的事情,即改变函数中变量的值,C程序员将变量的指针传递到函数中。受C语言启发的语言,如Java,借鉴了这一思想,并继续像C一样传递指向方法的指针,只是Java调用其指针引用。同样,这是一个不同的使用词的参考比传递参考。 C++允许通过使用与字符相同的字符来声明引用参数,该字符恰好是用来指示C和C++中变量的地址的字符。例如,如果我们通过引用传入指针,那么参数和参数就不仅仅指向同一个对象。相反,它们是相同的变量。如果一个被设置为不同的地址或null,那么另一个也会被设置为null。 在下面的C++示例中,我通过引用传递一个指向空终止字符串的指针。在下面的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>
}
编辑

有些人写了评论,似乎表明他们不是在看我的例子,或者他们没有得到C++的例子。不知道断开的位置,但是猜C++例子并不清楚。我在pascal中发布了相同的示例,因为我认为pascal中的pass-by-reference看起来更干净,但我可能错了。我可能只是让人们更加困惑;我希望不是

在pascal中,通过引用传递的参数称为var参数。在下面的setToNil过程中,请注意参数“ptr”前面的关键字“var”。当指针传递到此过程时,它将通过引用传递。注意行为:当此过程将ptr设置为nil(即pascal表示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、发明Java的James Gosling和David Holmes摘自Java编程语言,第2章,第2.6.5节

方法的所有参数都是按值传递的。换句话说,, 方法中参数变量的值是调用程序的副本 指定为参数

他继续对物体提出同样的观点

您应该注意,当参数是对象引用时,它是 对象引用不是通过值传递的对象本身

在同一节的末尾,他提出了一个更广泛的观点,即java只按值传递,而从不按引用传递

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编程语言不通过引用传递对象;信息技术 按值传递对象引用。因为两份相同的 引用指的是同一个实际对象,通过一个 参考变量通过另一个变量可见。只有一个 参数传递模式通过值传递,这有助于保持 简单

本书的这一部分对Java中的参数传递以及按引用传递和按值传递之间的区别进行了详细的解释,这是由Java的创建者完成的。我会鼓励任何人阅读它,特别是如果你仍然不相信的话

我认为这两个模型之间的差异非常微妙,除非您在编程时实际使用了pass-by-reference,否则很容易忽略这两个模型的不同之处

我希望这能解决争论,但可能不会

编辑3

我可能对这篇文章有点着迷。可能是因为我觉得Java制造商无意中传播了错误信息。如果他们没有使用reference这个词作为指针,而是使用了其他的东西,比如 丁格贝利,这不会有问题的。您可以说,Java通过值而不是引用传递dingleberries,没有人会感到困惑

这就是只有Java开发人员对此有异议的原因。他们看参考词并认为他们确切地知道这意味着什么,所以他们甚至懒得考虑相反的论点。
changeReference(f);
不管怎样,我注意到一篇老帖子中的一条评论,这是一个我非常喜欢的气球式的比喻。如此之多,以至于我决定把一些剪贴画粘在一起,制作一组卡通来说明这一点

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"
}
通过值传递引用对引用的更改不会反映在调用方的作用域中,但对对象的更改会反映在调用方的作用域中。这是因为引用已复制,但原始和副本都引用同一对象。

按引用传递没有引用的副本。调用方和被调用函数共享单个引用。对引用或对象数据的任何更改都会反映在调用方的作用域中。

编辑4

我看到过关于这个主题的帖子,其中描述了Java中参数传递的底层实现,我认为这非常好,非常有用,因为它使 这是一个抽象的概念。然而,对我来说,问题更多的是语言规范中描述的行为,而不是行为的技术实现。这是来自以下方面的练习:

当调用§15.12中的方法或构造函数时 实际参数表达式初始化新创建的参数 每个声明类型的变量,在执行 方法或构造函数。中显示的标识符 DeclaratorId可以在方法体中用作简单名称或 构造函数引用形式参数

这意味着,java在执行方法之前会创建所传递参数的副本。像大多数在大学里学习编译器的人一样,我使用的是编译器书籍。它在第1章中很好地描述了按值调用和按引用调用。按值调用的描述与Java规范完全匹配

当我在90年代研究编译器时,我使用了1986年的第一版,它比Java早了9到10年。然而,我刚刚看到了2007年的一个副本,它实际上提到了Java!第1.6.6节标记的参数传递机制非常好地描述了参数传递。下面是标题为Callby value的摘录,其中提到了Java:

在“按值调用”中,如果实际参数是 表达式,如果它是变量,则复制它。该值被放置在 属于该函数的相应形式参数的位置 称为过程。此方法在C和Java中使用,是一种常见的方法 C++中的选项,以及大多数其他语言。
我们通常会说,通过引用传递的变量可以进行变异。这个术语出现在教科书中是因为语言理论家需要一种方法来区分原始数据类型int、bool、byte和复杂的结构化对象数组、流、类,也就是说,那些可能是无限内存分配的对象。我想指出的是,在大多数情况下,你不必考虑这一点。我编程java多年,直到我学会C++。直到现在,我还不知道什么是passbyreference和passbyvalue。直观的解决方案总是对我有用,这就是为什么java是初学者最好的语言之一。因此,如果您当前担心,如果您的函数需要引用或值,只需按原样传递它就可以了。Java按值传递引用。简而言之,这种混淆是因为在Java中,所有非原始数据类型都是通过引用处理/访问的。然而,传递总是有价值的。所以对于所有非基本类型,引用都是通过其值传递的。所有的原始类型也都是通过值传递的。对于那些来自C++的人,当接触java时会开始混淆,这是错误的。java调用C++调用什么指针。java C++中不存在C++调用的内容。C++引用是指针类型,但具有全局范围。当您更改C++引用时,引用中的所有事件都会发生变化,无论是在调用函数中还是在调用函数中。Java不能做到这一点。Java是严格按值传递的,对Java引用的更改是严格本地的。Java被调用函数无法更改调用函数的引用值。您可以通过使用像AtomicReference这样的包装对象来模拟C++引用。C++引用与范围无关。在实现中,它们就像不允许有空值的指针。除此之外的主要区别在于,它们在语法上表现为引用数据的别名。在Java中,引用的工作方式几乎相同,但有特殊的规则允许:使用==运算符与null和其他引用值进行比较。C++也通过值,虽然该值可以是引用的指针/引用。调用方法对C++引用的更改也可以通过调用方法来显示。这在Java中是不存在的,也不是指针式的行为。在Java和C中,指针值的更改仅限于本地。我不知道如何正确地调用这种行为,但它有点类似于某些脚本语言的外部范围。例如,正确的按引用传递请参见此处的swap程序:不可能在Java中编写具有相同副作用的swap方法。语言引用的质量是C++引用中不存在的,它不存在于java引用或C指针中。我还要补充一点,即pass-by-value字面意思是传递堆栈中的文本值。那么,在第一个示例中,Fifi会发生什么情况?它是否不再存在,它是否从未创建过,或者它是否存在于堆中,但在堆栈中没有引用变量?对我来说,说对象的引用是按值传递的与说对象是按引用传递的是一样的。我是一个Java新手,但我认为与此相反,基本数据是passB
y值。@user36800:你错了。您是否使用Fifi完成了示例并仔细查看了结果?检查是否确实是fooaDog;尽管foo覆盖了d的值,但没有更改aDog,这表明函数的所有输入都是通过值传递的。@user36800:嗯,这两个语句都是错误的。通过引用传递对象意味着,如果函数修改变量,那么它将修改对象本身。这不是Java中发生的事情;对象不能通过引用传递,但只能将引用作为输入传递给函数,当函数执行d=newdogffi时;它覆盖输入变量d,该变量存储引用,但不是“引用传递的对象”。与C中的函数签名中的&d相反,它将通过引用传递。[cont]@dbrewster很抱歉,但是。。。Fifi不再在我们当中了JVM2.2非常清楚地表明了这一点:有。。。可以存储在变量中、作为参数传递、由方法返回和操作的两种值:原语值和引用值。对象引用是值。所有的东西都是通过值传递的。这表明java不是通过值,因为它不象C++那样把整个对象复制到堆栈上,如上面的例子……,狗Objo,…,没有,java按值传递引用。这就是为什么在java示例中覆盖objPtr时,原始Dog对象不会改变。但是,如果修改objPtr指向的对象,它会这样做。Java总是按值传递参数,但按值传递的是对对象的引用,而不是对象的副本。简单吗?我希望我学习Java的Herbert Schildt书在上面的例子中教过这个小的澄清问题,所以当在地址72的BBB创建新狗时,这是否意味着在返回时,创建的狗在72岁时,它的价值会丢失并恢复到42?@ebresie@ebresie大部分是的,我稍后会澄清大部分。指向74的新狗的唯一指针我想你指的是74而不是72是foo函数的参数。当foo返回时,它的所有参数都从堆栈中弹出,因此没有任何参数指向72,可以对其进行垃圾收集。我说的主要是因为没有发生逆转;调用者中的指针myDog一直指向42,并且从未更改,无论函数中发生了什么,因此,没有还原。但是,您不能更改该指针指向的位置。->但是,你不能用其他方法改变指针指向的位置。@NiharGht好的观点-我已经澄清了,如果仍然不清楚,请再次评论好的工作人员,你已经说清楚了。请有人投票否决这个答案,因为它不包含任何解释,而且有抄袭他人答案的味道!我没有足够的声望投票否决这个无用的答案。
array1[0] = 5;
array1 = array2;
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 */
    }
}
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>
}
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().
     }
}
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.