同样是引用类型 为什么数组可以被修改后被传出而String不能在方法内被修改后传出 (Java、堆、栈、垃圾回收)

引用类型传参到方法内部后进行修改数据发生了怎样的变化?String类型传参无法做到在方法内部修改后传出方法外?

引言

 假设有一个要求是这样的,需要你写一个方法在方法内实现对一个数组的排序,那么这应该是很简单的,没有什么可疑问的。现在有一个同样类似的的要求,需要你写一个方法,在方法内部对一个字符串"csdn"进行变换,将其转为大写的字符串"CSDN",我们又该怎么做呢?还是与数组排序那样操作就可以吗?事实上通过实验我们发现并非看上去这么简单,下面就让我们一起讨论其中的一些问题吧!


问题引入

问题一

  • 请实现一个方法,数组传参,在该方法内对该数组进行排序(升序或降序任意),并在方法外输出。

问题二

  • 请实现一个方法,字符串传参,在该方法内对该字符串进行大小写转换,并在方法外输出,问方法外输出的是否为转换后的字符串?

 第一个问题看起来很简单,相信每个同学都能很轻易做到,第二个问题呢?你是不是想,直接在方法内直接对原字符串进行修改不就好了,这有何难?如果是这样那你就掉进了一个坑里了,或者说你对Java的堆、栈了解得并不是十分透彻。下面让我们来看看具体的代码吧!


程序示例

这里我们将两个问题的代码放在一个类里实现了,具体请看如下代码。

public class ChangeWorlds {

