我一直认为Java使用的是按引用传递。然而,我看了一篇博客文章,声称Java使用的是按值传递。我不认为我理解作者所做的区分。
这是什么意思?
我一直认为Java使用的是按引用传递。然而,我看了一篇博客文章,声称Java使用的是按值传递。我不认为我理解作者所做的区分。
这是什么意思?
让我用四个例子来解释我的理解。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:
/**
这个“传递值的感觉像传递引用”
*/
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语言规范指出
当方法或构造函数被调用时(§15.12),实际参数表达式的值将初始化新创建的参数变量,每个声明类型,在方法或构造函数体执行之前。
因此,它将参数的值分配(或绑定)给相应的参数变量。
参数的值是什么?
让我们考虑引用类型,Java虚拟机规范指出
有三种引用类型:类类型、数组类型和接口类型。它们的值是动态创建的类实例、数组或实现接口的类实例或数组的引用。
Java语言规范也指出
引用值(通常只是引用)是指向这些对象的指针,以及一个特殊的空引用,它不引用任何对象。
参数的值(某些引用类型)是指向对象的一个指针。请注意,变量、具有引用类型返回类型的方法调用和实例创建表达式(new ...
)都解析为引用类型值。
因此
public void method (String param) {}
...
String variable = new String("ref");
method(variable);
method(variable.toString());
method(new String("ref"));
将引用类型的值绑定到新创建的参数param
上,这正是传值的定义。因此,Java是按值传递的。调用方法或访问所引用对象的字段都与这个问题无关。
在Java中,修改变量意味着重新分配它,如果在方法内部重新分配变量,那么对调用者来说是不可见的。而修改所引用的对象是一个完全不同的概念。
原始值也在Java虚拟机规范中进行了定义,这里给出了相应类型的值,以适当的方式编码(8、16、32、64等位)。
在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中不可用。
区别在于,或许只是我的记忆有误,因为我曾经和原始贴主有着相同的印象。Java总是按值传递。在 Java 中,所有对象(除了原始数据类型)都是引用。这些引用是按值传递的。
正如许多人之前所提到的,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
这是因为 iA 和 iB 是新的本地引用变量,它们具有传递引用的相同值(分别指向 a 和 b)。因此,尝试更改 iA 或 iB 的引用只会在本地作用域内进行更改,而不会在该方法之外。
我一直将它视为“按拷贝传递”。它是值的副本,无论是原始类型还是引用类型。如果是原始类型,它就是值的位拷贝;如果是对象,则是引用的拷贝。
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
基本包装类和字符串是不可变的,所以使用这些类型的任何示例都不能像其他类型/对象一样起作用。
Account account1 = new Account();
public class Test
{
public static void reverseArray(int[] array1)
{
// ...
}
public static void main(String[] args)
{
int[] array1 = { 1, 10, -7 };
int[] array2 = { 5, -190, 0 };
reverseArray(array1);
}
}
因此,如果我们说
array1[0] = 5;
在reverseArray方法中,它将改变数组a的内容。
我们在reverseArray方法中有另一个引用变量(array2),它指向一个数组c。如果我们说
array1 = 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)已经消失。这意味着我们现在只有两个引用变量在main方法中,array1和array2分别指向c和b数组。没有引用变量指向对象(数组)a。因此,它可以被垃圾回收。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();
}
int
、bool
、char
、double
等),这些类型会直接按值传递。然后Java有对象(从java.lang.Object
派生的所有内容)。实际上,对象总是通过引用处理的(引用是一个指针,你无法触摸)。这意味着实际上对象是按引用传递的,因为引用通常不是有趣的。但这也意味着您不能更改引用所指向的对象,因为引用本身是按值传递的。void foo(int x)
按值传递一个整数。 void foo(int *x)
是一个函数,它不想要一个int a
,而是想要一个指向int的指针:foo(&a)
。人们可以使用&
运算符将变量地址传递给它。
将此转换为C ++,我们会用到引用。在这个上下文中,“引用”基本上是隐藏等式中指针部分的语法糖:void foo(int &x)
由foo(a)
调用,编译器本身知道它是一个引用,并且应该传递非引用a
的地址。在Java中,所有引用对象的变量实际上都是引用类型,实际上强制执行大多数意图和目的的引用调用,而没有像C ++那样提供细粒度控制(和复杂性)。
对一些帖子进行了一些纠正。
C语言不支持引用传递。它始终是值传递。C++支持引用传递,但不是默认设置,而且相当危险。
在Java中,无论是原始值还是对象地址(大致上),它都始终是按值传递的。
如果Java对象像通过引用传递一样“表现”,那么这是可变性的属性,与传递机制毫无关系。
我不确定为什么会如此混淆,也许是因为许多Java“程序员”没有受过正式培训,因此不理解内存中真正发生的情况?