我一直认为Java使用的是按引用传递。然而,我看了一篇博客文章,声称Java使用的是按值传递。我不认为我理解作者所做的区分。
这是什么意思?
我一直认为Java使用的是按引用传递。然而,我看了一篇博客文章,声称Java使用的是按值传递。我不认为我理解作者所做的区分。
这是什么意思?
不想重复,但对于那些在阅读了许多答案后仍可能感到困惑的人,有一个要点:
按值传递(pass by value)
与C++中的按值传递
是不相等的,尽管它听起来像这样,这可能就是混淆的原因。分解一下:
按值传递
意味着传递对象的值(如果是对象),实际上是对象的副本。按值传递
意味着传递对象的地址值(如果是对象),而不是像C++中那样实际上是对象的“值”(即副本)。按值传递
,在函数内操作对象(例如myObj.setName("new")
)对函数外的对象产生影响;但是在C++中通过按值传递
,对函数外部的对象没有影响。按引用传递
,在函数中操作对象会影响函数外部的对象!类似于只是类似于,而不是相同于Java中的按值传递
,是不是有点像?..而人们总是重复“Java中没有按引用传递”,=> 嘭,混淆开始了...foo(bar)
,则bar
的值永远不会更改--如果bar
是对对象的引用,则其值是所引用对象的标识,无论函数执行什么操作,它仍然引用调用者中的同一对象。 - David SchwartzJava是按值传递。
这个主题上已经有很好的回答了。不知何故,我对于基本数据类型和对象的传值/传引用从未很清楚。因此,为了满足自己的好奇和清晰度,我测试了以下代码; 希望能帮助到寻求类似清晰度的某些人:
class Test {
public static void main (String[] args) throws java.lang.Exception
{
// Primitive type
System.out.println("Primitve:");
int a = 5;
primitiveFunc(a);
System.out.println("Three: " + a); //5
//Object
System.out.println("Object:");
DummyObject dummyObject = new DummyObject();
System.out.println("One: " + dummyObject.getObj()); //555
objectFunc(dummyObject);
System.out.println("Four: " + dummyObject.getObj()); //666 (555 if line in method uncommented.)
}
private static void primitiveFunc(int b) {
System.out.println("One: " + b); //5
b = 10;
System.out.println("Two:" + b); //10
}
private static void objectFunc(DummyObject b) {
System.out.println("Two: " + b.getObj()); //555
//b = new DummyObject();
b.setObj(666);
System.out.println("Three:" + b.getObj()); //666
}
}
class DummyObject {
private int obj = 555;
public int getObj() { return obj; }
public void setObj(int num) { obj = num; }
}
如果取消注释b = new DummyObject()
这一行代码,随后所做的修改会在一个新的实例上进行,也就是说会创建一个新的对象。因此,这些修改不会反映在调用该方法的位置上。否则,由于修改只作用于对象的“引用”,即b指向相同的dummyObject,所以修改将被反映出来。这有点难理解,但Java始终会复制该值-重点是,通常该值是一个引用。因此,您最终会不假思索地得到相同的对象...
这个问题的许多混淆来自于Java试图重新定义“按值传递”和“按引用传递”的含义。重要的是要理解这些是行业术语,在这种背景下才能正确理解。它们旨在帮助您编写代码并且了解它们非常有价值,因此让我们首先了解一下它们的含义。
可以在这里找到对两者的很好描述。
按值传递 函数接收的值是调用者正在使用的对象的副本。它完全独特于函数,并且您对该对象所做的任何操作仅在函数内部可见。
按引用传递 函数接收的值是调用者正在使用的对象的引用。该值所指向的对象被函数修改的任何内容都将被调用者看到,并且从那时起它将使用这些更改。
从这些定义中可以清楚地看出,引用是按值传递的事实是无关紧要的。如果我们接受这个定义,那么这些术语变得毫无意义,而所有语言都只是按值传递。
无论你如何传递引用,它都只能按值传递。这不是重点。重点是你将自己对象的引用传递给了函数,而不是它的副本。你可以丢弃接收到的引用这一事实是不相关的。如果我们接受了这个定义,那么这些术语就变得毫无意义,每个人都总是按值传递。1. import java.awt.Dimension;
2. class ReferenceTest {
3. public static void main (String [] args) {
4. Dimension d = new Dimension(5,10);
5. ReferenceTest rt = new ReferenceTest();
6. System.out.println("Before modify() d.height = " + d.height);
7. rt.modify(d);
8. System.out.println("After modify() d.height = "
9. }
10.
11.
12.
13. }
14. }
void bar() {
Foo f = new Foo();
doStuff(f);
}
void doStuff(Foo g) {
g.setName("Boo");
g = new Foo();
}
class ReferenceTest {
public static void main (String [] args) {
int a = 1;
ReferenceTest rt = new ReferenceTest();
System.out.println("Before modify() a = " + a);
rt.modify(a);
System.out.println("After modify() a = " + a);
}
void modify(int number) {
number = number + 1;
System.out.println("number = " + number);
}
}
Before modify() a = 1
number = 2
After modify() a = 1
所有东西都是按值传递的,包括原始类型和对象引用。但是如果它们的接口允许,对象可以被修改。
当您将对象传递给一个方法时,实际上传递的是该对象的引用,而方法实现可能会修改该对象。
void bithday(Person p) {
p.age++;
}
对象本身的引用是按值传递的:您可以重新分配参数,但更改不会反映回来:
void renameToJon(Person p) {
p = new Person("Jon"); // this will not work
}
jack = new Person("Jack");
renameToJon(jack);
sysout(jack); // jack is unchanged
实际上,"p"是引用(指向对象的指针),无法更改。
基本类型按值传递。对象的引用也可以被视为基本类型。
总之,所有东西都是按值传递的。
Java始终采用按值传递,参数是传递变量的副本,所有对象都是使用引用定义的,引用是存储对象在内存中位置的变量。
查看注释以了解执行过程;按照数字显示执行流程。
class Example
{
public static void test (Cat ref)
{
// 3 - <ref> is a copy of the reference <a>
// both currently reference Grumpy
System.out.println(ref.getName());
// 4 - now <ref> references a new <Cat> object named "Nyan"
ref = new Cat("Nyan");
// 5 - this should print "Nyan"
System.out.println( ref.getName() );
}
public static void main (String [] args)
{
// 1 - a is a <Cat> reference that references a Cat object in memory with name "Grumpy"
Cat a = new Cat("Grumpy");
// 2 - call to function test which takes a <Cat> reference
test (a);
// 6 - function call ends, and <ref> life-time ends
// "Nyan" object has no references and the Garbage
// Collector will remove it from memory when invoked
// 7 - this should print "Grumpy"
System.out.println(a.getName());
}
}
目前有一栋蓝色的120平方英尺的“微型房屋”,停在1234 Main St,前面有一个修剪整齐的草坪和花坛。
当地一家房地产公司雇了一位经纪人,并告诉他要为那栋房子保留清单。
我们称呼那位经纪人为“鲍勃”。你好,鲍勃。
鲍勃通过网络摄像头更新他的清单,称之为tinyHouseAt1234Main
,以实时记录实际房屋的任何变化。他还记录了询问该清单的人数。
今天鲍勃的整数viewTally
是42。
每当有人想了解1234 Main St的蓝色微型房屋的信息时,他们会向鲍勃询问。
鲍勃查找他的清单tinyHouseAt1234Main
并告诉他们所有相关信息-颜色、漂亮的草坪、阁楼床和堆肥厕所等。然后他将他们的询问添加到他的viewTally
中。但他不会告诉他们真实的物理地址,因为鲍勃的公司专门经营随时可以移动的微型房屋。现在这个清单已经有43个人询问过了。
在另一家公司,房地产经纪人可能会明确表示他们的清单“指向”1234 Main St的房子,并在旁边加上一个小的*
,因为他们主要处理很少搬迁的房屋(尽管可能有做此事的原因)。但鲍勃的公司不会这样做。
当然,鲍勃不会亲自去把实际房子放到卡车上直接展示给客户-那是不切实际和荒谬的浪费资源。传递完整的清单表格是一件事,但一直传递整个房子是昂贵和荒谬的。
(顺便说一下:鲍勃的公司也不会每次有人询问时都3D打印新的独特房屋副本。这正是新兴的、同名的基于网络的公司及其分支机构所做的-这是昂贵和缓慢的,人们经常混淆这两家公司,但它们仍然非常受欢迎)。
在一些靠近大海的老公司中,像鲍勃这样的房地产经纪人甚至可能不存在来管理清单。客户可能会直接向转轮卡片盘“安妮”(简称&
)咨询房屋的实际地址。客户不是像鲍勃那样从清单中阅读引用房屋详细信息,而是从安妮(&
)处获得房屋地址,然后直接去1234 Main St,有时并不知道他们可能会找到什么。
有一天,鲍勃的公司开始提供一项新的自动化服务,需要客户感兴趣的房屋的清单。
好吧,拥有这些信息的人是鲍
jobKillingAutomatedListingService(Listing tinyHouseAt1234Main, int viewTally)
Bob发送...
该服务在其端口调用此列表houseToLookAt
,但实际上它收到的是Bob的清单的精确副本,其中包含完全相同的值,这些值指的是位于1234 Main St.的房屋。
这项新服务还具有自己的内部计数器,用于记录查看清单的人数。出于职业礼貌,该服务接受Bob的计数器,但它并不关心并完全用其自己的本地副本覆盖它。今天的计数器为1,而Bob的计数器仍为43。
房地产公司称此为“按值传递”,因为Bob传递了他的viewTally
和他的Listing tinyHouseAt1234Main
的当前值。他实际上没有传递整个物理房屋,因为那是不切实际的。也没有像Annie(&
)那样传递真正的物理地址。
但他确实传递了对该房子引用的值的副本。在某些方面,这似乎是一个愚蠢而拘泥于细节的区别,但这就是他的公司的工作方式...
新的自动化服务不像其他一些时髦的金融和科学公司那样完全功能和数学导向,可能会产生意想不到的副作用...
一旦给定Listing对象,它允许客户端使用远程无人机机器人舰队实际上重新粉刷真正的1234 Main St房屋!它允许客户端控制机器人推土机,以实际上挖掘花坛!这是疯狂的!!!
该服务还允许客户完全重定向houseToLookAt
到另一个地址的某个其他房子,而不涉及Bob或他的清单。突然间,他们可以看到4321 Elm St.,它与Bob的清单没有任何联系(幸运的是,他们不能造成更多的损害)。
Bob在实时网络摄像头上观看所有这些内容。他接受了他唯一的工作责任的单调乏味,告诉客户新的丑陋的油漆工作和突然失去的路缘吸引力。毕竟,他的Listing仍然是1234 Main St。无论它还值多少钱,Bob都会像往常一样准确地和尽职地报告其tinyHouseAt1234Main
的详细信息,直到他被解雇或房子被“虚无”彻底摧毁。
实际上,该服务使用其houseToLookAt
Bob原始清单的副本唯一不能做的事情是将地址从1234 Main St更改为其他地址,或将其更改为空虚,或将其更改为某种随机类型的对象,例如鸭嘴兽。 Bob的Listing仍然始终指向1234 Main St,无论它仍具有何种价值。他像往常一样传递其当前值。
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();
new PassByValue().changeValue(t);
当将引用t传递给函数时,它不会直接传递对象test的实际引用值,而是创建t的副本,然后将其传递给函数。由于它是按值传递,因此它传递的是变量的副本,而不是它的实际引用。由于我们说t的值为0x100234,因此t和f将具有相同的值,因此它们将指向同一个对象。
第二个例子
如果您使用引用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,如下所示:
第三个例子
希望这能帮到你。