	public static void main(String[] args) {
		//数组排序部分
		int[] arr = {1,4,2,3};
		System.out.print("排序前:");
		for(int i = 0; i< arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		sort(arr);
		System.out.println();
		System.out.print("排序后:");
		for(int i = 0; i< arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
		//字符串转换大写部分
		String str = "csdn";
		System.out.println("变换前:" + str);
		change(str);
		System.out.println("变换后:" + str);
	}
//	为了方便我们直接对字符串进行赋值大写的CSDN
	public static void change(String str) {
		str = "CSDN";
		System.out.println("方法内输出:" + str);
	}
	
//	数组的排序方法  用来对比String
	public static void sort(int[] arr) {
		for (int i = 0; i < arr.length; i++) {
			for(int j = i +1; j < arr.length; j++) {
				if(arr[i]>arr[j]) {
					int temp = arr[i];
					arr[i] = arr[j];
					arr[j] = temp;
				}
			}
		}
	}
}

代码介绍

  •  我们在ChangeWorlds 类中实现了两个方法,sort()和change(),都是不返回参数的,如果直接返回参数的话我们这里的问题就毫无意义了,因此才不返回参数。
  •  在main()方法内,我们先创建了一个数组,然后将该数组传入sort()方法进行排序,并且在main()方法中对该数组排序前后都做了一次输出。
  •  我们又创建了一个String对象,请注意这里我用的是“创建”这个词,并且同上面的数组一样传入change()方法,并在方法内修改字符串,修改前后在main()方法中输出了两次以观察进入方法前后该字符串发生的变化。
  •  并且在change()方法内,我们还增加了一次输出用来观察变化情况。

输出结果

排序前:1 4 2 3 
排序后:1 2 3 4 
变换前:csdn
方法内输出:CSDN
变换后:csdn

 通过输出结果我们可以看到,数组的排序功能完美实现了,但是字符串在变换前后没有发生任何变化!但它在方法内部的输出却发生了变化。你可能会想,这还不简单,这就就是值传递吗,你在方法内修改它肯定不能修改到字符串本身。先不要着急,问题或许不是那么简单,我们到下一步接着进行详细分析。


分析

在这里插入图片描述
 刚刚说了,数组排序完美实现,但字符串在方法前后没有发生任何变化,而在方法内却发生变化了。有的同学可能瞬间就想到了,这不就是和值传递的情况一模一样吗!看起来这确实和值传递一模一样,但你可不要忘了,String是什么类型?他可不是基本数据类型,它是引用数据类型,引用数据类型是不会进行值传递的!就和数组一样,数组也是引用数据类型,这才能够在方法修改后传出方法外。
在这里插入图片描述
 那就有大问题了,都是引用数据类型,凭什么数组可以被修改后传出,而字符串不能被修改后传出?那String到底还是不是引用类型了?
 它当然还是引用类型,只是在传参时发生的操作并非看上去那么简单。我们通过图解来具体分析一下其中的变化。
在这里插入图片描述
 首先我们要对堆和栈具有一定的了解,否则分析就无法进行。堆存放的是对象,而对象的引用存放的在栈里,普通的数据类型和一些方法、局部变量也都存放在栈里。此外我们还要知道一点,堆和栈内数据的生存期是不同的,栈内的数据会随着方法的结束而被回收,然而堆并不是,堆内的数据是不会随着方法的结束而自动回收的,而Java的垃圾回收机制就是关于这一点的,那什么时候堆内的数据会被回收呢?只有当该对象没有被指向时,它就被自动回收了。
在这里插入图片描述
 最后我们还要知道一点的是,java中没有地址传递,只有值传递。到这里你是不是有点蒙了,怎么又说没有地址传递?那前边说的又是什么意思??这里我们解释一下,java中的地址传递并不是传统意义上的,比如我们有一个对象Obj,传参时实际上是指向Obj的引用1被作为参数被复制了一份,因此当前这个引用2指向的同样是在堆内的Obj对象。如上图。
在这里插入图片描述

 知道了前面的知识后,我们进入分析的正题!接下来的都是重点,请认真起来了!首先String当然是引用类型,因此传参时也一定同样是地址传递,所以当字符串str传参时,str的地址被复制了一份到栈中,此时复制的这个引用依然是指向原字符串"csdn"的。如上图,我们将原str依然用str表示,将传入方法后的参数str用str’表示。目前这两个引用指向的都是"csdn"对象。
在这里插入图片描述
 然后我们进行了一步很重要的操作,一个看起来没有任何问题的操作,但恰恰问题就是出在了这里。我们对str’进行修改了,给它重新分配了一个新的对象"CSDN"。这直接导致了后面所有问题的出现,因为当我们这么做之后,str’就已经不指向原"csdn"对象了!(如上图)再对str’的任何操作都不会对原"csdn"对象有任何影响了,因为它俩指向的对象已经不是同一个了,所以字符串"csdn"不会发生任何变化!


结论

 到这里对于该问题我们已经分析得很清楚了,依然有疑问的同学可以多看几遍理解理解。最后我们对前面的所有问题做个小结。小结如下。

  • 基本类型的变量放在栈里;
  • 封装类型中,对象放在堆里,对象的引用放在栈里。
  • 当传参时,JVM是重新在栈里开辟了一块空间str’,str’与str的地址是相同的,很重要的一点,他俩本身在栈中是两块不同的内存!
  • 在方法内,赋值操作相当于让JVM给str’又重新在堆内开辟了一块新的内存,str’ = new String(CSDN),当退出方法后,该内存str’就被回收了,所以,这里方法内部对str’的任何操作,实际上都没有对原来的对象进行过任何修改。
  • 一句话来说就是,一旦你在方法内对字符串进行了赋值操作了,那么之后你在堆内的任何操作都不再是对原对象进行操作了。
  • 变量存放在栈里,而变量指向是存放在堆内存!!!

关于复制后又新创建的对象"CSDN"去哪儿了的同学请看下面。

这里有一点需要注意,当change()方法运行结束时,回收的不只是str’,str’指向的对象也会被回收!上面说到了,堆内对象的生命周期与方法无关,即使方法被回收了,只要该对象仍有变量指向它,那么堆内的对象也不会被回收。它的生命周期从创建时开始,到没有变量指向该对象时,就会被自动回收了。但是,这里正是因为str’被回收了,也就间接导致了"CSDN"对象间接地没了指向它的引用,因此在这里它是与str’一起被回收的。

  • 7
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值