首先,不要纠结于 Pass By Value 和 Pass By Reference 的字面上的意义,否则很容易陷入所谓的“一切传引用其实本质上是传值”这种并不能解决问题无意义论战中。
更何况,要想知道Java到底是传值还是传引用,起码你要先知道传值和传引用的准确含义吧?可是如果你已经知道了这两个名字的准确含义,那么你自己就能判断Java到底是传值还是传引用。
这就好像用大学的名词来解释高中的题目,对于初学者根本没有任何意义。
一:搞清楚 基本类型 和 引用类型的不同之处
int num = 10;
String str = "hello";
![](https://i-blog.csdnimg.cn/blog_migrate/9246ea056a543c511430993dfa029e41.png)
如图所示,num是基本类型,值就直接保存在变量中。而str是引用类型,变量中保存的只是实际对象的地址。一般称这种变量为"引用",引用指向实际对象,实际对象中保存着内容。
二:搞清楚赋值运算符(=)的作用
num = 20;
str = "java";
![](https://i-blog.csdnimg.cn/blog_migrate/92a2804c5eecd71171ade35b57c1d8ed.png)
对于基本类型 num ,赋值运算符会直接改变变量的值,原来的值被覆盖掉。
对于引用类型 str,赋值运算符会改变引用中所保存的地址,原来的地址被覆盖掉。但是原来的对象不会被改变(重要)。
如上图所示,"hello" 字符串对象没有被改变。(没有被任何引用所指向的对象是垃圾,会被垃圾回收器回收)
三:调用方法时发生了什么?参数传递基本上就是赋值操作。
第一个例子:基本类型
void foo(int value) {
value = 100;
}
foo(num); // num 没有被改变
第二个例子:没有提供改变自身方法的引用类型
void foo(String text) {
text = "windows";
}
foo(str); // str 也没有被改变
第三个例子:提供了改变自身方法的引用类型
StringBuilder sb = new StringBuilder("iphone");
void foo(StringBuilder builder) {
builder.append("4");
}
foo(sb); // sb 被改变了,变成了"iphone4"。
第四个例子:提供了改变自身方法的引用类型,但是不使用,而是使用赋值运算符。
StringBuilder sb = new StringBuilder("iphone");
void foo(StringBuilder builder) {
builder = new StringBuilder("ipad");
}
foo(sb); // sb 没有被改变,还是 "iphone"。
重点理解为什么,第三个例子和第四个例子结果不同?
下面是第三个例子的图解:
![](https://i-blog.csdnimg.cn/blog_migrate/6b6f007350227a8addfb8e9d453df91c.png)
builder.append("4")之后
![](https://i-blog.csdnimg.cn/blog_migrate/34b5e2aab212b75055026f6c5bd937e3.png)
下面是第四个例子的图解:
![](https://i-blog.csdnimg.cn/blog_migrate/6b6f007350227a8addfb8e9d453df91c.png)
builder = new StringBuilder("ipad"); 之后
![](https://i-blog.csdnimg.cn/blog_migrate/2280616c70498b9e8a92e579da1acfa9.png)
2018年1月31日添加部分内容:
这个答案点赞的不少,虽然当时回答时并没有讲的特别详细,今天就稍微多讲一些各种类型数据在内存中的存储方式。
从局部变量/方法参数开始讲起:
局部变量和方法参数在jvm中的储存方法是相同的,都是在栈上开辟空间来储存的,随着进入方法开辟,退出方法回收。以32位JVM为例,boolean/byte/short/char/int/float以及引用都是分配4字节空间,long/double分配8字节空间。对于每个方法来说,最多占用多少空间是一定的,这在编译时就可以计算好。
我们都知道JVM内存模型中有,stack和heap的存在,但是更准确的说,是每个线程都分配一个独享的stack,所有线程共享一个heap。对于每个方法的局部变量来说,是绝对无法被其他方法,甚至其他线程的同一方法所访问到的,更遑论修改。
当我们在方法中声明一个 int i = 0,或者 Object obj = null 时,仅仅涉及stack,不影响到heap,当我们 new Object() 时,会在heap中开辟一段内存并初始化Object对象。当我们将这个对象赋予obj变量时,仅仅是stack中代表obj的那4个字节变更为这个对象的地址。
数组类型引用和对象:
当我们声明一个数组时,如int[] arr = new int[10],因为数组也是对象,arr实际上是引用,stack上仅仅占用4字节空间,new int[10]会在heap中开辟一个数组对象,然后arr指向它。
当我们声明一个二维数组时,如 int[][] arr2 = new int[2][4],arr2同样仅在stack中占用4个字节,会在内存中开辟一个长度为2的,类型为int[]的数组,然后arr2指向这个数组。这个数组内部有两个引用(大小为4字节),分别指向两个长度为4的类型为int的数组。
![](https://i-blog.csdnimg.cn/blog_migrate/8f434a1b20257bc54b62a544b52141b9.png)
所以当我们传递一个数组引用给一个方法时,数组的元素是可以被改变的,但是无法让数组引用指向新的数组。
你还可以这样声明:int[][] arr3 = new int[3][],这时内存情况如下图
![](https://i-blog.csdnimg.cn/blog_migrate/16eeda4bbb9c2ac4ede178d819943279.png)
你还可以这样 arr3[0] = new int [5]; arr3[1] = arr2[0];
![](https://i-blog.csdnimg.cn/blog_migrate/756a4fa0e2decd2a6b2984c76296ac57.png)
关于String:
原本回答中关于String的图解是简化过的,实际上String对象内部仅需要维护三个变量,char[] chars, int startIndex, int length。而chars在某些情况下是可以共用的。但是因为String被设计成为了不可变类型,所以你思考时把String对象简化考虑也是可以的。
String str = new String("hello")
![](https://i-blog.csdnimg.cn/blog_migrate/bd9bb29877e509f106acd4466093df4e.png)
当然某些JVM实现会把"hello"字面量生成的String对象放到常量池中,而常量池中的对象可以实际分配在heap中,有些实现也许会分配在方法区,当然这对我们理解影响不大。
里面:Hugo Gu的回答我十分的赞同。再次感谢作者。
作者:Hugo Gu
链接:为什么 Java 只有值传递,但 C# 既有值传递,又有引用传递,这种语言设计有哪些好处? - 知乎用户的回答
来源:知乎
著作权归作者所有,转载请联系作者获得授权。
先强调这个问题前半句是真命题。说问题逻辑有问题,说一切都是值传递,都是没理解什么叫引用传递和值传递。
虽然这个问题根本就没有在问“Java是不是值传递”,但是看完其它答案发现,如果不先解释清楚到底什么是值传递,什么是引用传递,后面的好处也无从谈起。只关心好处的请拉到最后。
第一种误解是:Java是引用传递。(这么理解的人,大体会解释说Java的形参是对象的引用所以才叫引用传递。这个解释的错误在于:引用传递这个词不是这个意思,这个词是形容调用方式,而不是参数本质的类型的。所以,即使有人因为明白引用本身也是个值,然后觉得Java其实是值传递了,这种理解也是错的。你这种理解,叫“传递的是值”,而非“值传递”。后面展开。)
第二种误解是:值类型是值传递,引用类型用的是引用传递。
第三种误解是:认为所有的都是值传递,因为引用本质上也是个值,本质就是个指针嘛。
第四种误解是:常出现在C++程序员中,声明的参数是引用类型的,就是引用传递;声明的参数是一般类型或指针的就是值传递。(也有人把指针归为引用传递,其实它比较特殊,无论你归哪边都是错的。)
值传递与引用传递,在计算机领域是专有名词,如果你没有专门了解过,一般很难自行悟出其含义。而且在理解下面的解释时,请不要把任何概念往你所熟悉的语言功能上套。很容易产生误解。比如Reference,请当个全新的概念,它和C#引用类型中的引用,和C++的&,一点儿关系都没有。
值传递和引用传递,属于函数调用时参数的求值策略(Evaluation Strategy),这是对调用函数时,求值和传值的方式的描述,而非传递的内容的类型(内容指:是值类型还是引用类型,是值还是指针)。值类型/引用类型,是用于区分两种内存分配方式,值类型在调用栈上分配,引用类型在堆上分配。(不要问我引用类型里定义个值类型成员或反之会发生什么,这不在这个本文的讨论范畴内,而且你看完之后,你应该可以自己想明白)。一个描述内存分配方式,一个描述参数求值策略,两者之间无任何依赖或约束关系。
在函数调用过程中,调用方提供实参,这些实参可以是常量:
Call(1);
也可以是变量:
Call(x);
也可以是他们的组合:
Call(2 * x + 1);
也可以是对其它函数的调用:
Call(GetNumber());
但是所有这些实参的形式,都统称为表达式(Expression)。求值(Evaluation)即是指对这些表达式的简化并求解其值的过程。
求值策略(值传递和引用传递)的关注的点在于,这些表达式在调用函数的过程中,求值的时机、值的形式的选取等问题。求值的时机,可以是在函数调用前,也可以是在函数调用后,由被调用者自己求值。这里所谓调用后求值,可以理解为Lazy Load或On Demand的一种求值方式。
而且,除了值传递和引用传递,还有一些其它的求值策略。这些求值策略的划分依据是:求值的时机(调用前还是调用中)和值本身的传递方式。详见下表:
<img src="<a href=" https://pic4.zhimg.com/9d4d1d25add61af4442cae8069651e67_b.jpg" data-editable="true" data-title="zhimg.com 的页面"> https://pic4.zhimg.com/9d4d1d25add61af4442cae8069651e67_b.jpg</a>" data-rawwidth="524" data-rawheight="101" class="origin_image zh-lightbox-thumb" width="524" data-original="<a href=" https://pic4.zhimg.com/9d4d1d25add61af4442cae8069651e67_r.jpg" data-editable="true" data-title="zhimg.com 的页面"> https://pic4.zhimg.com/9d4d1d25add61af4442cae8069651e67_r.jpg</a>">
![](https://i-blog.csdnimg.cn/blog_migrate/df549c356b817a45ac20e0b85512d244.png)
看到这里的名传递,可能就有人联想到C++里的别名(alias),其实也是两码事儿。语言层直接支持名传递的语言很不主流,但是在C#中,名传递的行为可以用Func<T>来模拟,说到这儿应该能大概猜出名传递的大致行为了。不过这不是重点,重点是值传递和引用传递。上面给出的传值方式的表述有些单薄,下表列出了一些二者在行为表象上的区别。
<img src="<a href=" https://pic1.zhimg.com/47590cd61b19a99dbe227b470e016fa0_b.jpg" data-editable="true" data-title="zhimg.com 的页面"> https://pic1.zhimg.com/47590cd61b19a99dbe227b470e016fa0_b.jpg</a>" data-rawwidth="474" data-rawheight="73" class="origin_image zh-lightbox-thumb" width="474" data-original="<a href=" https://pic1.zhimg.com/47590cd61b19a99dbe227b470e016fa0_r.jpg" data-editable="true" data-title="zhimg.com 的页面"> https://pic1.zhimg.com/47590cd61b19a99dbe227b470e016fa0_r.jpg</a>">![](https://i-blog.csdnimg.cn/blog_migrate/ce72c5fa2ead7d99a6128d816619fe12.png)
这里的改变不是指mutate, 而是change,指把一个变量指向另一个对象,而不是指仅仅改变属性或是成员什么的(如Java,所以说Java是Pass by value,原因是它调用时Copy,实参不能指向另一个对象,而不是因为被传递的东西本质上是个Value,这么讲计算机上什么不是Value?)。
这些行为,与参数类型是值类型还是引用类型无关。对于值传递,无论是值类型还是引用类型,都会在调用栈上创建一个副本,不同是,对于值类型而言,这个副本就是整个原始值的复制。而对于引用类型而言,由于引用类型的实例在堆中,在栈上只有它的一个引用(一般情况下是指针),其副本也只是这个引用的复制,而不是整个原始对象的复制。
这便引出了值类型和引用类型(这不是在说值传递)的最大区别:值类型用做参数会被复制,但是很多人误以为这个区别是值类型的特性。其实这是值传递带来的效果,和值类型本身没有关系。只是最终结果是这样。
求值策略定义的是函数调用时的行为,并不对具体实现方式做要求,但是指针由于其汇编级支持的特性,成为实现引用传递方式的首选。但是纯理论上,你完全可以不用指针,比如用一个全局的参数名到对象地址的HashTable来实现引用传递,只是这样效率太低,所以根本没有哪个编程语言会这样做。(自己写来玩玩的不算)
综上所述,对于Java的函数调用方式最准确的描述是:参数藉由值传递方式,传递的值是个引用。(句中两个“值”不是一个意思,第一个值是evaluation result,第二个值是value content)
由于这个描述太绕,而且在字面上与Java总是传引用的事实冲突。于是对于Java,Python、Ruby、JavaScript等语言使用的这种求值策略,起了一个更贴切名字,叫Call by sharing。这个名字诞生于40年前。
前面讨论了各种求值策略的内涵。下面以C++为例:
void ByValue(int a)
{
a = a + 1;
}
void ByRef(int& a)
{
a = a + 1;
}
void ByPointer(int* a)
{
*a = *a + 1;
}
int main(int argv, char** args)
{
int v = 1;
ByValue(v);
ByRef(v);
// Pass by Reference
ByPointer(&v);
// Pass by Value
int* vp = &v;
ByPointer(vp);
}
Main函数里的前两种方式没有什么好说,第一个是值传递,第二个函数是引用传递,但是后面两种,同一个函数,一次调用是Call by reference, 一次是Call by value。因为:
ByPointer(vp); 没有改变vp,其实是无法改变。
ByPointer(&v); 改变了v。(你可能会说,这传递的其实是v的地址,而ByPointer无法改变v的地址,所以这是Call by value。这听上去可以自圆其说,但是v的地址,是个纯数据,在调用的方代码中并不存在,对于调用者而言,只有v,而v的确被ByPointer函数改了,这个结果,正是Call by reference的行为。从行为考虑,才是求值策略的本意。如果把所有东西都抽象成值,从数据考虑问题,那根本就没有必要引入求值策略的概念去混淆视听。)
请体会一下,应该就明白上面一直在说的调用的行为的意思。
C语言不支持引用,只支持指针,但是如上文所见,使用指针的函数,不能通过签名明确其求值策略。C++引入了引用,它的求值策略可以确定是Pass by reference。于是C++的一个奇葩的地方来了,它语言本身(模拟的不算,什么都能模拟)支持Call by value和Call by reference两种求值策略,但是却提供了三种语法去做这俩事儿。
C#的设计就相对合理,函数声明里,有ref/out,就是引用传递,没有ref/out,就是值传递,与参数类型无关。
不过如果观察一下void ByRef(int& a)和void ByPointer(int* a)所生成的汇编代码,会发现在一定条件下其实是一样的。都是这个样子:
; 12 : {
push ebp
mov ebp, esp
sub esp, 192 ; 000000c0H
push ebx
push esi
push edi
lea edi, DWORD PTR [ebp-192]
mov ecx, 48 ; 00000030H
mov eax, -858993460 ; ccccccccH
rep stosd
; 13 : *a = *a + 1;
mov eax, DWORD PTR _a$[ebp]
mov ecx, DWORD PTR [eax]
add ecx, 1
mov edx, DWORD PTR _a$[ebp]
mov DWORD PTR [edx], ecx
调用方的代码也是一样的。代码就不贴了。
这两种传递方式说完了,下面回到正题说好处。问题中“这种”指代不明,且认为是Java。
支持多种求值策略可以给语言带来更高的灵活性,但是同时也需要一个“灵活”的人来良好地驾驭。Java通过牺牲这种价值不大还可能带来问题的灵活性,带来了语言自身语法一致性、逻辑鲁棒性及更容易学习等多个好处。
不仅仅Java和C#,每个语言,在设计时都需要在这些特性间做出自己独特的取舍来体现自己的设计理念,并适应不同人,不同使用环境的要求。虽然说没有什么功能是一个语言可以做,而另一个语言做不了的。但是每个语言,都有它最适合的范畴与不适合的范畴。
再举个例子例证下
public class Employee {
public int age;
}
public class Main {
public static void changeEmployee(Employee employee)
{
employee = new Employee();
employee.age = 1000;
}
public static void main(String[] args) {
Employee employee = new Employee();
employee.age = 100;
changeEmployee(employee);
System.out.println(employee.age);
}
}
如果参数是基本类型,传递的是基本类型的字面量值的拷贝。
如果参数是引用类型,传递的是该参量所引用的对象在堆中地址值的拷贝。
- = 是赋值操作(任何包含=的如+=、-=、 /=等等,都内含了赋值操作)。不再是你以前理解的数学含义了,而+ - * /和 = 在java中更不是一个级别,换句话说, = 是一个动作,一个可以改变内存状态的操作,一个可以改变变量的符号,而+ - * /却不会。这里的赋值操作其实是包含了两个意思:1、放弃了原有的值或引用;2、得到了 = 右侧变量的值或引用。Java中对 = 的理解很重要啊!!可惜好多人忽略了,或者理解了却没深思过。
- 对于基本数据类型变量,= 操作是完整地复制了变量的值。换句话说,“=之后,你我已无关联”;至于基本数据类型,就不在这科普了。
- 对于非基本数据类型变量,= 操作是复制了变量的引用。换句话说,“嘿,= 左侧的变量,你丫别给我瞎动!咱俩现在是一根绳上的蚂蚱,除非你再被 = 一次放弃现有的引用!!上面说了 = 是一个动作,所以我把 = 当作动词用啦!!”。而非基本数据类型变量你基本上可以
- 参数本身是变量,参数传递本质就是一种 = 操作。参数是变量,所有我们对变量的操作、变量能有的行为,参数都有。所以把C语言里参数是传值啊、传指针啊的那套理论全忘掉,参数传递就是 = 操作。
这样你就不难理解了。add()函数和append()函数、addNum()函数不同的地方就是add()里面没有对参数进行赋值操作,换句话说就是在add()函数中,参数list始终没有放弃现有的引用,它的所作所为,都直接反应到现有引用的对象上。
而append()函数,一上来第一句话就对str变量进行了 = 操作,也就是str没有瞎动(事实上String类型的不可变性设计也决定了str也瞎动不了),而是很识趣地引用了一个新的对象。
对于add()函数,可以套口诀:1、参数传递本质就是一种 = 操作,= 的左边是add()函数的参变量list,=右边是main函数中给出的list;2、非基本数据类型变量,= 操操作是复制了变量的引用,所以add里的list获得了和main里的list同一对象引用,在add里没有对list进行=的语句,所有它会一直保持该引用,它所作的一切改变都被看在眼里。
假如把add()函数改成这个样子static void add(List<Integer> list){
list = another_list;
list.add(100);
}
那么打印结果里就不会有100了
对于append()函数,可以套口诀:1、参数传递本质就是一种 = 操作,= 的左边是append()函数的参变量str,=右边是main函数中给出的a;2、非基本数据类型变量,= 操操作是复制了变量的引用,3、= 是赋值操作,所以在第一条语句里,str就放弃了原有引用。
再假如把append()函数改成这个样子static void append(String str){
str.addmyself(" is a");
}
咳咳~~假设String类里真有addmys elf方法,那么打印结果就是 A is a
至于addNum()函数,就没啥纠结了,套口诀就行:1、参数传递本质就是一种 = 操作;2、对于基本数据类型变量,= 操作是完整地复制了变量的值。
=====
还有你这道题目,append函数和addNum完全就是充满深深的恶意啊!!!为啥偏偏add函数里没有赋值操作,而是方法调用???!!
所有还是重申一下,Java中对 = 的理解很重要啊!!你理解了第一条定律就理解了一半了。
还有就是一定要记住,把C语言里参数传值传指针那套理论全忘掉, 传参就是 = 操作。其实Java里的所有参数都是“值”传递。之所以用引号括起来是因为值传递容易让人产生误解。
八个基础类型是按照一般人理解的值传递的方式传递的,将变量对应的值复制一份传递给函数。
而其他所有的引用类型依然是按照“值”传递的方式传递的,但是他们传的值不是对象的一份值拷贝,而是指向对象的引用的一份值拷贝,因此你可以在方法内通过传入的对象引用的值拷贝操作外界对象,但是一旦重新让这个引用的值拷贝指向别的对象,那么接下来的一切就跟外部对象没有关系了。
简而言之,引用类型的传递其实就是和C++中的指针传递一样的。因为C++中的指针传递也是传递了一份指针的值拷贝。
根据Horstmann的《java核心技术》(中文第8版P115-P117)的描述,java中是没有引用传递的,原文摘录如下:
“java程序设计语言总是采用值调用。也就是说,方法得到的是所有参数值的一个拷贝,特别是,方法不能修改传递给它的任何参数变量的内容。”
“有些程序员(甚至是本书的作者),认为java程序设计语言对对象采用的是引用调用,实际上这种理解是不对的。”
首先明确一下值传递和引用传递的概念:
值传递:表示方法接收的是调用者提供的值。
引用传递:表示方法接收的是调用者提供的变量地址。
总结起来,java中参数传递情况如下:
· 一个方法不能修改一个基本数据类型的参数
· 一个方法可以修改一个对象参数的状态
· 一个方法不能实现让对象参数引用一个新对象
这个问题困扰了我一个下午,查了好多资料,这里为了精简,挑简单的给题主回复了一下,详细代码可以移步csdn:java引用传递和值传递的详细探讨
你这个问题其实很简单。要搞清楚这个问题,要明白:堆 和 栈。引用是保存在栈上,对象是保存在堆中。引用指向堆上的对象,也就是说引用的值为对象在栈上的内存地址。那么你修改引用时改的是引用的值,也就是让引用指向其它对象。那么应该怎么修改堆上的对象呢?首先你得访问到堆上的对象吧?如何访问到它呢?在C/C++中是通过指针运算符 *p 来访问到指针p指向的堆上的对象的,然后再修改它。那么Java中没有指针运算符,那么怎么办呢?Java中是通过点操作符,也就是 list.add中的那个点,表示先访问到list这个引用指向的对象,然后让该对象调用 add 方法,从而修改了list指向的堆上的对象。所以当你单独修改 list = xxx;时你修改的是引用,让list引用指向其它对象,而没有修改 list 引用指向的对象,因为你根本就没有访问到堆上的对象,你怎么修改它呢?
所以:要修改堆上的对象,你要先访问到它,不然你就不能修改它。访问堆上的对象的方法就是 . 点操作符。
没有访问到如何修改呢?
public static void main(String[] args) {
String x = new String("ab");
change(x);
System.out.println(x);
}
public static void change(String x) {
x = "cd";
}
![](https://i-blog.csdnimg.cn/blog_migrate/6f51bd0e6c074631b6e5cbcd8a63cd1e.jpeg)
Java的方法参数传递只有一种,就是 “pass-by-value”,也就是值传递。
- 如果是基本类型,就是将原有的数据拷贝一份,方法内的操作对原有的数据不会有影响。
- 如果是对象类型,这里是容易误解的地方,因为正好规定对象的地址也叫做"reference", 我们将对象作为参数传递的时候实际上是将对象的地址传递进去。
第一种很好理解,我们主要来分析第二种。
有这么一个方法:
public void tricky(Point arg1, Point arg2) {
arg1.x = 100;
arg1.y = 100;
Point temp = arg1;
arg1 = arg2;
arg2 = temp;
System.out.println("Inside func arg1 x: " + arg1.x + ", y: " + arg1.y);
System.out.println("Inside func arg2 x: " + arg2.x + ", y: " + arg2.y);
}
主函数:
public static void main(String[] args) {
Point p1 = new Point(2, 3);
Point p2 = new Point(2, 3);
System.out.println("p1 x: " + p1.x + ", y: " + p1.y);
System.out.println("p2 x: " + p2.x + ", y: " + p2.y);
tricky(p1, p2);
System.out.println("p1 x: " + p1.x + ", y: " + p1.y);
System.out.println("p2 x: " + p2.x + ", y: " + p2.y);
}
请试着写出所有的输出。
(不许偷看)
========================================================================
p1 x: 2, y: 3
p2 x: 2, y: 3
Inside func arg1 x: 2, y: 3
Inside func arg2 x: 100, y: 100
p1 x: 100, y: 100
p2 x: 2, y: 3
我们来逐一分析:
第1,2条容易,新建两个Point对象,赋值(2,3)
![](https://i-blog.csdnimg.cn/blog_migrate/09e9984bf2411de7d6938843eaedf14c.png)
此时输出:
p1 x: 2, y: 3
p2 x: 2, y: 3
进入到方法中,p1和p2的对象地址传给arg1和arg2。所以arg1和arg2也指向了对应的对象:
![](https://i-blog.csdnimg.cn/blog_migrate/2190f145a08a689fe262bbf1f57101b1.png)
在tricky()方法中:
arg1.x = 100;
arg1.y = 100;
因此其所指的对象内的值发生了变化(注意这里地址没有改变)。
![](https://i-blog.csdnimg.cn/blog_migrate/67ab950122e2bb7af290921f8409ae25.png)
Point temp = arg1;
arg1 = arg2;
arg2 = temp;
![](https://i-blog.csdnimg.cn/blog_migrate/0e2f6883596c9054cc54bbba8593096e.jpeg)
观察上图,这时方法内的输入如下:
Inside func arg1 x: 2, y: 3
Inside func arg2 x: 100, y: 100
跳出方法后:
![](https://i-blog.csdnimg.cn/blog_migrate/1b9c37ce9f6af31ab0acd415e28f33e5.png)
此时p1和p2的输出如下:
p1 x: 100, y: 100
p2 x: 2, y: 3
最后请记住,
值传递
值传递
值传递!
即使传递的对象,也是传递对象的地址(英文就叫reference了)的值!
Happy Coding!
方法参数有2种类型:
1.基本数据类型(int,double,....)
2.对象引用
如果说你是call by reference 那么下面的代码将会交换A , B2个对象
void swap( test A , test B ) {
test C = A;
A = B;
B = C;
}
然而 你可以去试一下 并没有交换。 交换的只是拷贝出来的2个test对象。
总结来看
1.一个方法不能修改一个基本数据类型的参数
2.一个方法可以改变一个对象参数的状态
3.一个方法不能让对象参数引用一个新的对象
PS:来源Java 核心技术卷I 感觉这里写的蛮好 就选择了一些给题主看下。
Intopass 的回答很详细了。