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



java如何引用传递 string是值传递还是引用传递 (25)

我一直认为Java是传递引用的

但是,我看过一些博客文章(例如, 这个博客 )声称它不是。

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

解释是什么?


Answers

区别,或者也许只是我记忆中的方式,因为我曾经与原始海报的印象相同:Java总是按价值传递。Java中的所有对象(在Java中,除了基元之外的任何东西)都是引用。这些引用按值传递。


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

Java规范说Java中的所有内容都是按值传递的。 在Java中没有“pass-by-reference”这样的东西。

理解这一点的关键是类似的东西

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” - 仍然是相同的狗; myDog的值没有改变。)

按照地址并改变最后的内容是完全有效的; 但是,这不会改变变量。

Java的工作方式与C完全相同。您可以分配指针,将指针传递给方法,按照方法中的指针操作并更改指向的数据。 但是,您无法更改指针指向的位置。

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

如果Java具有pass-by-reference语义,那么我们在上面定义的foo方法会更改someDog在BBB上分配someDog时指向的位置。

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


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

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

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


据我所知,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,因为在交换函数中,您使用的copys对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);
}

这将在命令行上填充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创建一个包装器,这样就可以将它与字符串一起使用:

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这样的不可变对象。


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

/ **这种'通过价值传递'有'通过参考传递'的感觉

有些人说原始类型和'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通过引用传递或通过值传递时,您只需微笑:-)

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

第二步记住,当你将一个Object传递给一个方法时,你传递的是Object引用,而不是Object本身。

  • 学生 :师父,这是否意味着Java是传递参考?
  • 师父 :蚱蜢,没有。

现在想想Object的引用/变量是什么/是什么:

  1. 变量保存位,告诉JVM如何到达内存中引用的Object(Heap)。
  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。
  • 在第2行创建一个新的Person对象,存储在内存中,并为变量person提供对Person对象的引用。 那就是它的地址。 比方说3bad086a。
  • 保存Object对象地址的变量person将传递给第3行中的函数。
  • 在第4行,你可以听到沉默的声音
  • 检查第5行的评论
  • 创建一个方法局部变量 - anotherReferenceToTheSamePersonObject - 然后在#6行中产生魔力:
    • 变量/引用被逐位复制并传递给函数内的anotherReferenceToTheSamePersonObject
    • 没有创建Person的新实例。
    • person ”和“ anotherReferenceToTheSamePersonObject ”都保持相同的3bad086a值。
    • 不要试试这个,但是人== anotherReferenceToTheSamePersonObject会是真的。
    • 两个变量都具有引用的IDENTICAL COPIES,它们都引用相同的Person对象,堆上的SAME对象而不是COPY。

一张图片胜过千言万语:

请注意,anotherReferenceToTheSamePersonObject箭头指向Object而不是指向变量person!

如果你没有得到它,那么只要相信我,并记住最好说Java是通过值传递的 。 那么, 通过参考值传递 。 哦,更好的是传递变量值的副本! ;)

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

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

  • 如果它是原始数据类型,则这些位将包含原始数据类型本身的值。
  • 如果它是一个Object,那么这些位将包含告诉JVM如何到达Object的地址值。

Java是按值传递的,因为在方法中你可以根据需要修改引用的对象,但无论你怎么努力,你都永远无法修改将继续引用的传递变量(不是p _ _ _ _ _ _ _)同样的对象无论如何!

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

当然,你可以缩短它,只是说Java是值得传递的!


我无法相信没人提到Barbara Liskov。当她在1974年设计CLU时,她遇到了同样的术语问题,并且通过共享(也称为通过对象共享调用按对象调用)发明了这个特定情况的“通过值调用,其值为参考“。


Java始终是按值传递的 。 不幸的是,他们决定将对象的位置称为“引用”。 当我们传递一个对象的值时,我们将引用传递给它。 这对初学者来说很困惑。

它是这样的:

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" 。 使用Dog "Fifi"在函数foo不更改main的值aDog ,因为对象引用按值传递。 如果它是通过引用传递的,那么mainaDog.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");
}

在上面的例子中, Fifi是调用foo(aDog)之后的狗的名字,因为对象的名字是在foo(...)food上执行的任何操作都是这样的,出于所有实际目的,它们是在aDog本身上执行的(除非d被更改为指向不同的Dog实例,例如d = new Dog("Boxer") )。


我觉得争论“传递引用与传递价值”并不是非常有用。

如果你说“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);
}

创建一个String,并在堆中分配空间,并将字符串的地址存储在堆栈中并赋予标识符hisName ,因为第二个String的地址与第一个String的地址相同,因此不会创建新的String并且没有分配新的堆空间,但是在堆栈上创建了新的标识符。 然后我们调用shout() :创建一个新的堆栈帧,并创建一个新的标识符, name并分配已存在的String的地址。

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


