Java 是 "逐项传递" 还是 "按值传递"?

我一直以为 Java 是通过引用的

然而, 我已经看到了一些博客文章 (例如,这个博客), 声称它不是。

我想我不明白他们的区别。

原因是什么?

第1个答案

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
}

在上面的示例 aDog.getName() 中, 仍将返回 "Max"aDogmain 函数中, 当 foo Dog "Fifi" 对象引用通过值传递时, 中的值不会更改。如果它是通过引用传递的, 则 aDog.getName()main "Fifi" 调用后将返回 foo in。

同样:

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(...) 里面。foo执行的任何操作 d 都是这样的, 出于所有的实际目的, 它们都是在上面执行 aDog 的, 但不可能更改变量本身的值 aDog

第2个答案

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

Java 规范说, Java 中的所有内容都是按值传递的。Java 中没有 "按引用传递" 之类的东西。

理解这一点的关键是, 类似

Dog myDog;

不是狗, 是狗。它实际上是一个指向狗的指针。

这意味着, 当你有

Dog myDog = new Dog("Rover");
foo(myDog);

您实质上是将创建的对象的地址传递 Dogfoo 该方法。

(我说的本质上是因为 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 到它指向 ( Dog 对象在地址 42)
    • Dog(地址 42 的那个) 被要求把他的名字改为 max
  • 在 "BBB" 线
    • 一个新 Dog 的被创建。假设他在地址 74
    • 我们将参数分配 someDog 给 74
  • 在 "CCC" 线
    • 有些狗被跟随 Dog 到它指向 ( Dog 对象在地址 74)
    • Dog(地址 74 的那个) 被要求把他的名字改到罗里夫
  • 然后, 我们返回

现在, 让我们想想在方法之外发生了什么:

myDog 变化吗?

那是钥匙

记住, myDog 这是一个指针, 而不是一个实际 Dog 的, 答案是没有 myDog , 仍然有值 42; 它仍然指向原来的 Dog (但请注意, 由于行 "aaa", 它的名字现在是 "最大"-仍然是相同的狗; myDog "s 值没有更改。

跟随一个地址并改变地址的结尾是完全有效的;但是, 不会更改变量。

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

    Foo f = new Foo("f");
    

    enter image description here

  2. 从方法方面, Foo 声明具有名称的类型的引用, a 并对其进行最初分配 null

    public static void changeReference(Foo a)
    

    enter image description here

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

    changeReference(f);
    

    enter image description here

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

    Foo b = new Foo("b");
    

    enter image description here

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

    enter image description here


  6. 调用方法时 modifyReference(Foo c) , c 将创建一个引用并为其分配具有属性 "f" 的对象。

    enter image description here

  7. c.setAttribute("c");将更改引用指向它的对象的属性 c , 引用指向它的对象是同一个对象 f

    enter image description here

我希望现在您能理解在 Java 中如何将对象作为参数传递工作:)

第4个答案

这将为您提供一些关于 Java 如何真正工作的见解, 以至于在您下一次讨论 Java 通过引用或传递值时, 您只需微笑:-)

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

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

  • 学生: 师父, 这是否意味着 java 是按引用传递的?
  • 师父: 蝗虫, 没有。

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

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

发生什么事了?

  • 变量人员是在 #1 行中创建的, 在开头为 null。
  • 在 #2 行中创建一个新的 Person 对象, 该对象存储在内存中, 并为变量人员提供对 person 对象的引用。也就是说, 它的地址。假设 3bad086a。
  • 持有 Object 地址的变量人员将传递给行 #3 中的函数。
  • 排 #4 你可以听到寂静的声音
  • 检查行 #5 上的评论
  • 创建了一个方法局部变量-另一个引用 Tothesame 个性化对象, 然后在 #6 中产生了魔力:
    • 变量引用人员被逐位复制, 并传递给函数中的另一个引用 tothesame 个人对象。
    • 不会创建 "人员" 的新实例。
    • "" 和 "其他参考 Tothesam 伤者"的值相同, 为 3bad086a。
    • 不要尝试这个, 但人 = = 另一个参考 Tothesame 个人对象将是正确的。
    • 这两个变量都有引用的识别复制, 它们都引用相同的人对象, 堆上的相同对象, 而不是复制。

