问题:Java是"按引用传递"还是"按值传递"?

我一直认为Java是通过引用传递

但是,我已经看到了几篇博客文章(例如,此博客)声称不是。

我不认为我能理解他们的区别。

解释是什么?

标签:java,methods,parameter-passing,pass-by-reference,pass-by-value

回答1:

Java始终是传递值。不幸的是,当我们传递对象的值时,我们正在传递 reference 到它。这会使初学者感到困惑。

它是这样的:

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"main中的值aDog在具有Dog "Fifi" 作为对象引用按值传递。如果通过引用传递,则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是调用foo(aDog)后的狗的名字,因为对象的名称是在foo(...)food上执行的任何操作,实际上都是在aDog上执行,但不是 可以更改变量aDog本身的值。

回答2:

我刚刚注意到您引用了我的文章

Java规范说Java中的所有内容都是按值传递的。 Java中没有"通过引用传递"这样的东西。

了解这一点的关键是类似

Dog myDog;

不是是狗;实际上是狗的指针

那是什么时候?

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对象)
    • 要求Dog(地址为42的那只狗)将他的名字更改为Max
  • " BBB"行
    • 已创建一个新的Dog。假设他在地址74
    • 我们将参数someDog分配给74
  • " CCC"行
    • someDog后跟它指向的Dog(地址为74的Dog对象)
    • 要求Dog(地址为74的那个)将他的名字更改为Rowlf
  • 然后,我们返回

现在让我们考虑一下方法外发生的事情:

myDog是否已更改?

有钥匙。

请记住,myDog是一个指针,而不是实际的Dog,答案是否定的。 myDog仍然具有值42;它仍指向原始的Dog(但请注意,由于行" AAA",其名称现在为" Max"-仍是同一条Dog; myDog的值具有不变)。

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

Java的工作原理与C完全相同。您可以分配一个指针,将该指针传递给方法,跟随该方法中的指针并更改所指向的数据。但是,您不能更改该指针指向的位置。

在C ++,Ada,Pascal和其他支持传递引用的语言中,您实际上可以更改传递的变量。

如果Java具有传递引用语义,则我们上面定义的foo方法将更改为myDog分配someDog在BBB线上。

将参考参数视为传入变量的别名。分配别名后,传入变量也将分配。

回答3:

Java始终通过值传递参数,而不是通过引用传递参数。


让我通过示例进行解释:

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的引用,并为其分配具有属性的类型为Foo的新对象" f"

    Foo f = new Foo("f");
    

  2. 从方法方面,声明名称为a的类型为Foo的引用,并且最初为其分配了null。< / p>

    public static void changeReference(Foo a)
    

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

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

    Foo b = new Foo("b");
    

  5. a=b对引用的a不是 f,进行了新分配属性为"b"的对象。

  6. 当您调用modifyReference(Fooc)方法时,将创建引用c并为其分配具有属性"f"。

  7. c.setAttribute("c");将更改引用c指向它的对象的属性,并且它是引用< code> f 指向它。

我希望您现在了解如何将对象作为参数传递给Java:)

回答4:

这将为您提供一些有关Java真正工作方式的见解,以至于在下一次有关Java通过引用传递或通过值传递的讨论中,您只会笑:-)

第一步,请从您的脑海中抹去以" p"开头的单词" _ _ _ _ _ _ _ _ _",特别是如果您来自其他编程语言。 Java和'p'不能写在同一本书,论坛甚至txt中。

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

  • 学生:大师,这是否意味着Java是通过引用传递的?
  • 硕士:蚱hopper,没有。

现在想一想对象的引用/变量的作用/是:

  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行中创建,并且在开头为空。
  • 在第2行中创建一个新的Person对象,并将其存储在内存中,并将变量 person 赋予对Person对象的引用。即是它的地址。比方说3bad086a。
  • 保存对象地址的变量 person 在第3行传递给函数。
  • 在第4行中,您可以听见寂静之声
  • 检查第5行的评论
  • 创建方法局部变量- anotherReferenceToTheSamePersonObject -,然后在第6行出现魔术:
    • 将变量/引用 person 逐位复制并传递给函数内的 otherReferenceToTheSamePersonObject
    • 没有创建Person的新实例。
    • " person "和" anotherReferenceToTheSamePersonObject "具有相同的值3bad086a。
    • 不要尝试此操作,但person == anotherReferenceToTheSamePersonObject将为真。
    • 两个变量都具有引用的IDIONAL COPIES,并且它们都引用相同的Person对象,堆上的SAME对象和NOT A COPY。