在java中,所有内容都是引用,所以当你有类似的东西时:Point pnt1 = new Point(0,0);Java确实如下:

  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

在这条线上'传递价值'进入游戏......

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

参考文献pnt1pnt2按值传递给棘手的方法,这意味着现在你参考pnt1,并pnt2有自己的copies命名arg1arg2。所以pnt1,并arg1 到同一个对象。(与pnt2和相同arg2

tricky方法中:

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

接下来的tricky方法

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

在这里,您首先创建新的tempPoint引用,它将指向相同的位置,如arg1引用。然后您将参考arg1像同一个地方arg2的参考。最后arg2指向同一个地方temp

从这里的范围tricky方法走了,你没有获得任何更多的引用:arg1arg2temp但重要的是,当你在生活中使用这些引用时所做的一切都会永久地影响它们所指向的对象。

所以在执行方法之后tricky,当你返回时main,你有这种情况:

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

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

的问题的关键是,字参考在表述“通过引用传递”是指某物从字的通常含义完全不同的参考在Java中。

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


为了显示对比,请比较以下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只有两种类型的传递:内置类型的值,以及对象类型的指针值。


无论您使用何种语言,引用始终是表示的值。

获取框外视图,让我们看看Assembly或一些低级内存管理。在CPU级别,如果将任何内容写入内存或其中一个CPU寄存器,则对任何内容的引用会立即变为。(这就是为什么指针是一个很好的定义。它是一个值,它同时具有目的)。

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

在核心的情况下,技术上不可能在不表示任何语言的情况下将引用传递给任何语言(当它立即变为值时)。

比方说,我们有一个变量富,它的位置是在内存中的第47字节,它的是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获得PUSHd到堆栈。
  3. 47被复制到其中一个CPU寄存器
  4. 47推到堆栈。
  5. 223被复制到其中一个CPU寄存器。
  6. 223获取PUSHd到堆栈。

在每个情况下,都创建了一个值 - 现有值的副本 - 现在由接收方法来处理它。当你在方法中写入“Foo”时,它要么从EAX中读出,要么自动解除引用,或者双重解引用,这个过程取决于语言的工作方式和/或Foo的类型。这是开发人员隐藏的,直到她绕过解除引用过程。因此,引用是表示的,因为引用是必须处理的值(在语言级别)。

现在我们已经将Foo传递给了方法:

  • 在情况1.和2.如果您更改Foo(Foo = 9)它只影响本地范围,因为您有一个值的副本。从方法内部我们甚至无法确定原始Foo所在的内存位置。
  • 如果您使用默认语言结构并更改Foo(Foo = 11),它可以全局更改Foo(取决于语言,即Java或类似Pascal的procedure findMin(x, y, z: integer; var m : integer);)。但是,如果该语言允许您绕过取消引用过程,您可以更改47,比方说49。在那一点上,如果你读它,Foo似乎已被改变了,因为你已经改变了它的本地指针。如果你要在方法(Foo = 12)中修改这个Foo,你可能会搞砸程序的执行(又名.segfault),因为你会写一个不同于预期的内存,你甚至可以修改一个注定要保存可执行文件的区域程序和写入它将修改运行代码(Foo现在不在47)。但是Foo的价值47没有全局更改,只有方法内部的一个,因为47也是方法的副本。
  • 在第5和第6例中。如果你223在方法内修改它会产生与3.或4中相同的混乱。(一个指针指向一个现在错误的值,它再次用作指针)但这仍然是一个本地问题,因为223被复制了。但是,如果您能够取消引用Ref2Foo(即223),达到并修改指向的值47,比如说,49它将全局影响Foo ,因为在这种情况下,方法得到了一个副本,223但引用的47只存在一次,并且更改了对49每一个会导致Ref2Foo双解引用到一个错误的值。

对无关紧要的细节进行挑剔,即使是通过引用传递的语言也会将值传递给函数,但这些函数知道它们必须将它用于解除引用目的。这个传递参考值只是程序员隐藏的,因为它实际上是无用的,术语只是通过引用传递

严格的pass-by-value也是无用的,这意味着每次调用一个以数组为参数的方法时都必须复制一个100 MB的数组,因此Java不能严格按值传递。每种语言都会传递对这个巨大数组的引用(作为一个值),并且如果该数组可以在方法内部进行本地更改,或者允许该方法(如Java那样)全局修改数组,则采用写时复制机制(来自调用者的视图)和一些语言允许修改引用本身的值。

因此,简而言之,在Java自己的术语中,Java是值传递,其中可以是:实数值或作为引用表示的


简而言之,Java对象具有一些非常特殊的属性。

一般来说,Java有原始类型(intboolchardouble,等等)直接由值来传递。然后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 ++提供的细粒度控制(和复杂性)。


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

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

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

将打印"Hah!"而不是null。这个工作的原因是因为bar是一个值的副本baz,它只是一个引用"Hah!" 。如果它是实际的引用本身,那么foo将重新定义baznull


我想我会提供这个答案,以便从规格中添加更多细节。

首先,通过引用传递与传递值之间的区别是什么?

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

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

或者来自维基百科,关于传递参考的主题

在逐个引用的评估(也称为pass-by-reference)中,函数接收对用作参数的变量的隐式引用,而不是其值的副本。这通常意味着函数可以修改(即赋值)用作参数的变量 - 其调用者将看到的内容。

对主题传递的价值

在call-by-value中,计算参数表达式,并将结果值绑定到函数[...]中的相应变量。如果函数或过程能够为其参数赋值,则仅指定其本地副本[...]。

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

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

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

论证的价值是什么?

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

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

Java语言规范还规定

引用值(通常只是引用)是指向这些对象的指针,以及一个特殊的空引用,它指的是没有对象。

参数(某种引用类型)的值是指向对象的指针。请注意,变量,具有引用类型返回类型的方法的调用以及实例创建表达式(new ...)都将解析为引用类型值。

所以

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

all将String实例引用的值绑定到方法新创建的参数中param。这正是pass-by-value的定义所描述的。因此,Java是按值传递的

您可以按照引用来调用方法或访问引用对象的字段这一事实与对话完全无关。传递参考的定义是

这通常意味着函数可以修改(即赋值)用作参数的变量 - 其调用者将看到的内容。

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

here还在Java虚拟机规范中定义了原始值。该类型的值是相应的积分或浮点值,适当编码(8,16,32,64等位)。


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

那么如何让任何人都对此感到困惑,并相信Java是通过引用传递的,或者认为他们有一个Java作为传递参考的例子? 关键是Java在任何情况下都不会直接访问对象本身的值。 对对象的唯一访问是通过对该对象的引用 。 因为Java对象总是通过引用而不是直接访问,所以通常将字段和变量以及方法参数作为对象进行讨论 ,而当它们只是对对象的引用时这种混淆源于这种(严格来说,不正确的)命名法的变化。

所以,在调用方法时

  • 对于原始参数( intlong等),pass by value是原语的实际值 (例如,3)。
  • 对于对象,pass by value是对象引用的值。

因此,如果你有doSomething(foo)public void doSomething(Foo foo) { .. }那么两个Foos已经复制了指向相同对象的引用

当然,通过值传递对对象的引用看起来非常像(并且在实践中无法区分)通过引用传递对象。


您永远不能通过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中不可用。


一些帖子的一些更正。

C不支持通过引用传递。它总是通过价值。C ++确实支持按引用传递,但不是默认值,而且非常危险。

Java中的值是什么并不重要:对象的原始或地址(粗略),它总是按值传递。

如果Java对象“行为”就像通过引用传递一样,那就是可变性的属性,并且与传递机制完全无关。

我不确定为什么会这么混乱,也许是因为很多Java“程序员”没有经过正式培训,因此不明白内存中究竟发生了什么?


Java是按值调用的。

这个怎么运作

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

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

  • 如果它是一个像Foo foo = new Foo()这样的对象数据类型,那么在这种情况下,对象地址的副本会像文件快捷方式一样传递,假设我们在C:\ desktop中有一个文本文件abc.txt,并假设我们设置了快捷方式相同的文件,并把它放在C:\ desktop \ abc-shortcut中,所以当你从C:\ desktop \ abc.txt访问文件并写入''并关闭文件时再次从快捷方式打开文件然后你write '是程序员学习的最大的在线社区'然后总文件更改将是'是程序员学习的最大的在线社区'这意味着从打开文件的位置开始无关紧要,每次我们访问同一个文件时,我们都可以假设Foo为文件,并假设foo存储在123hd7h(原始地址如C:\ desktop \ abc.txt)地址和234jdid(复制地址如C:\ desktop \ abc-shortcut,其中实际上包含了文件的原始地址)。所以为了更好地理解制作快捷方式文件和感觉...


Java按VALUE传递参数,按值传递参数。

长话短说:

对于来自C#的人:没有“out”参数。

对于来自PASCAL的人:没有“var”参数

这意味着您无法更改对象本身的引用,但您始终可以更改对象的属性。

解决方法是使用StringBuilder参数String。你总是可以使用数组!


我已经为here任何编程语言创建了一个致力于这类问题的线程。here

还提到了Java。以下是简短摘要:

  • Java按值传递参数
  • “by value”是java将参数传递给方法的唯一方法
  • 使用作为参数给出的对象的方法将改变对象,因为引用指向原始对象。(如果该方法本身改变某些值)

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中, 没有任何东西通过引用传递 。 一切都通过价值传递 。 对象引用是按值传递的。 此外,字符串是不可变的 。 所以当你附加到传递的String时,你只需要一个新的String。 你可以使用返回值,或者传递一个StringBuffer。





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