一张图片胜过千言万语:

Pass by Value

请注意, 另一个引用 Tothesamengonobject 箭头是指向对象的, 而不是指向变量的人!

如果你没有得到它, 那么只要相信我, 记住最好说 Java 是通过价值传递的。好吧,通过参考值.哦, 好吧, 甚至更好的是通过复制的可变值! ;)

现在请随意恨我, 但请注意, 考虑到这一点, 在谈论方法参数时, 传递原始数据类型和对象没有区别

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

  • 如果是基元数据类型, 这些位将包含基元数据类型本身的值。
  • 如果它是一个对象位将包含地址的值, 告诉 t
第5个答案

Java 总是通过值传递, 没有例外,永远

那么, 怎么会有人对此感到困惑, 相信 Java 是通过引用传递的, 或者认为他们有一个 Java 作为引用传递的例子呢?关键的一点是, Java 在任何情况下都不会提供对对象本身值的直接访问。对对象的唯一访问是通过对该对象的引用。因为 Java 对象总是通过引用而不是直接访问, 所以通常会将字段、变量和方法参数作为对象来处理,而当它们只是对对象的引用时.混乱源于术语的这一 (严格地说, 是不正确的) 变化.

因此, 当调用方法时

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

因此, 如果您 doSomething(foo)public void doSomething(Foo foo) { .. } 和两个 fos 已复制指向相同对象的引用

当然, 按值传递对对象的引用看起来很像 (实际上与通过引用传递对象是无法区分的)。

第6个答案

Java 按值传递引用。

因此, 您不能更改传入的引用。

第7个答案

我觉得争论 "逐参考传递与逐值传递" 并不是超级有帮助。

如果您说 "Java 是通过任何 (引用值)", 无论哪种情况, 您都没有提供完整的答案。这里有一些额外的信息, 希望能帮助了解记忆中发生的事情。

在我们进入 Java 实现之前, 在堆栈堆上崩溃课程: 值以一种有序的方式在堆栈上进行, 就像自助餐厅的一叠盘子一样。 堆中的内存 (也称为动态内存) 是随意和无序的。JVM 只是在任何可能的地方找到空间, 并将其释放为不再需要使用空间的变量。

好。首先, 本地基元位于堆栈上。因此, 此代码:

int x = 3;
float y = 101.1f;
boolean amIAwesome = true;

会导致这样的结果:

primitives on the stack

声明和实例化对象时。实际的对象在堆上。堆栈上发生了什么?堆上对象的地址。C++ 程序员会将其称为指针, 但一些 Java 开发人员反对 "指针" 一词。无论。只需知道对象的地址在堆栈上。

就像这样:

int problems = 99;
String name = "Jay-Z";

a b*7ch aint one!

数组是一个对象, 因此它也会放在堆上。数组中的对象呢?它们获得自己的堆空间, 每个对象的地址都进入数组。

JButton[] marxBros = new JButton[3];
marxBros[0] = new JButton("Groucho");
marxBros[1] = new JButton("Zeppo");
marxBros[2] = new JButton("Harpo");

marx brothers

那么, 当你调用一个方法时, 会传递什么?如果传入对象, 则实际传入的是对象的地址。有人可能会说地址的 "值", 也有人说这只是对对象的引用。这就是 "参考" 和 "价值" 支持者之间的圣战的起源。你所说的并不重要, 因为你明白被传递的是对象的地址。

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, 也不会分配新的堆空间 d, 但在堆栈上创建一个新标识符。然后我们调用 shout() : 创建一个新的堆栈框架, 并创建一个新的标识符, name 并分配已经存在的 string 的地址。

la da di da da da da

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

第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 。之所以能这样做, 是因为 bar 它是值的副本 baz , 而该值只是对 "Hah!" 值的引用。如果它是实际引用本身, 那么 foo 就会重新定义 baznull

