欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

值传递和引用传递、对象类型与基本类型、实参与形参之间、栈堆之间的七七八八

程序员文章站 2022-05-07 13:02:51
...

数据和变量的分类

首先,我们需要大概明白,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类型(为了深度拷贝方便),尽管他们是引用类型的变量,但是由于没有提供自身修改的函数,所以每次对对象的修改操作都会形成新的对象,即在堆中开辟新的内存,所以它的效果和值传递是一样的