一张图片值一千字:

请注意,anotherReferenceToTheSamePersonObject箭头指向对象而不是变量人!

如果您没有得到它,那就请相信我,并记住,最好说 Java是通过价值传递的。好吧,通过参考值传递。哦,好吧, 通过变量值的复制! ;)

现在随时可以恨我,但请注意,鉴于这种在谈论方法参数时传递原始数据类型和对象没有区别。

您总是传递参考值的位的副本!

  • 如果它是原始数据类型,则这些位将包含原始数据类型本身的值。
  • 如果它是一个对象,则这些位将包含告诉JVM如何访问该对象的地址的值。

Java是按值传递的,因为在方法内部,您可以随意修改所引用的对象,但是无论尝试多么努力,您都永远无法修改将保持引用的传递变量(不是p _ _ _ _ _ _ _))相同的对象,无论如何!


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


当然,您可以简略地说,只是说 Java是按价值传递的!

回答5:

Java始终按值传递,永远没有例外。

那么,如何让所有人对此感到困惑,并且认为Java是通过引用传递的,或者认为他们有一个Java充当引用传递的示例呢?关键是Java 从不在任何情况下都可以直接访问对象本身的值。对对象的唯一访问是通过对该对象的引用。由于Java对象总是通过引用而不是直接访问 ,因此通常将字段和变量以及方法参数称为 objects ,在书呆子方面,它们只是对对象的引用这种混淆(严格来说是不正确的)是由于术语的变化引起的。

所以,在调用方法时

  • 对于基本参数(intlong等),按值传递是原始的实际值(例如,3)。
  • 对于对象,按值传递是对对象的引用的值。

因此,如果您有doSomething(foo)publicvoiddoSomething(Foofoo){..},则两个Foos已复制了引用指向相同的对象。

自然地,按值传递对对象的引用看起来很像(实际上在实践中是无法区分的)按引用传递对象。

回答6:

Java按值传递引用。

所以您不能更改传入的引用。

回答7:

我觉得争论"引用传递vs传递值"并不是很有帮助。

如果您说" 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,因为第二个字符串的地址为与第一个相同,不会创建新的String且不会分配新的堆空间,但是会在堆栈上创建一个新的标识符。然后我们调用shout():创建了一个新的堆栈框架,并创建了一个新的标识符name,并为其分配了已经存在的String的地址。

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

回答8:

仅为显示对比,请比较以下 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"
}

在Java中,

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仅具有两种传递类型:内置类型按值传递,而对象类型按指针值传递。

回答9:

Java按值传递对对象的引用。

回答10:

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

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

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

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

回答11:

我不敢相信还没有人提到过芭芭拉·里斯科夫(Barbara Liskov)。当她在1974年设计CLU时,遇到了相同的术语问题,并发明了通过共享进行呼叫(也称为通过对象共享进行呼叫按对象调用)针对"按值调用,其中值是引用"的特定情况。

回答12:

问题的症结在于"通过引用传递"一词中的 reference 一词意味着与Java中 reference 一词的通常含义完全不同。

通常在Java中,引用表示对对象的引用。但是编程语言理论中的按引用/值传递术语是在谈论对包含变量的存储单元的引用,这完全不同。

回答13:

在Java中,所有内容都是引用,因此,当您具有以下内容时:Pointpnt1=newPoint(0,0); Java执行以下操作:

  1. 创建新的Point对象
  2. 创建新的Point引用,并将该引用初始化为先前创建的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

在这一行上,"按值传递"功能正在发挥作用……

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

引用pnt1pnt2通过值传递到棘手的方法,这意味着现在您的引用pnt1pnt2副本分别命名为arg1arg2。因此,pnt1和< code> arg1 指向指向同一对象。 (与pnt2arg2相同)

