String类型的参数传递
JAVA的数据类型和引用类型的参数传递是java初学者们经久不衰的话题
利用空余的时间,我给大家大概的总结了下参数传递知识点,随便扩展下String类型的参数传递方式
相信之前大家都对数据类型和引用类型的参数传递有所了解,废话不多说,直接看码
static void change(int methodVar) {
System.out.println("change before,x=" + methodVar); //10
methodVar = 100;
System.out.println("change after,x=" + methodVar); //100
}
public static void main(String[] args) {
int mainVar = 10;
System.out.println("main before,a=" + mainVar); //10
change(mainVar);
System.out.println("main after,a=" + mainVa); //10
}
运行结果如下
其内部实现原理是这样
然后是引用类型的参数传递的例子
static void change(int[] methodArr) {
System.out.println("change before,arr[0]=" + methodArr[0]); //10
methodArr[0] = 30;
System.out.println("change after,x[0]=" + methodArr[0]); //30
}
public static void main(String[] args) {
int[] mainArr = new int[] {
10, 90 };
System.out.println("main before,a[0]=" + mainArr[0]); //10
change(mainArr);
System.out.println("main after,a[0]=" + mainArr[0]); //30
}
运行结果如下
这里我们得出一个小结
-
传递基本类型参数:传递参数值 (拷贝参数值)
-
传递引用类型参数:传递参数所引用的堆空间地址值
说到了引用类型,我们之前了解的String类型也是引用类型
这里我们不妨试一下用String类型传递参数会发生什么
static void change(String methodStr) {
System.out.println("change before:"+methodStr); //小哥哥
methodStr = "小姐姐";
System.out.println("change after:"+methodStr); //小姐姐
}
public static void main(String[] args) {
String mainStr = "小哥哥";
System.out.println("main before:"+ mainStr); //小哥哥
change(mainStr);
System.out.println("main after:"+ mainStr); //小姐姐 ? 小哥哥
}
小伙伴们先思考一下 最后控制台输出的是小哥哥 还是 小姐姐
|
|
|
|
思考区
|
|
|
|
|
|
|
|
代码结果
看到结果的时候,小伙伴们可能会有疑惑?
引用类型传递参数的方式不应该是传递参数所引用的堆空间地址值吗?
为什么输出的结果是小哥哥
这个时候不出意外的话会有些学过面对对象的小伙伴抢着回答,说这还不简单!
这位踊跃的小伙伴说的很有道理
这里我们先明白一个知识点:堆内存空间是用来存储实例对象和数组,通俗的来说 就是使用new关键字
可代码里没有new关键字来创建String对象使用
那 String mainStr = “小哥哥”; 这个代码的底层到底是怎样实现的呢。
所谓知其然必先知其所以然,这时我们先来了解一下
JVM内存的大概模型
在了解JVM内存大概模型之前先温习一下之前学的内容
- 堆:存放对象实例和数组
- 栈:也称虚拟机栈,每个方法被执行的时候都会同时创建一个栈帧,用来存储该方法的局部变量、操作数栈(所谓线程实际的操作台)等…
- 方法区:“类”(class文件)被加载后的信息,常量,静态变量等数据存放在这里
这里我们重点在于常量池
而class文件中的常量池是啥东西呢?
其中文本字符串这些字面量就被加载到字符串常量池
那字符串常量池又是什么?
在工作中String类是我们使用频率非常高的一种对象类型,JVM为了避免字符串的重复创建,增加不必要的内存开销,在堆中开辟了一个空间,用来维护String类,以用来提升性能和减少内存开销。
这个空间就是字符串常量池又称:字符串池(String Pool)
通俗的讲:当一个String类对象要调用某个字符串,它可以先去字符串常量池里找找有没有它想要的,而不是一股脑得new,这样就增加不必要得内存开销
废话不多说,下面实际操作一下Java中的两种创建字符串对象的方式:
- 采用字面量的方式创建String类型对象
public class StringDemo {
public static void main(String[] args) {
String str1="StringPool";
String str2="StringPool";
System.out.println(str1==str2);
}
}
运行结果------------------- true
- 采用new关键字的方式创建String类型对象
public class StringDemo {
public static void main(String[] args) {
String str1=new String("StringPool");
String str2=new String("StringPool");
System.out.println(str1==str2);
}
}
运行结果------------------- false
这里就体现了字符串池的缺点,就是需要牺牲JVM在常量池中遍历对象所需要的时间,不过其时间成本相比而言还是比节省堆内存小很多。
了解字符串池的大概过程后,让我们回过头来看先前的例子
```go
public static void main(String[] args) {
//先判断字符串常量池里是否存在"小哥哥",没有,则把"小哥哥"载入字符串常量池,设地址为0x11
String mainStr = "小哥哥";
//这里的mainStr变量引用地址为0x11---"小哥哥" 输出 "小哥哥"
System.out.println("main before:"+ mainStr); //小哥哥
change(mainStr);
//由于mainStr引用的地址0x11内容没有被修改,所以输出 ---小哥哥
System.out.println("main after:"+ mainStr); //小姐姐 ? 小哥哥
}
//mainStr变量调用change方法时,把引用的地址拷贝给方法参数 methodStr变量
static void change(String methodStr) {
//这里的+methodStr变量引用地址为0x11---"小哥哥" 输出 "小哥哥"
System.out.println("change before:"+methodStr); //小哥哥
//先判断字符串常量池里是否存在"小姐姐",没有,则把"小姐姐"载入字符串常量池,设地址为0x22
methodStr = "小姐姐";
//这里的methodStr变量引用地址为0x22---"小姐姐" 输出 "小姐姐"
System.out.println("change after:"+methodStr); //小姐姐
}
小试牛刀
public class StringDemo {
public static void main(String[] args) {
String str1 = new String("strA");
String str2 = new String("strB");
String str3 = "strA";
String str4 = "strB";
String str5 = "strA";
System.out.println(str1 == str2);
System.out.println(str3 == str4);
System.out.println(str3 == str5);
System.out.println(str1.equals(str2));
System.out.println(str3.equals(str4));
System.out.println(str1.equals(str3));
}
}
|
|
|
|
思考区
|
|
|
|
|
|
|
|
代码结果
public class StringDemo {
public static void main(String[] args) {
String str1 = new String("strA");
String str2 = new String("strB");
String str3 = "strA";
String str4 = "strB";
String str5 = "strA";
System.out.println(str1 == str2); //false
System.out.println(str3 == str4); //false
System.out.println(str3 == str5); //true
System.out.println(str1.equals(str2)); //false
System.out.println(str3.equals(str4)); //false
System.out.println(str1.equals(str3)); //true ????
}
}
到这里就会有小伙伴们感到疑惑了? equals() 方法比较的不是两个对象的引用地址吗?
str1,str2这两者井水不犯河水,一个地址存在堆中,一个地址存在字符串常量池中
equals()方法对比后的结果为什么是ture呢?
String类equals()方法扩展
原来String类底层已经重写 (覆盖)了Object类的equals()方法
IDEA中 鼠标对着String类型对象调用的equals()方法 ctrl + 左键 查看被重写后的toString()方法;
public boolean equals(Object anObject) {
//首先用==比较,如果相等,说明就是同一个对象,肯定是相等的
if (this == anObject) {
return true;
}
//前置判断:是否是String类型,否则肯定不相等
if (anObject instanceof String) {
String aString = (String)anObject;
//coder方法其实就是获取字符串采用的编码方式,如果编码方式都不一样,肯定结果为false
if (coder() == aString.coder()) {
//根据数据是否是压缩数据,采用不同的比较方式
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
看不懂没关系,我们再看下内部的equals()方法
哎,这不就是我们熟悉的数组了吗?
下面就是我们之前所看的源码的整合
public boolean equals(Object anObject) {
//首先通过(this == anObject)判断两个对象堆内存地址是否相同
if (this == anObject) {
return true;
}
//判断anObject对象是否为String类的实例 如果是则再进行判断,不是直接返回false
if (anObject instanceof String) {
//将anObject对象转为String类型
String anotherString = (String)anObject;
int n = value.length;
//然后通过判断两个String对象的字段长度是否一样 如果长度一样则再进行判断,不是直接返回false
if (n == anotherString.value.length) {
//把两个String对象转为字符数字
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
//通过while循环判断两个String对象的value数组中的每一个字符是不是相同的
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
下面是我对String类的equals()方法源码的总结:
比较地址
-
true返回true
-
false ---------> 1–判断类型,2–比较长度,3–对比字符
了解完了源码之后回头再看此题
public class StringDemo {
public static void main(String[] args) {
String str1 = new String("strA");
String str3 = "strA";
/*
1.比较地址为false,进一步比较其"内容"
2.str3是String类的实例,true,继续下一个判断
3.str1 str3的内容字段长度一样 返回true,最后进行字符的一一对比
4.通过遍历char[]数组对比str1 str3的字符之后,最后返回true
*/
System.out.println(str1.equals(str3));
}
}
这下小伙伴们是不是觉得自己在java的小白路上又进一大步
最后,给大家留一道题
String a = ''a" + ''b"+ ''c";
问:这句代码运行之后在字符串常量池里面创建了几个对象?