Heim > Fragen und Antworten > Hauptteil
1、问题描述
java方法传String类型的参数时,为啥方法对值的修改没有在方法之外生效?假设是其他类型的对象,那么方法对对象的数据的修改是会在方法之外生效的啊,可是String类型也是对象类型啊,为啥就没有在方法之外生效呢?
2、代码示例
//示例1:对象类型
public class PassReferByValue
{
String a = "123";
public static void test(PassReferByValue test)
{
test.a = "abc";
}
public static void main(String[] args)
{
PassReferByValue test = new PassReferByValue();
System.out.println("对象的数据的初始值是:" + test.a); //123
test(test);
System.out.println("通过调用test方法修改对象的数据的值之后:" + test.a); //abc
}
}
总结:因为是对象类型,所以方法每次修改对象的数据的值之后,都在方法之外生效了,也就是说,在方法之外和在方法之内访问到的对象的数据的值是一致的。
//示例2:String类型
public class Test
{
public static void test(String str)
{
str = "word";
}
public static void main(String[] args)
{
String string = "hello";
System.out.println("对象的数据的初始值是:" + string); //hello
test(string);
System.out.println("通过调用test方法修改对象的值之后还是以前的值:" + string); //hello
}
}
总结:如果方法的参数是String类型,虽然方法修改了它的值,但是并没有在方法之外生效,也就是说,在方法之外和在方法之内的访问到的值不一样!!!这是为什么???
3、网上的解释
String等immutable类型因为没有提供自身修改的函数,每次操作都是新生成一个对象,所以要特殊对待。
http://blog.csdn.net/fbysss/article/details/3082949
这样的解释太肤浅了,完全是基于文字的描述,有没有基于代码层面的解释,或者其他的更深层次的解释呢?
阿神2017-04-18 09:15:32
按照你的理解思路来讲
示例一确实是对对象的数据的修改
,但是示例二却是对参数本身的修改
了
不管是值传递还是引用传递,其实本质上都是值传递
,方法调用的时候 计算出参数的值,拷贝一份给对应的参数
只是对于对象类型,传递的是对象的引用(或者理解为指针,地址)值,虽然参数拿到的是拷贝的引用,但是和原引用指向的是同一个对象,因此根据这个引用可以操作到原对象的数据
你的示例二中,调用方法时,计算参数string
的值(可以理解为hello
这个字符串对象在内存中的地址),拷贝给参数str
,参数str
虽然拿到了原来hello
字符串对象的引用(即str
的值等于string
的值,也即hello
的地址),但是立马给这个参数重新赋值,这个时候str
参数已经跟hello
字符串没有任何关系了,str
不再指向hello
,改指向world
了,但是这个过程中hello
自身以及hello
在内存中的地址没有任何变化,方法外部的string
变量也一直指向hello
这个字符串对象,没有任何变化
天蓬老师2017-04-18 09:15:32
三句话总结一下:
1.对象
就是传引用
2.原始类型
就是传值
3.String,Integer, Double
等immutable类型因为没有提供自身修改的函数,每次操作都是新生成一个对象,所以要特殊对待。可以认为是传值。
Integer 和 String 一样。保存value的类变量是Final属性,无法被修改,只能被重新赋值/生成新的对象。 当Integer 做为方法参数传递进方法内时,对其的赋值都会导致 原Integer 的引用被 指向了方法内的栈地址,失去了对原类变量地址的指向。对赋值后的Integer对象做得任何操作,都不会影响原来对象。
链接描述
PHPz2017-04-18 09:15:32
test1.a = "567"
,这里你改的是 test1
所引用的对象的属性
str = "word"
你这里改的是引用变量 str
所指向的对象。
简单的说,两个,小明和小红,你给小明把手换成了机械手,小明的手就真的换了,但是你把小红改名叫小明……小红这个人本身还是没变的。
再深层一点,小明和小红去参加一个活动,活动不需要知道你的真名,所以给他们分配了代号A和B。这个时候,活动中A受伤了,把手换成了机械的,结果活动完了,小明的手确实已经换成机械的了。但是B只是把名牌了C进行了交换,出来之后,小红还是小红,并没有变成C对应的那个人。
这里,活动就是方法,进去分配的代号就是形参,对形参直接赋值不会影响实参,但对形参引用的对象的属性进行赋值,实际已经影响到了对象的属性。
给你补充一段带注释的代码,看能不能帮助你理解。如果要了解方法调用的真实过程还涉及栈之类的知识,就不搞那么复杂了。这段代码没有输出,你可以在理解之后自己尝试加一些输出来验证自己的理解是否正确。
public class PassReferByValue
{
String a = "123";
public static void test(PassReferByValue test)
{
test.a = "abc";
}
public static void test2(PassReferByValue test) {
test = new PassReferByValue();
test.a = "cde";
}
public static void test3(String s) {
s = "world";
}
public static void main(String[] args)
{
PassReferByValue obj = new PassReferByValue();
// -----------------------------------------------
// test(obj);
// 如果把这个调用过程展开,代码就像这样(为了便于识别,我改了下变量名
// 说明一下:下面用 { } 代码段主要是为了限定里面的变量作用域
// 进入 test(obj);
{
PassReferByValue test = obj;
test.a = "abc";
}
// 没有返回类型,所以这里方法直接结束,出来
// ------------------------------------------------
// 现在来展开 test2(obj);
{
PassReferByValue test = obj;
test = new PassReferByValue(); // test 是一个新对象,不是 obj 了
test.a = "cde"; // 改变的是新对象,对 obj 没有影响
}
// 所以方法调用结束之后 obj 还是没变,还是 obj.a.equals("abc");
// -------------------------------------------------
// 现在来看 string 的情况
String hello = "hello";
// 这里开始展开 test3(hello);
{
String s = hello;
s = "world"; // 这里修改了 s 引用的对象,但没有修改 hello 引用的对象
// 所以 hello.equals("hello"), s.equals("world");
}
}
}
高洛峰2017-04-18 09:15:32
//示例2:String类型
public class Test
{
public static void test(String str)
{
//str这个变量作用域在test这个函数里,原本指向"hello",现在指向"word"
str = "word";
}
public static void main(String[] args)
{
String string = "hello";
System.out.println("对象的数据的初始值是:" + string); //hello
test(string);
System.out.println("通过调用test方法修改对象的值之后还是以前的值:" + string); //hello
}
}
我的一篇专栏文章的开头有讲到相关知识点,看你有点晕乎乎的,向你推荐一下。
阿神2017-04-18 09:15:32
String是immutable类型,也就是说其值是不可变的。例如String a = "abc"
,在内存中a这个引用指向"abc"这块内存,当使用a = "bcd"
时,并没有将"abc"所在内存块的值改为"bcd",而是另外生成一个字符串"bcd",然后将a指向"bcd",所以方法外的引用仍然指向"abc"这块内存。
而可变的类型,会直接修改"abc"所在内存的值,这样做方法外的引用仍然指向原来的内存地址,于是可以在方法外访问到修改后的值。