当前位置: 首页 > 图灵资讯 > 技术篇> Java 应用程序中的按值传递语义

Java 应用程序中的按值传递语义

来源:图灵教育
时间:2024-03-01 15:57:47

节选理解参数是按值而不是引用传输的说明 Java 应用程序中只有一种参数传输机制,即按值传输。写它是为了揭示一个常见的神话,即认为它是 Java 应用程序根据引用传输参数,避免因依赖“引用传输”而导致的常见编程错误。

一些关于这个摘录的反馈认为,我对这个问题感到困惑,或者完全犯了错误。许多不同意我的读者使用它 C++ 以语言为例。因此,我将在这个专栏中使用它。 C++ 和 Java 应用程序进一步阐明了一些事实。

要点 看完所有的评论,问题终于明白了,至少在一个主要问题上是混淆的。有评论认为我的节选是错误的,因为对象是按引用传递的。对象确实是按引用传递的;节选与此没有冲突。所有参数都是按值计算的 -- 另一个参数 -- 传递的。下面的说法是正确的:在 Java 对象永远不会在应用程序中传递,只会传递对象引用。因此,根据引用传递对象。但重要的是区分参数是如何传递的,这就是节选的意图。Java 按引用传递对象的事实并不意味着应用程序并不意味着 Java 根据引用传递参数,应用程序。对象可以引用参数, Java 按值传递对象引用应用程序。

C++ 和 Java 应用程序中的参数传递 Java 应用程序中的变量可以是以下两种类型之一:引用类型或基本类型。当作为参数传递给一种方法时,处理这两种类型的方法是相同的。这两种类型都是按值传递的;没有一种是按引用传递的。这是一个重要的特征,如随后的代码示例所示。

在继续讨论之前,定义两个术语:按值传递和引用传递是很重要的。按值传递意味着当一个参数传递到一个函数时,函数接收原始值的副本。所以,如果函数修改了该参数,则只更改副本,并且原始值保持不变。引用传输意味着当参数传输到函数时,函数接收原始值的内存地址,而不是值的副本。因此,如果函数修改了参数,调用代码中的原始值也会发生变化。

关于 Java 应用程序中参数传递的一些混淆源于这样一个事实:许多程序员来自 C++ 编程转向 Java 编程的。C++ 它不仅包括非引用类型,还包括引用类型,分别按值和引用传递。Java 编程语言有基本类型和对象引用;因此,认为 Java 应用程序像 C++ 按值传递基本类型,按引引用传递符合逻辑。毕竟,你会这么认为,如果一个引用正在传递,它必须按引用传递。很容易相信这一点。事实上,有一段时间我也相信这一点,但这是不正确的。

在 C++ 和 Java 在应用程序中,当传递给函数的参数不被引用时,它们都是该值的副本(按值传递)。区别在于引用。在 C++ 当传递给函数的参数是引用时,您传递的是引用或内存地址(引用)。在 Java 在应用程序中,当对象引用是传递给方法的参数时,你传递的是引用的副本(按值传递),而不是引用本身。请注意,调用方法的对象引用和副本都指向同一对象。这是一个重要的区别。Java 在传递不同类型的参数时,应用程序是如何工作的 C++ 并无不同。Java 应用程序根据值传递所有参数,从而制作所有参数的副本,无论其类型如何。

示例 我们将使用前面的定义和讨论来分析一些例子。首先考虑一段 C++ 代码。C++ 语言采用按值传递和引用传递的参数传递机制:

清单 1:C++ 示例

#include #include void modify(int a, int *P, int &r); int main (int argc, char** argv) { int val, ref; int *pint; val = 10; ref = 50; pint = (int*)malloc(sizeof(int)); *pint = 15; printf("val is %d\n", val); printf("pint is %d\n", pint); printf("*pint is %d\n", *pint); printf("ref is %d\n\n", ref); printf("calling modify\n"); //按值传递 val 和 pint,按引用传递 ref。 modify(val, pint, ref); printf("returned from modify\n\n"); printf("val is %d\n", val); printf("pint is %d\n", pint); printf("*pint is %d\n", *pint); printf("ref is %d\n", ref); return 0; } void modify(int a, int *p, int &r) { printf("in modify...\n"); a = 0; *p = 7; p = 0; r = 0; printf("a is %d\n", a); printf("p is %d\n", p); printf("r is %d\n", r); }

该代码的输出如下:

清单 2:C++ 代码的输出

val is 10 pint is 4262128 *pint is 15 ref is 50 calling modify in modify... a is 0 p is 0 r is 0 returned from modify val is 10 pint is 4262128 *pint is 7 ref is 0

该代码声明了三个变量:两个整形变量和一个指针变量。设置每个变量的初始值并打印出来。指针值及其所指向的值同时打印出来。然后将所有三个变量传递给参数 modify 函数。前两个参数按值传递,最后一个参数按引用传递。modify 函数的函数原型表明最后一个参数应作为引用传递。回想一下,C++ 所有参数按值传递,除引用外,后者按引用传递。

modify 函数改变了所有三个参数的值: 将第一个参数设置为 0。 将第二个参数指向的值设置为 7.然后将第二个参数设置为 0。 将第三个参数设置为 0。

 