tricky方法中:

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

下一步 方法

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

在这里,您首先创建新的temp点参考,该参考将在与arg1参考相同的地方 point 。然后将引用arg1移至 point 到与arg2引用相同的位置。最终,arg2 point 指向同一位置,例如temp

在这里,tricky方法的范围已消失,您将无法再访问以下引用:arg1arg2,< code> temp 。 但是重要的一点是,当这些引用处于"生命"状态时,您所做的一切都会永久影响它们指向的对象。

因此,在执行方法tricky后,返回到main时,会出现以下情况:

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

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

回答14:

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的副本,然后将其传递给函数。由于它是按值传递,因此它传递变量的副本,而不是变量的实际引用。由于我们说过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,如下所示:

希望这会有所帮助。

回答15:

无论使用哪种语言,引用在表示时始终是一个值。

获取盒子视图之外的内容,让我们看一下Assembly或一些低级的内存管理。在CPU级别,如果将任何内容的 reference 写入内存或CPU寄存器之一,则立即成为 value 。 (这就是为什么 pointer 是一个很好的定义。它是一个值,同时具有目的)。

内存中的数据具有位置,并且在该位置具有一个值(字节,字,任意值)。在Assembly中,我们有一个方便的解决方案,可以为特定的 Location (又名变量)提供 Name ,但是在编译代码时,汇编程序只需替换 Name >具有指定位置,就像您的浏览器用IP地址替换域名一样。

从根本上讲,从技术上讲不可能在不表示任何内容的情况下将引用传递给任何语言(当它立即成为值时)。

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

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

这是跳转表的工作方式。

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

  1. 5被复制到一个CPU寄存器(即EAX)。
  2. 5被压入堆栈。
  3. 47被复制到其中一个CPU寄存器
  4. 47推入堆栈。
  5. 223被复制到一个CPU寄存器中。
  6. 223被压入堆栈。

在每种情况下,都会创建一个值-现有值的一个副本,现在该操作由接收方法来处理。当您在方法中编写" Foo"时,它是从EAX中读出,还是自动取消引用,或双重取消引用,该过程取决于语言的工作方式和/或Foo的类型。这对开发人员是隐藏的,直到她规避了取消引用过程。因此,引用在表示时为,因为引用是必须处理的值(在语言级别)。

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

  • 在情况1和情况2中,如果更改Foo(Foo=9),则仅会影响本地范围,因为您拥有Value的副本。从方法内部,我们甚至无法确定原始Foo在内存中的位置。
  • 在情况3和4.中,如果您使用默认语言构造并更改Foo(Foo=11),则可能会全局更改Foo(取决于语言,即Java或类似Pascal的<代码> 程序findMin(x,y,z:整数; var m :整数);)。但是,如果该语言允许您规避取消引用过程,则可以将 47更改为 49。到那时,如果您阅读了Foo,似乎已经更改了,因为您已经更改了 本地指针。而且,如果您要在方法( Foo=12)中修改此Foo,则可能会FUBAR执行程序(也称为segfault),因为您将写入与预期不同的内存,则可以甚至修改一个预定存放可执行程序的区域,对其进行写操作也会修改正在运行的代码(Foo现在不在 47处)。但是Foo的 47值并没有全局更改,只是方法内部的值,因为 47也是该方法的副本。
  • 在情况5和6.中,如果您在方法内修改223,则会创建与3.或4中相同的混乱状态。(一个指针,指向现在的错误值,也就是(用作指针)),但这仍然是本地问题,因为已复制。但是,如果您能够取消引用Ref2Foo(即223),请访问并修改指向值47,例如,将其修改为49 ,它将全局影响Foo,因为在这种情况下,方法获得了223的副本,但是引用的47仅存在一次,将其更改为49将会导致每个Ref2Foo双重引用都使用错误的值。

细微地注意细节,即使是通过引用传递的语言也会将值传递给函数,但是那些函数知道必须将其用于取消引用的目的。这种"按引用传递值"对程序员来说只是隐藏的,因为它实际上是无用的,并且术语仅是"按引用传递"。