第11个答案

我真不敢相信还没有人提到芭芭拉·利斯科夫当她在 1974 年设计 CHU 时, 她遇到了同样的术语问题, 她发明了 "调用" 一词, 即共享(也称为通过对象共享调用和按对象调用), 用于这种特定的情况, 即 "在值是参考 "。

第12个答案

问题的关键是, "通过引用传递" 表达中的词引用是指与 java 中通常的词引用含义完全不同的东西。

通常在 Java引用中表示对对象的引用。但通过编程语言理论的引用值传递的技术术语是指持有变量的记忆单元, 这是完全不同的。

第13个答案

在 java 中, 所有内容都是引用的, 因此当您有类似这样的内容时: Point pnt1 = new Point(0,0); java 执行以下操作:

  1. 创建新的 Point 对象
  2. 创建新的 Point 引用, 并在以前创建的 Point 对象上初始化该引用的点 (参考)
  3. 从这里, 通过 Point 对象生命, 您将通过 pnt1 引用访问该对象。因此, 我们可以说, 在 Java 中, 您可以通过对象的引用来操作对象。

enter image description here

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 对象, 并关联两个不同的引用。enter image description here

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 )enter image description here

tricky 方法中:

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

enter image description here

方法中的下一个 tricky

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

在这里, 您首先创建新的 temp point 引用, 它将指向相同 arg1 的位置, 如引用。然后将引用移动 arg1 到同一位置 (如 arg2 引用)。 最后 arg2指向同一个地方就像 temp

enter image description here

从这里方法的范围 tricky 消失了, 你没有更多的访问引用: arg1 ,, arg2 temp但重要的是, 当这些引用 ' 生活中 ' 时, 你对它们所做的一切都会永久影响它们指向的对象.

因此, 在执行方法后 tricky , 当您返回到 main , 您会遇到这种情况:enter image description here

因此, 现在, 程序的完全执行将是:

X1: 0         Y1: 0
X2: 0         Y2: 0
X1: 100       Y1: 100
X2: 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 内部值, 这只是一个例子)。

first illustration

new PassByValue().changeValue(t);

当将引用 t 传递给函数时, 它不会直接传递对象测试的实际参考值, 但它将创建 t 的副本, 然后将其传递给函数。由于它是通过值传递的, 因此它传递的是变量的副本, 而不是它的实际引用。因为我们说 t 的值是 0x100234 , t 和 f 都将具有相同的值, 因此它们将指向相同的对象。

second illustration

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

third illustration

希望这将有所帮助。

第15个答案

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

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

内存中的数据有一个位置, 在该位置有一个值 (字节、单词、任何内容)。在程序集中, 我们有一个方便的解决方案, 为某些位置(又名变量) 指定一个名称, 但在编译代码时 , 汇编程序只是将 name 替换为指定的位置, 就像您的浏览器将域名替换为 IP 地址。

从核心来看, 技术上不可能在不表示任何语言的情况下 (当它立即成为值时) 传递对任何语言中的任何内容的引用。

假设我们有一个变量 Foo, 它的位置在内存中的第 47 个字节, 它的是 5。我们有另一个变量Ref2foo 是在内存中的第 282 字节, 它的值将是 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 PUSHd 到堆栈。
  5. 223 将被复制到其中一个 CPU 寄存器。
  6. 223 得到普希德堆栈。

