Pass-By-Value as a Parameter Passing Mechanism in Java – 在Java中作为参数传递机制的 “逐值传递”(Pass-By-Value)

最后修改: 2018年 5月 8日

中文/混合/英文(键盘快捷键:t)

 

1. Introduction

1.介绍

The two most prevalent modes of passing arguments to methods are “passing-by-value” and “passing-by-reference”. Different programming languages use these concepts in different ways. As far as Java is concerned, everything is strictly Pass-by-Value.

向方法传递参数的两种最普遍的模式是 “逐值传递 “和 “逐指传递”。不同的编程语言以不同的方式使用这些概念。就Java而言,一切都是严格的Pass-by-Value

In this tutorial, we’re going to illustrate how Java passes arguments for various types.

在本教程中,我们将说明Java如何传递各种类型的参数。

2. Pass-by-Value vs Pass-by-Reference

2.逐值传递与逐项传递

Let’s start with some of the different mechanisms for passing parameters to functions:

我们先来看看向函数传递参数的一些不同机制。

  • value
  • reference
  • result
  • value-result
  • name

The two most common mechanisms in modern programming languages are “Pass-by-Value” and “Pass-by-Reference”. Before we proceed, let’s discuss these first:

现代编程语言中最常见的两种机制是 “逐值传递 “和 “逐指传递”。在我们继续之前,让我们先讨论一下这些。

2.1. Pass-by-Value

2.1.逐值传递

When a parameter is pass-by-value, the caller and the callee method operate on two different variables which are copies of each other. Any changes to one variable don’t modify the other.

当一个参数是逐值传递时,调用者和被调用者方法对两个不同的变量进行操作,这些变量是彼此的副本。对一个变量的任何改变都不会修改另一个。

It means that while calling a method, parameters passed to the callee method will be clones of original parameters. Any modification done in callee method will have no effect on the original parameters in caller method.

这意味着在调用一个方法时,传递给被调用者方法的参数将是原始参数的克隆。在被调用者方法中做的任何修改都不会影响到调用者方法中的原始参数。

2.2. Pass-by-Reference

2.2.逐一参照

When a parameter is pass-by-reference, the caller and the callee operate on the same object.

当一个参数是通过引用的时候,调用者和被调用者对同一个对象进行操作。

It means that when a variable is pass-by-reference, the unique identifier of the object is sent to the method. Any changes to the parameter’s instance members will result in that change being made to the original value.

这意味着当一个变量被逐一传递时,对象的唯一标识符被发送到方法中。对参数的实例成员的任何改变都会导致该改变被发送到原始值。

3. Parameter Passing in Java

3.Java中的参数传递

The fundamental concepts in any programming language are “values” and “references”. In Java, Primitive variables store the actual values, whereas Non-Primitives store the reference variables which point to the addresses of the objects they’re referring to. Both values and references are stored in the stack memory.

任何编程语言的基本概念都是 “值 “和 “引用”。在Java中,主变量存储实际的值,而非主变量存储引用变量,这些变量指向它们所引用的对象的地址。值和引用都存储在堆栈内存中。

Arguments in Java are always passed-by-value. During method invocation, a copy of each argument, whether its a value or reference, is created in stack memory which is then passed to the method.

Java中的参数总是按值传递。在方法调用过程中,每个参数的副本,无论是值还是引用,都会在堆栈内存中创建,然后传递给方法。

In case of primitives, the value is simply copied inside stack memory which is then passed to the callee method; in case of non-primitives, a reference in stack memory points to the actual data which resides in the heap. When we pass an object, the reference in stack memory is copied and the new reference is passed to the method.

在基元的情况下,值被简单地复制到堆栈内存中,然后传递给被调用的方法;在非基元的情况下,堆栈内存中的引用指向驻留在堆中的实际数据。当我们传递一个对象时,堆栈内存中的引用被复制,新的引用被传递给方法。

Let’s now see this in action with the help of some code examples.

现在让我们借助一些代码实例来看看这一点的实际效果。

3.1. Passing Primitive Types

3.1.传递原始类型

The Java Programming Language features eight primitive data types. Primitive variables are directly stored in stack memory. Whenever any variable of primitive data type is passed as an argument, the actual parameters are copied to formal arguments and these formal arguments accumulate their own space in stack memory.

Java编程语言具有八种原始数据类型原始变量直接存储在堆栈内存中。每当任何原始数据类型的变量被作为参数传递时,实际参数被复制到形式参数,这些形式参数在堆栈内存中积累了自己的空间。

The lifespan of these formal parameters lasts only as long as that method is running, and upon returning, these formal arguments are cleared away from the stack and are discarded.

这些形式参数的寿命只持续到该方法运行的时候,在返回时,这些形式参数会从堆栈中被清除,并被丢弃。

Let’s try to understand it with the help of a code example:

让我们试着借助一个代码例子来理解它。