严格的传递值也没有用,这意味着每次我们调用以数组为参数的方法时,都必须复制一个100 MB的数组,因此Java不能严格价值传递。每种语言都会传递对该巨大数组的引用(作为值),并且如果该数组可以在方法内部进行本地更改,或者采用该方法(如Java),则可以采用写时复制机制。调用者的视图)和几种语言可以修改引用本身的值。

因此,简而言之,用Java自己的术语来说,Java是传递值,其中 value 可以是:实际值,它表示参考

回答16:

Java是按值调用

工作原理

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

  • 如果它是原始数据类型,则这些位包含原始数据类型本身的值,这就是为什么如果我们在方法内部更改标头的值,则它不会反映外部的更改。

    < / li>
  • 如果它是像 Foo foo = new Foo()这样的对象数据类型,则在这种情况下,对象地址的副本将通过文件快捷方式传递,假设我们有一个文本文件<位于> C:\ desktop 的strong> abc.txt ,并假设我们创建相同文件的快捷方式,并将其放入 C:\ desktop \ abc-shortcut 当您从 C:\ desktop \ abc.txt 访问该文件并写入 "堆栈溢出" 并关闭该文件,然后再次从快捷方式打开该文件时,则您写< strong>是最大的供程序员学习的在线社区" ,则文件总更改将为 "堆栈溢出是最大的供程序员学习的在线社区" 从打开文件的位置开始,每次我们访问同一文件时,我们都可以假设 Foo 作为文件,并假定foo存储在 123hd7h (原始地址如 C:\ desktop \ abc.txt )地址和 234jdid (复制的地址,例如 C:\ desktop \ abc-shortcut ,其中实际上包含文件的原始地址)。为了更好地理解,请制作快捷方式文件和感觉..

回答17:

已经有很好的答案可以解决这个问题。我想通过分享一个非常简单的示例(将进行编译)做出一点点贡献,该示例将对比c ++中的按引用传递和Java中的按值传递之间的行为。

几点:

  1. 术语"引用"是重载,具有两个单独的含义。在Java中,它只是表示一个指针,但在"按引用传递"的上下文中,它表示传入的原始变量的句柄。
  2. Java是按值传递,但允许我们通过按值传递Java引用(即指针)来模拟pass be reference。意味着它传递了Java参考的副本。 EDIT :由于有人对此发表了评论,所以让我解释一下。在C之前,FORTRAN和COBOL等几种(但不是全部)较早的语言支持PBR,但是C不支持。为了更改函数内部变量的值,C程序员通过将指向变量的指针传递到函数中来模拟PBR。受C启发的语言(例如Java)借鉴了这一想法,并像C一样继续模仿PBR。
  3. C ++通过使用"&"字符(恰好与C和C中用于表示"变量的地址"的字符相同)声明引用参数,从而允许通过引用 C ++)。例如,如果我们按引用传递指针,则参数和参数不仅指向同一对象。相反,它们是相同的变量。如果将一个设置为其他地址或设置为null,则另一个设置也将设置为
  4. 在下面的C ++示例中,我通过引用将指针传递给空终止的字符串 。在下面的Java示例中,我按值传递了对String的Java引用(再次与指向String的指针相同)。注意注释中的输出。

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 ++示例。我在pascal中发布了相同的示例,因为我认为通过引用传递在pascal中看起来更干净,但是我可能是错的。我可能只是让人们更加困惑。我希望不会。

在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, James Gosling(发明Java的人)" Java编程语言" 的摘录,以及David Holmes,第2章,第2.6节。 5

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

他继续就物体提出了相同的观点。 。 。

您应该注意,当参数是对象引用时,传递"按值" 是对象引用而不是对象本身。

在同一部分的结尾处,他对Java仅通过值传递而从不传递引用做了更广泛的说明。

Java编程语言不通过引用传递对象; 按值传递对象引用。由于相同引用的两个副本引用了相同的实际对象,因此通过一个引用变量进行的更改将通过另一个变量可见。仅有一种参数传递模式-按值传递-,这有助于使事情保持简单。

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

我认为这两个模型之间的区别非常微妙,除非您在实际使用传递引用的地方进行了编程,否则很容易错过两个模型之间的区别。

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