在高于值 (现有值的副本) 的每一种情况下, 都已创建, 现在由接收方法来处理它。当您在方法中编写 "Foo" 时, 它要么从 EAX 中读出, 要么自动取消引用, 或者进行双项引用, 则此过程取决于语言的工作方式和 foo 的定义类型。这是对开发人员隐藏的, 直到她绕过取消引用过程。因此,引用在表示时是一个, 因为引用是必须处理的值 (在语言级别)。

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

  • 在情况 1。和 2。如果您更改 Foo ( Foo = 9 ), 则它只会影响本地作用域, 因为您有 "值" 的副本。从方法内部, 我们甚至无法确定原始 Foo 在内存中的位置。
  • 在情况 3。并且 4。如果您使用默认语言构造并更改 Foo ( Foo = 11 ), 它可能会在全局范围内更改 Foo (取决于语言, 即。Java 或像 Pascal procedure findMin(x, y, z: integer; 的 var m : integer); )。但是, 如果语言允许您规避取消引用过程, 您可以更改 47 、对其说 49 。此时, 如果您阅读了 Foo, 则该 foo 似乎已被更改, 因为您已更改了指向它的本地指针。如果您要在方法 () 内修改这个 Foo, Foo = 12 您可能会 FUBAR 程序的执行 (又名 seg-f 高标准), 因为您将写入与预期不同的内存, 您甚至可以修改一个区域, 注定要保存可执行程序和写入它将修改正在运行的代码 (Foo 现在不在47
第16个答案

Java 是按值调用的

它是如何工作的

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

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

  • 如果它是一个对象数据类型, 如Foo foo = 新 foo () , 那么在这种情况下, 对象地址的副本传递类似于文件快捷方式, 假设我们有一个文本文件abc. txt 在c:slesat,假设我们使同一文件的快捷方式, 并把这个在 C:\ sdesk Top\ abcb 快捷方式, 所以当你从 C:\ deskp 不要 \ Abcc. txt 和写"堆栈溢出"和关闭文件, 再次打开文件从快捷方式, 然后你写' 是最大的在线社区程序员学习 '然后总的文件更改将是' 堆栈溢出是最大的在线社区为程序员学习 '这意味着它并不重要, 从你打开文件的位置, 每次我们访问同一个文件, 在这里我们可以假设Foo作为文件, 假设 foo 存储在123hd7h(原始地址如C:\ deskp 当作. txt) 地址和234jdid(复制地址如C:\ desktope\ abc-快捷方式, 它实际上包含的原始地址里面的文件)。 所以为了更好地理解制作快捷方式文件和感觉..。

第17个答案

据我所知, 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);
}

这将填充你好世界, 而不是世界你好, 因为在交换函数中, 你使用的 copys 没有对主引用的影响。但是, 如果您的对象不是不可变的, 您可以更改它, 例如:

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。如果您将 String 缓冲区更改为 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);
}

但是, 您可以为这样的字符串制作一个包装器, 使其能够与字符串一起使用:

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 的原因, 当涉及到 "添加" 两个字符串, 因为你可以修改原始对象, 你不能与不可变的对象, 如字符串是。

第18个答案

不, 这不是通过引用。

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

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

第19个答案

让我尝试用四个例子来解释我的理解。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" 是 "按值传递" 的, 对象是 "通过引用传递"。

但从这个例子中, 我们可以理解它实际上只是通过值传递的, 请记住, 我们在这里传递引用作为值。 即: 引用是通过值传递的。 这就是为什么能够改变, 但在本地范围之后仍然如此。 但我们不能在原始范围之外更改实际引用。 这意味着什么, 这一点由一个例子证明。

