在学习参数传递的时候,我们学习了两种参数传递方式,第一种参数为基本类型的称为值传递,第二种参数为封装类型(引用数据类型)的称为引用传递。
先来说一下回顾基本数据类型:
- 整型:byte short int long
- 浮点型:float double
- 布尔型:Boolean
- 字符型:char
引用数据类型:
- 类 class
- 接口 interface
- 数组 Array
下面先举例基本类型参数👇
public class Test {
public static void main(String[] args) {
int num = 0 ;
changeNum(num);
System.out.println("num="+num);
}
private static void changeNum(int num) {
num = 1;
}
}
运行的结果是:num=0。
再举例引用数据类型参数👇
class Product {
private int num;
private String proName;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getProName() {
return proName;
}
public void setProName(String proName) {
this.proName = proName;
}
}
public class Test {
public static void main(String[] args) {
Product p = new Product();
p.setProName("before");
p.setNum(0);
changeProduct(p);
System.out.println("p.proName="+p.getProName());
System.out.println("p.num="+p.getNum());
}
private static void changeProduct(Product p) {
p.setProName("after");
p.setNum(1);
}
}
运行的结果是:p.proName=after和p.num=1 。
上面两个例子明显是值传递和引用传递。下面说明一下参数是String类型的情况👇
public class Test {
public static void main(String[] args) {
String str = "ab";
changeString(str);
System.out.println("str="+str);
}
private static void changeString(String str) {
str = "cd";
}
}
按照前面的例子,String是一个引用类型,它应该是引用传递,是可以改变值的,所以我们认为运行的结果应该是str=‘cd’。
但是实际运行呢?str=‘ab’。
下面说一下基本数据类型变量和引用数据类型变量在内存中的存放机制👇。
- 基本数据类型变量放在栈里,存放的是具体的值。
- 引用数据类型变量的引用放在栈里,存放的是对象地址,对象放在堆里。
- 举例:int num = 0 ; Product p = new Product();
java在传递参数时,是将变量复制一份,然后传入方法体执行。
先来解释一下基本数据类型的传递👇
- 虚拟机为num分配一个地址,并存入0。
- 虚拟机复制一个num,我们叫它num’,num和num’的内存地址不同,但存的值都是0。
- 虚拟机把num’传入方法,方法将num’的值改为1。
- 方法结束,方法外打印num的值,由于num内存中的值没有改变,还是0,所以打印是0。
再来解释一下引用数据类型数据的传递👇
- 虚拟机在堆中开辟一块存放 Product 的内存,里面有变量 proName和num,这块内存有个地址。
- 虚拟机在栈中给p分配一个地址,这个地址就是堆中Product的地址。
- 虚拟机复制一个p,我们称作p’,p和p’的内存地址不同,但它们存的值是相同的,都是 都是1中Product的内存地址。
- 将p’传入方法,方法改变了1中的proName和num。
- 方法结束,方法外打印p中变量的值,由于p和p’中存的都是1中Product的地址,但是1中Product里的值发生了改变, 所以,方法外打印p的值,是方法执行以后的。我们看到的效果是封装类型的值是改变的。
最后我们来解释 String类型的在传递过程👇
- 虚拟机在堆中开辟一块内存,并存值”ab”。
- 虚拟机在栈中分配给str一个内存,内存中存的是1中的地址。
- 虚拟机复制一份str,我们叫str’,str和str’内存不同,但存的值都是1的地址。
- 将str’传入方法体。
- 方法体在堆中开辟一块内存,并存值”cd”。
- 方法体将str’的值改变,存入5的内存地址。
- 方法结束,方法外打印str,由于str存的是1的地址,所有打印结果是”ab”。
以上内容摘自路人而已 的文章,原文地址https://blog.csdn.net/u010469514/article/details/80838678。
下面是我自己的一些想法👇
String的确是引用类型,但是它很特殊, JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。 JDK1.8开始,取消了Java方法区,取而代之的是位于直接内存的元空间(metaSpace)。 但无论存放在哪里,它存储的基本原理还是不变的。
下面看好一段代码 👇
public static void main(String[] args) {
String str1 = "abc";
String str2 = str1;
str1 = "def";
System.out.println(str2);
}
str1变量改变的时候,没有根据地址值去堆里的Stringconstant pool 找内存再去改变值,而是在堆中新开辟一块内存,在这块内存里有新的值,并把新的地址0x222赋给变量str1,而str2存放的依旧是0x111,所以str2的值不变。
再说一下创建String变量时需要注意的地方,再看一段代码👇
public static void main(String[] args) {
//下面两行代码创建了3个字符串对象,都在常量池当中
String s1 = "abc";
String s2 = "abc" + "xy";
String s3 = new String("xy");
}
- 创建s1时,在堆中的Stringconstant pool 开辟一块内存,存放"abc",然后把这块内存的地址值赋给栈中的s1。
- 创建s2时,因为已经有了"abc"对象,不需要再创建,而是创建"xy"对象,再拼接计算,创建"abc"对象。这也就是谁说,当我们再次创建一个字符串的时候,jvm会先去String constant pool 中检索这个这个常量是否存在,如果存在则将引用返回,如果不存在,则创建新的常量,然后将引用返回。
String str1 = "abc";
String str2 = "abc";
System.out.println(str1 == str2);//true
//当比较基本数据类型,==比较的是值,当比较引用数据类型,==比较的是地址值,所以返回true。
//之后的文章会讲解==和equals方法
- 创建s3用了new关键字,new关键字是实例化对象的,存在堆内存当中,而"xy"是在方法区的字符串常量池当中,所以s3保存的是一个堆内存对象的地址,堆中对象保存的是常量池"xy"的内存地址。