打印新值,然后返回函数。当执行返回时 main 这三个参数的值和指针指向的值再次打印出来。作为第一个和第二个参数传递的变量不受影响 modify 函数的影响,因为它们是按值传递的。但是指针指向的值发生了变化。请注意,与前两个参数不同,变量作为最后一个参数传递 modify 函数发生了变化,因为它是按引用传递的。

现在考虑用 Java 语言编写的类似代码:

清单 3:Java 应用程序

class Test { public static void main(String args[]) { int val; StringBuffer sb1, sb2; val = 10; sb1 = new StringBuffer("apples"); sb2 = new StringBuffer("pears"); System.out.println("val is " + val); System.out.println("sb1 is " + sb1); System.out.println("sb2 is " + sb2); System.out.println(""); System.out.println("calling modify"); ////按值传递所有参数 modify(val, sb1, sb2); System.out.println("returned from modify"); System.out.println(""); System.out.println("val is " + val); System.out.println("sb1 is " + sb1); System.out.println("sb2 is " + sb2); } public static void modify(int a, StringBuffer r1, StringBuffer r2) { System.out.println("in modify..."); a = 0; r1 = null; //1 r2.append(" taste good"); System.out.println("a is " + a); System.out.println("r1 is " + r1); System.out.println("r2 is " + r2); } }

该代码的输出如下:

清单 4:Java 输出应用程序

val is 10 sb1 is apples sb2 is pears calling modify in modify... a is 0 r1 is null r2 is pears taste good returned from modify val is 10 sb1 is apples sb2 is pears taste good

该代码声明了三个变量:一个整形变量和两个对象引用。设置每个变量的初始值并打印出来。然后将所有三个变量传递给参数 modify 方法。

modify 该方法改变了所有三个参数的值: 将第一个参数(整数)设置为 0。 引用第一个对象 r1 设置为 null。 保留第二个引用 r2 值,但通过调用 append 该方法改变了它引用的对象(这与之前的对象相匹配) C++ 指针在示例中 p 类似的处理)。

 

当执行返回时 main 这三个参数的值再次打印。正如预期的那样,整形 val 没有改变。对象引用 sb1 也没有改变。如果 sb1 它是按引用传递的,正如许多人所声称的那样,它将被视为 null。但是,因为 Java 编程语言按值传递所有参数,因此它将是 sb1 引用的副本传递给 modify 方法。当 modify 方法在 //1 位置将 r1 设置为 null 时间,它只是对的 sb1 引用的副本进行了操作,而不是像 C++ 对原始值进行操作。

另外,请注意引用第二个对象 sb2 打印出来的是在 modify 即使是方法中设置的新字符串。 modify 中的变量 r2 只是引用 sb2 一个副本,但它们指向同一个对象。因此,复制引用调用的方法改变了同一对象。

写一个交换方法 假设我们知道参数是如何传递的 C++ 以不同的方式编写交换函数。使用指针的交换函数类似于以下代码,其中指针是按值传递的:

清单 5:使用指针的交换函数

#include #include void swap(int *a, int *b); int main (int argc, char** argv) { int val1, val2; val1 = 10; val2 = 50; swap(&val1, &val2); return 0; } void swap(int *a, int *b) { int temp = *b; *b = *a; *a = temp; }

引用的交换函数类似于以下代码,其中引用是按引用传递的:

清单 6:使用引用的交换函数

#include #include void swap(int &a, int &b); int main (int argc, char** argv) { int val1, val2; val1 = 10; val2 = 50; swap(val1, val2); return 0; } void swap(int &a, int &b) { int temp = b; b = a; a = temp; }

两个 C++ 所有代码示例都像预期的那样交换了价值。假如 Java 如果应用程序使用“按引用传输”,则应采用以下交换方法 C++ 示例一样正常工作:

清单 7:Java 交换函数是否相似 C++ 中间按引用传输参数

class Swap { public static void main(String args[]) { Integer a, b; a = new Integer(10); b = new Integer(50); System.out.println("before swap..."); System.out.println("a is " + a); System.out.println("b is " + b); swap(a, b); System.out.println("after swap..."); System.out.println("a is " + a); System.out.println("b is " + b); } public static void swap(Integer a, Integer b) { Integer temp = a; a = b; b = temp; } }

因为 Java 应用程序按值传输所有参数,因此该代码不会正常工作,其输入如下:

清单 8:清单 7 的输出

before swap... a is 10 b is 50 after swap... a is 10 b is 50

那么,在 Java 如何在应用程序中编写一种交换两个基本类型值或两个对象引用值的方法?因为 Java 应用程序根据值传递所有参数,因此您不能这样做。为了交换值,您必须使用该方法来调用外部和内部连接。

结论 本人在书中包含这些信息的意图不是做一些琐碎的分析或试图使问题复杂化,而是警告程序员: Java 假设“按引用传递”的语义在应用程序中是危险的。假如你在那里 Java 假设“按引用传递”的语义在应用程序中,你可能会写上述类似的交换方法,然后想知道为什么它工作不正常。