*/

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("Ana
第20个答案

在 Java 中永远不能通过引用传递, 显而易见的方法之一是当您希望从方法调用返回多个值时。请考虑 c++ 中的以下代码位:

void getValues(int& arg1, int& arg2) {
    arg1 = 1;
    arg2 = 2;
}
void caller() {
    int x;
    int y;
    getValues(x, y);
    < "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 中不可用。

第21个答案

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

首先,通过引用与通过值传递有什么区别?

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

通过值传递意味着被调用函数的参数将是调用方传递的参数的副本。

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

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

而在按价值传递的问题上

在 "按值调用" 中, 计算参数表达式, 并将生成的值绑定到函数 [...] 中的相应变量。 如果函数或过程能够为其参数赋值, 则只分配其本地副本 [...]。

其次, 我们需要知道 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"));

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

您可以按照引用来调用方法或访问引用对象的字段, 这与对话完全无关.按引用传递的定义是

第22个答案

区别, 或者可能只是我记得的方式, 因为我曾经在原来的海报的印象相同的是: Java 总是通过值传递。Java 中的所有对象 (在 Java 中, 除了基元之外的任何对象) 都是引用。这些引用是按值传递的。

第23个答案

正如许多人之前提到的, Java 总是按价值传递的

下面是另一个示例, 将帮助您了解差异 (经典交换示例):

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

出现这种情况的原因是, Ha 和 iB 是具有相同传递引用值的新局部引用变量 (它们分别指向 a 和 b)。因此, 尝试更改 Va 或 IA 的引用只会在本地范围内更改, 而不会在此方法之外更改。

第24个答案

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();
}
第25个答案

在 Java 中, 只有引用是通过值传递的:

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

在基元类型的情况下, Java 行为很简单: 在基元类型的另一个实例中复制值。

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

这种行为可能与基元类型不同: 因为复制的对象变量包含相同的地址 (指向同一对象) 对象的内容对象的内容可能仍在方法中修改, 以后可能会在外部进行访问, 从而产生错觉(包含) 对象本身是通过引用传递的。

"字符串" 对象似乎是一个完美的反例,城市传说中的 "对象是通过引用传递":

实际上, 在一个方法中, 您永远无法更新作为参数传递的 String 的值:

字符串对象, 包含不可修改的数组声明为最终的字符。 只有对象的地址可能会替换为另一个使用 "new"。 使用 "new" 更新变量不会允许从外部访问 object, 因为该变量最初是按值传递并复制的。

第26个答案

我一直认为它是 "复制"。它是值的副本, 无论是原始值还是引用值。如果它是一个基元, 它是作为值的位的副本, 如果它是一个对象, 它是引用的副本。

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 密码副本:

名称 = Maxx
名称 = 菲多

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

第27个答案

已经创建了一个专门针对任何编程语言的问题的线程。

还提到了 Java。以下是简短的总结:

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

对一些帖子作了一些更正。

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

Java 中的值是什么并不重要: 原始或 (大致) 对象的地址, 它始终是通过值传递的。

如果 Java 对象的行为与通过引用传递的行为一样, 那就是可变形的属性, 与传递机制绝对无关。

我不知道为什么这让人如此困惑, 也许是因为这么多 Java "程序员" 没有经过正式培训, 因此不明白内存中到底发生了什么?

第29个答案

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

通常, java 具有 int bool char double 直接按值传递的基元类型 (、, 等)。然后 Java 有对象 (从派生的所有 java.lang.Object 内容)。对象实际上始终是通过引用 (引用是您无法触摸的指针) 来处理的。这意味着, 实际上, 对象是通过引用传递的, 因为引用通常并不有趣。但是, 这确实意味着, 当引用本身通过值传递时, 您不能更改指向哪个对象。

这听起来奇怪而混乱吗?让我们考虑 c 如何实现通过引用和通过值传递。在 C 中, 默认约定是通过值传递的。void foo(int x)按值传递 int. void foo(int *x)是一个不需要的函数, int a 而是指向 int: 的指针. foo(&a)人们会用它 & 和运算符一起传递一个变量地址。

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

第30个答案

Java 通过 VALUE 和仅值传递参数。

长话短说:

对于那些来自 C#:没有 "输出" 参数.

对于那些来自 PASCAL:没有 "var" 参数

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

解决方法是改用 StringBuilder 参数 String 。而且你可以随时使用数组!

相关问题

Java 是 "逐项传递" 还是 "按值传递"? 如何比较 Java 中的字符串? 什么是 Null Pointerexception, 我如何修复它? 无限参数的 c # 方法或数组或列表的方法? 我们可以使用 java.util.function 获得方法名称吗? 在不重新分配命名参数的情况下调用函数 &ldquo;:&rdquo; 何时出现在 python 中的函数参数/关键字参数中?[重复] 为什么取消引用 C 中的引用?在 * 旁边使用 &amp; 为什么 C 中的绝对值函数不接受常量输入? 指针是通过 C 中的值传递的吗?