值传递和引用传递、对象类型与基本类型、实参与形参之间、栈堆之间的七七八八
数据和变量的分类
首先,我们需要大概明白,java中的数据类型分为两类,基本类型与对象类型(一些特殊的如String后续讨论)
基本类型:byte,short,int,long,char,float,double,boolean,returnAddress
对象类型:类类型,接口类型和数组
以上数据类型是针对数据本身特点而言的,如果我们要使用他们,也就是数据成为了变量,此时结合数据在内存中的保存方式,对应的叫法成为了基本类型与引用类型
基本类型保存的是对象本身的值,而引用类型保存的是引用的值,也就是对象地址的值。
数据是怎么存的
数据是怎么存的,其实是个很复杂的话题,需要涉及到很多java内存模型的知识,此处不再深入拓展。从以下场景的代码来简单说明下
// 首先需要明白,People这个对象实例化就是放在堆中的,所以它里面的成员变量自然也是在堆中的。
Class People{
// age是一个基本类型
int age;
// child是一个引用类型
People child;
void setAge(int age){
this.age = age;
}
void setChild(int child){
this.child= child;
}
}
Class People Test(){
public void createPeople(){
// 这一步只是声明了一个引用类型的变量man,并为他在方法栈中分配了内存空间,用来存放之后对象的地址值
People man;
// 这一步在内存中创建了一个对象,并将对象的地址值(比如A0X456465之类的)赋值给了man这个引用变量
man = new People()
// 这一步是在方法栈中声明了一个基本类型的变量age,并为他分配了内存用来存储数据本身。
int age;
// 这里的10是存在方法栈中的。
age = 10;
// 这里将方法区中定义并赋值的基本变量age的值赋值给了引用变量man所指向的对象的成员变量age。所以man.age的值是存在堆中的。
man.setAge(age);
// 这里将一个堆中对象child的地址值赋值给了man中的引用成员变量child。所以其实man.child与方法中定义的child这两个引用变量,指向的是同一个堆中的对象所在的地址
People child = new People;
man.setChild(child);
}
}
实参与形参
// 这里的age是实参
int age = 10;
// 方法签名里的就是形参
setAge(age);
引用传递和值传递
值传递
值传递就是实参把它的值传给形参,基本数据类型都是值传递,即函数接受的是实参的一个copy,内存中存在两份参数,也就是两份变量和值,方法中对参数的修改不会影响原本实参的值
引用传递
引用传递就是实参把引用传给形参,对象类型都是引用传递,即函数接受的是对象的地址值,内存中存在一份参数,一份变量和值,方法中对参数的修改会影响原本实参的值
结合存储方式做理解
其实造成这种区别的原因,结合上面数据的存储方式就很好理解了,不管是那种传递,传递本身的操作是一样的。
比如我定义了一个变量a,传递给方法test(),就是把这个变量a的值copy一份传给test()。
只不过当变量a的类型是基本数据类型的时候,a存储的就是值本身,传递的是一份原始值的copy,所以我们在方法中对传进来的a做修改,并不会影响到最初的a。
当变量a的类型是引用类型的时候,a存储的是引用本身,传的自然是引用的copy,我们在方法中对a做修改的时候,如果是对a指向的对象做修改(比如a.setAge(50)这种操作,这里假设age是一个成员变量),那么由于两个引用指向的是同一块堆中的内存,自然是有影响的。
如果是a = new A()这种操作,是不会影响到之前的a的,因为它没有对之前堆中对象做修改,只是改变了引用本身的值。
特殊情况
对于String、Integer、Double等几个基本类型包装类,都是immutable类型(为了深度拷贝方便),尽管他们是引用类型的变量,但是由于没有提供自身修改的函数,所以每次对对象的修改操作都会形成新的对象,即在堆中开辟新的内存,所以它的效果和值传递是一样的