编辑3

我可能对这个帖子有些痴迷。可能是因为我觉得Java的制造商无意间散布了错误信息。如果他们没有使用"引用"一词作为指针,而是使用了其他东西(例如dingleberry),那就没有问题了。您可以说," Java通过值而不是通过引用传递dingleberries",并且不会有人感到困惑。 (因此,当引用按引用与值进行传递时,我将引用称为dinglebarries。)

这就是只有Java开发人员对此有疑问的原因。他们看着"指称"一词,并认为他们确切地知道这是什么意思,因此他们甚至不必理会相反的论点。

无论如何,我注意到 dhackner 在较旧的帖子中发表的评论,他做出了我非常喜欢的气球类比。如此之多,以至于我决定将一些剪贴画粘合在一起,制作出一系列动画片来说明这一点。

按值传递引用-引用的更改不会反映在调用者的作用域中,但对象的更改会反映在调用者的作用域中。这是因为引用已复制,但是原始副本和副本都引用同一对象。

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

编辑4

我看过有关此主题的文章,这些文章描述了Java中参数传递的低级实现,我认为这很好并且非常有帮助,因为它使抽象概念具体化。但是,对我而言,问题更多是关于语言规范中描述的行为,而不是有关行为的技术实现。这是 Java的摘录语言规范,第8.4.1节

调用方法或构造函数时(第15.12节),实际参数表达式的值会在执行方法或构造函数的主体之前初始化新创建的参数变量(每个声明的类型)。

出现在DeclaratorId中的标识符可以用作方法或构造函数主体中的简单名称,以引用形式参数。

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

回到90年代研究编译器时,我使用了1986年的第一版书,它比Java早了9到10年。但是,我遇到了2007年第二版的副本实际上提到了Java!标记为"参数传递机制"的第1.6.6节很好地描述了参数传递。以下是"按值调用"标题下的摘录,其中提到了Java:

在按值调用中,将评估实际参数(如果它是一个表达式)或将其复制(如果它是一个变量)。该值放置在属于被调用过程的相应形式参数的位置。 此方法用在C和Java中,是C ++和大多数其他语言中的常用选项。

回答18:

据我所知,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",因为在交换功能中,您使用的副本对主引用没有影响。但是,如果您的对象不是一成不变的,则可以更改它,例如:

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,则它将生成Hello,因为String是不可变的。例如:

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制作包装器,使其能够与Strings一起使用:

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的原因,因为您可以使用诸如String这样的不可变对象来修改您无法使用的原始对象。

回答19:

不,它不是通过引用传递的。

Java是根据Java语言规范按值传递的:

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

回答20:

让我尝试借助四个示例来解释我的理解。 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:

/ **这种"按价值传递"有"按引用传递"的感觉

有人说原始类型和"字符串"是"按值传递",而对象是"按引用传递"。

但是从这个例子中,我们可以理解它实际上只是按值传递,请记住,这里我们将引用作为值传递。即:引用按值传递。这就是为什么可以更改,并且在本地范围后仍然适用的原因。但是我们不能在原始范围之外更改实际参考。下一个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)中提到的内容外,我们无法在原始范围之外更改实际引用。"

注意:我没有粘贴私有班级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]

回答21:

在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中不可用。

回答22:

我想我应该回答这个问题,以便在规格中添加更多细节。

首先,">传递之间有什么区别通过引用还是通过值传递?

通过引用传递意味着被调用函数的参数将与调用者传递的参数相同(不是值,而是身份-变量本身)。

按值传递意味着被调用函数的参数将是调用者传递的参数的副本。

或者从维基百科,关于通过引用的主题

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

并且关于传递值的主题

在按值调用中,对参数表达式进行求值,并将结果值绑定到函数[...]中的相应变量。如果该函数或过程能够为其参数分配值,则仅分配其本地副本[...]。

第二,我们需要知道Java在其方法调用中使用了什么。 Java语言规范状态

当调用方法或构造函数时(第15.12节),实际参数表达式的值会初始化新创建的参数变量,每种变量都在执行方法主体或函数体之前进行声明构造函数。

