值传递和引用传递
此篇博客只是copy下列博客或文章整理出来的,谢绝转载,侵权删除:
https://www.cnblogs.com/dolphin0520/p/3592498.html
https://zwmf.iteye.com/blog/1738574
https://www.cnblogs.com/aspirant/p/10320652.html
https://www.zhihu.com/question/31203609
概念
- 按值调用:一个方法接收的是调用着提供的值(值传递)
- 按引用调用:一个方法接收的是调用者提供的变量地址(如果是C语言的话来说就是指针啦,当然java并没有指针的概念)。
如果想搞明白值传递和引用传递需要搞明白对象
、对象引用
一、对象
与对象引用
对象
首先我们应该明白
对象
与对象引用
不是一回事。
《Java编程思想》原话说:“按照通俗的说法,每个对象都是某个类(class)的一个实例(instance),这里,‘类’就是‘类型’的同义词。”
从这句话可以得知:
对象
是类的实例
。比如,可以把人类
看做类的实例
,那么具体到某个人的张三
他就是对象
.
对象
与对象引用
再读《Java编程思想》的一段话:“每种编程语言都有自己的数据处理方式。有些时候,程序员必须注意将要处理的数据是什么类型。你是直接操纵元素,还是用某种基于特殊语法的间接表示(例如C/C++里的指针)来操作对象。所有这些在 Java 里都得到了简化,一切都被视为对象。因此,我们可采用一种统一的语法。尽管将一切都“看作”对象,但操纵的标识符实际是指向一个对象的“引用”(reference)。”
"操纵的标识符实际是指向一个对象的“引用”(reference)"这句话明确的指出
引用
和对象
不是一回事。
为了方便说明,定义一个简单的类:
class Person {
String name;
int age;
}
有了这个类,就可以用它来创建对象:
Person person1 = new Person();
通常把这条语句的动作称之为创建一个对象,其实,它包含了四个动作。
- 1)右边的“new Person”,是以Person类为模板,在堆空间里创建一个Person类
对象
(也简称为Person对象)。 - 2)末尾的()意味着,在对象创建后,立即调用Person类的构造函数,对刚生成的对象进行初始化。构造函数是肯定有的。如果你没写,Java会给你补上一个默认的构造函数。
- 3)左边的“Person person1”创建了一个Person类的
引用变量
。所谓Person类引用
,就是以后可以用来指向Person这一类对象
的对象引用
。 - 4)“=”操作符使
对象引用
指向刚创建的那个Person类的对象
。
为了更加清楚了解原理,把Person person1 = new Person()
这条语句拆成两部分:
Person person1;//第一句
person1 = new Person(); //第二句
为了形象地说明对象、引用及它们之间的关系,可以做一个或许不很妥当的比喻。
对象
好比是一只很大的气球,大到我们抓不住它。引用变量
是一根绳, 可以用来系汽球。
如果只执行了第一条语句,还没执行第二条,此时创建的
引用变量
person1还没指向任何一个对象
,它的值是null.(引用变量
可以指向某个对象
,或者为null。)
此时的person1好比一根还没有系上任何一个汽球的绳1。
执行了第二句后,一只
汽球1
做出来了(new一个Person类对象),并被系在person1这根绳上。我们抓住这根绳,就等于抓住了那只汽球1
。
在原来的基础上增加多几句代码:
Person person1;//第一句
person1 = new Person(); //第二句
Person person2; //第三句
person2 = person1; //第四句
person2 = new Person(); //第五句
person1 = person2; //第六句
执行到第三句代码就又做了一根绳2,还没系上汽球。
执行第四句代码,这里发生了复制行为,名为person1的
对象引用
复制给了person2(要说明的是,对象本身并没有被复制),person2也是对象引用
,相当于绳2也系在汽球1上。
执行第五句代码,则引用变量person2改指向第二个对象,绳2系向新的汽球。
-
从以上叙述再推演下去,我们可以获得以下结论:
- 1.一个
对象引用
可以指向0个或1个对象
(一根绳子可以不系汽球,也可以系一个汽球); - 2.一个
对象
可以有N个引用
指向它(可以有N条绳子系住一个汽球)。
- 1.一个
按上面的推断,person1也指向了第二个
对象
。这个没问题。问题是第一个对象
呢?没有一条绳子系住它,它飞了。多数书里说,它被Java的垃圾回收机制回收了。这不确切。正确地说,它已成为垃圾回收机制的处理对象。至于什么时候真正被回收,那要看垃圾回收机制的心情了。
由此看来,下面的语句应该不合法吧?至少是没用的吧?
new Person();
它是合法的,而且可用的。譬如,如果我们仅仅为了打印而生成一个对象,就不需要用引用变量来系住它。最常见的就是打印字符串:
System.out.println(“Hello World”);
字符串对象“Hello World”在打印后即被丢弃。有人把这种对象称之为临时对象。
对象与引用的关系将持续到对象回收。
值传递
- Java只有一种参数传递方式:那就是按值传递,即Java中传递任何东西都是传值。如果传入方法的是基本类型的东西,你就得到此基本类型的一份拷贝。如果是传递引用,就得到引用的拷贝。
基本类型
public class TestBasicType {
//基本类型的参数传递
public static void testBasicType(int m) {
System.out.println("m=" + m);//m=50
m = 100;
System.out.println("m=" + m);//m=100
}
public static void main(String[] args) {
int i = 50;
testBasicType(i);
System.out.println("i=+"+i);//i=50
//只是将值复制给参数m,m和i是两回事
}
}
引用类型
public class Person {
public int age;//Person的属性,全局变量,初始age为0
public void addAge(){
this.age = this.age+1;
}
public static void changeAge(Person p){
Person p1 = p; //这里将对象引用p复制给p1,p和p1指向同一对象,因此无论p还是p1操纵对象,对象的内容都会改变
p1.addAge(); //p1操纵 对象发生改变,p1指向的对象的age=1
System.out.println("p1.age="+p1.age); //因此p1.age=1
}
public static void main(String[] args) {
Person p = new Person();//创建一个Person对象,Person p 创建一个Person类的对象引用
changeAge(p);//将引用p复制给了这个方法的形参
System.out.println("p.age="+p.age);//p和p1指向同一对象,因此p.age=1
}
}
可以将上面的p1比喻为汽车的刹车,p比作汽车的油门,汽车的速度同时受到油门和刹车的控制,无论踩下油门还是刹车,
车速必有变化。当然控制车速的还会有离合,可以假设离合为新的并指向车速的引用p2,那么车速就由p,p1,p2控制,也就是说可以多个引用指向一个对象。
下面我们通过一个反例来证明值传递
public class CallByValue {
private static User user=null;
private static User stu=null;
/**
* 交换两个对象
* @param x
* @param y
*/
public static void swap(User x,User y){
User temp =x;
x=y;
y=temp;
//只是复制了引用给了参数x,y
//x,y变化影响不了外面的引用user,stu
}
public static void main(String[] args) {
user = new User("user",26);
stu = new User("stu",18);
System.out.println("调用前user的值:"+user.toString());
System.out.println("调用前stu的值:"+stu.toString());
swap(user,stu);
System.out.println("调用后user的值:"+user.toString());
System.out.println("调用后stu的值:"+stu.toString());
}
}
调用前user的值:User [name=user, age=26]
调用前stu的值:User [name=stu, age=18]
调用后user的值:User [name=user, age=26]
调用后stu的值:User [name=stu, age=18]
即使java函数在传递引用数据类型时,也只是拷贝了引用的值罢了,之所以能修改引用数据是因为它们同时指向了一个对象,但这仍然是按值调用而不是引用调用。
- 总结:
- 一个方法不能修改一个基本数据类型的参数(数值型和布尔型)。
- 一个方法可以修改一个引用所指向的对象状态,但这仍然是按值调用而非引用调用。
- Java只存在值传递。上面两种传递都进行了值拷贝的过程。
"="号
Java中对 = 的理解很重要啊!!
-
=
是赋值操作
:在java中,=
是一个动作,一个可以改变内存状态的操作,一个可以改变变量的符号,而+ - * /却不会。 -
这里的
赋值操作
其实是包含了两个意思:- 1.放弃了原有的值或引用.
- 2.得到了
=
右侧变量的值或引用。
-
对于基本数据类型变量,
=
操作是完整地复制了变量的值。换句话说,“=之后,你我已无关联”. -
对于非基本数据类型变量,
=
操作是复制了变量的引用。 -
参数本身是变量,所有我们对变量的操作、变量能有的行为,参数都有,参数传递本质就是一种
=
操作。