public class PrimitivesUnitTest {
 
    @Test
    public void whenModifyingPrimitives_thenOriginalValuesNotModified() {
        
        int x = 1;
        int y = 2;
       
        // Before Modification
        assertEquals(x, 1);
        assertEquals(y, 2);
        
        modify(x, y);
        
        // After Modification
        assertEquals(x, 1);
        assertEquals(y, 2);
    }
    
    public static void modify(int x1, int y1) {
        x1 = 5;
        y1 = 10;
    }
}

Let’s try to understand the assertions in the above program by analyzing how these values are stored in memory:

让我们试着通过分析这些值在内存中的存储方式来理解上述程序中的断言。

  1. The variables “x” and “y” in the main method are primitive types and their values are directly stored in the stack memory
  2. When we call method modify(), an exact copy for each of these variables is created and stored at a different location in the stack memory
  3. Any modification to these copies affects only them and leaves the original variables unaltered

baeldung - pass by value - passing primitives

3.2. Passing Object References

3.2.传递对象引用

In Java, all objects are dynamically stored in Heap space under the hood. These objects are referred from references called reference variables.

在Java中,所有对象都动态地存储在引擎盖下的堆空间中。这些对象是通过被称为引用变量的引用来实现的。

A Java object, in contrast to Primitives, is stored in two stages. The reference variables are stored in stack memory and the object that they’re referring to, are stored in a Heap memory.

与Primitives相比,Java对象的存储分为两个阶段。引用变量被存储在堆栈内存中,而它们所引用的对象则被存储在堆内存中。

Whenever an object is passed as an argument, an exact copy of the reference variable is created which points to the same location of the object in heap memory as the original reference variable.

每当一个对象作为参数被传递时,就会创建一个引用变量的精确拷贝,该拷贝指向对象在堆内存中与原始引用变量的相同位置。

As a result of this, whenever we make any change in the same object in the method, that change is reflected in the original object. However, if we allocate a new object to the passed reference variable, then it won’t be reflected in the original object.

这样做的结果是,只要我们在方法中对同一个对象做任何改变,这个改变就会反映在原始对象中。然而,如果我们为传递的引用变量分配一个新对象,那么它就不会反映在原始对象中。

Let’s try to comprehend this with the help of a code example:

让我们借助于一个代码例子来理解这个问题。

public class NonPrimitivesUnitTest {
 
    @Test
    public void whenModifyingObjects_thenOriginalObjectChanged() {
        Foo a = new Foo(1);
        Foo b = new Foo(1);

        // Before Modification
        assertEquals(a.num, 1);
        assertEquals(b.num, 1);
        
        modify(a, b);
        
        // After Modification
        assertEquals(a.num, 2);
        assertEquals(b.num, 1);
    }
 
    public static void modify(Foo a1, Foo b1) {
        a1.num++;
       
        b1 = new Foo(1);
        b1.num++;
    }
}
 
class Foo {
    public int num;
   
    public Foo(int num) {
        this.num = num;
    }
}

Let’s analyze the assertions in the above program. We have passed objects and b in modify() method that has the same value 1. Initially, these object references are pointing to two distinct object locations in a heap space:
baeldung - pass by value - passing primitives - initial

让我们分析一下上述程序中的断言。我们在modify()方法中传递了对象ab,这些对象具有相同的值1。最初,这些对象引用是指向堆空间中两个不同的对象位置:
baeldung - by value - pass primitives - initial

When these references and are passed in the modify() method, it creates mirror copies of those references a1 and b1 which point to the same old objects:

当这些引用ab被传递到modify()方法中时,它创建了这些引用a1b1的镜像副本,它们指向相同的旧对象。

baeldung - pass by value - passing primitives - before method ca

In the modify() method, when we modify reference a1, it changes the original object. However, for a reference b1, we have assigned a new object. So it’s now pointing to a new object in heap memory.

modify()方法中,当我们修改引用a1时,会改变原来的对象。然而,对于引用b1,我们已经分配了一个新的对象。所以它现在指向了堆内存中的一个新对象。

Any change made to b1 will not reflect anything in the original object:

b1所做的任何改变都不会反映在原始对象中。

baeldung - pass by value - passing primitives - after method cal

4. Conclusion

4.结论

In this article, we looked at how parameter passing is handled in case of Primitives and Non-Primitives.

在这篇文章中,我们看了在基元和非基元的情况下如何处理参数传递。

We learned that parameter passing in Java is always Pass-by-Value. However, the context changes depending upon whether we’re dealing with Primitives or Objects:

我们了解到,Java中的参数传递始终是逐值传递。然而,根据我们处理的是原语还是对象,上下文会发生变化。

  1. For Primitive types, parameters are pass-by-value
  2. For Object types, the object reference is pass-by-value

The code snippets used in this article can be found over on GitHub.

本文中使用的代码片段可以在GitHub上找到over