因此它将参数的值分配(或绑定)到相应的参数变量。

参数的值是什么?

让我们考虑一下引用类型, Java虚拟机规范状态

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

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等位)。

回答23:

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

Java参数全部按值传递(引用被方法使用时被复制):

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

对于对象来说,这是相同的:对象变量是仅包含使用" new"关键字创建的对象地址的指针(存储桶),并且像原始类型一样被复制。

该行为可能看起来与原始类型不同:因为复制的对象变量包含相同的地址(指向相同的对象)。对象的 content / members 可能仍然可以在方法中进行修改,然后在外部进行访问,从而产生一种幻想,即(包含的)对象本身是通过引用传递的。

"字符串"对象似乎是很好的反例,说"对象通过引用传递":

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

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

回答24:

区别,或者也许只是我记得与原始海报有相同印象的方式是:Java总是通过价值传递。 Java中的所有对象(在Java中,除基元以外的所有对象)都是引用。这些引用按值传递。

回答25:

许多人以前提到过, Java总是传递值< / a>

这是另一个示例,可以帮助您理解差异(经典的交换示例 ):

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的引用只会在本地范围内发生更改,而不会超出此方法。

回答26:

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

回答27:

我总是认为它是"通过副本"。它是值的副本,可以是原始值或引用。如果它是原始类型,则是作为值的位的副本;如果是对象,则是引用的副本。

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

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

回答28:

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

也提到了Java 。这是简短的摘要:

  • Java按值传递参数
  • "按值"是Java中将参数传递给方法的唯一方法
  • 在给定参数的对象中使用
  • 方法将改变对象,因为引用指向原始对象。 (如果该方法本身会更改某些值)

回答29:

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

通常,Java具有原始类型(intboolchardouble等)通过值直接传递。然后,Java具有对象(所有从java.lang.Object派生的对象)。实际上,对象总是通过引用(引用是您无法触摸的指针)来处理的。这意味着实际上,对象是通过引用传递的,因为引用通常并不有趣。但是,这确实意味着您无法更改指向哪个对象,因为引用本身是通过值传递的。

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

将其带到C ++,我们有参考。引用基本上是(在这种情况下)语法糖,它隐藏了等式的指针部分:voidfoo(int&x)foo(a)调用,其中编译器本身知道这是一个引用,因此应传递非引用a的地址。在Java中,所有引用对象的变量实际上都是引用类型,实际上在大多数意图和目的下都强制按引用进行调用,而没有C ++提供的精细控制(和复杂性)。

回答30:

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

当方法修改原始类型的参数时,对该参数的更改不会影响调用方法中的原始参数值。

当涉及对象时,对象本身无法传递给方法。因此,我们传递了保存在引用变量中的对象的地址。

Java如何创建和存储对象:创建对象时,我们将对象的地址存储在引用变量中。让我们分析以下语句。

Account account1 = new Account();

" Account account1"是引用变量的类型和名称," ="是赋值运算符," new"从系统中请求所需的空间量。关键字new右边用于创建对象的构造函数由关键字new隐式调用。使用assign运算符将创建对象的地址(右变量的结果,它是一个表达式)分配给左变量(这是具有名称和类型的参考变量)。 " new Account()"称为"类实例创建表达式"。

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

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

在下图中,您可以看到main方法中有两个参考变量(在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 };

        reverseArray(array1);
    }
}

所以,如果我们说

array1[0] = 5;

在reverseArray方法中,它将更改数组a。

reverseArray方法(array2)中还有另一个引用变量,它指向数组c。如果我们要说

array1 = array2;

在verseArray方法中,然后在reverseArray方法中的引用变量array1将停止指向数组a,并开始指向数组c(第二幅图中的虚线)。

如果我们将引用变量array2的值返回为reverseArray方法的返回值,并将此值分配给main方法中的引用变量array1,则main中的array1将开始指向数组c。

return 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 */
    }
}

现在,reverseArray方法结束了,它的引用变量(array1和array2)消失了。这意味着我们现在在主方法array1和array2中只有两个引用变量,分别指向c和b数组。没有引用变量指向对象(数组)。因此可以进行垃圾收集。

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

回到顶部