首页 > java方法传String类型的参数时,为啥方法对值的修改没有在方法之外生效?

java方法传String类型的参数时,为啥方法对值的修改没有在方法之外生效?

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

这样的解释太肤浅了,完全是基于文字的描述,有没有基于代码层面的解释,或者其他的更深层次的解释呢?


String是immutable类型,也就是说其值是不可变的。例如String a = "abc",在内存中a这个引用指向"abc"这块内存,当使用a = "bcd"时,并没有将"abc"所在内存块的值改为"bcd",而是另外生成一个字符串"bcd",然后将a指向"bcd",所以方法外的引用仍然指向"abc"这块内存。

而可变的类型,会直接修改"abc"所在内存的值,这样做方法外的引用仍然指向原来的内存地址,于是可以在方法外访问到修改后的值。


三句话总结一下:
1.对象就是传引用
2.原始类型就是传值
3.String,Integer, Double等immutable类型因为没有提供自身修改的函数,每次操作都是新生成一个对象,所以要特殊对待。可以认为是传值。
Integer 和 String 一样。保存value的类变量是Final属性,无法被修改,只能被重新赋值/生成新的对象。 当Integer 做为方法参数传递进方法内时,对其的赋值都会导致 原Integer 的引用被 指向了方法内的栈地址,失去了对原类变量地址的指向。对赋值后的Integer对象做得任何操作,都不会影响原来对象。
链接描述


值传递,引用传递


按照你的理解思路来讲

示例一确实是对对象的数据的修改,但是示例二却是对参数本身的修改

不管是值传递还是引用传递,其实本质上都是值传递,方法调用的时候 计算出参数的值,拷贝一份给对应的参数

只是对于对象类型,传递的是对象的引用(或者理解为指针,地址)值,虽然参数拿到的是拷贝的引用,但是和原引用指向的是同一个对象,因此根据这个引用可以操作到原对象的数据

你的示例二中,调用方法时,计算参数string的值(可以理解为hello这个字符串对象在内存中的地址),拷贝给参数str,参数str虽然拿到了原来hello字符串对象的引用(即str的值等于string的值,也即hello的地址),但是立马给这个参数重新赋值,这个时候str参数已经跟hello字符串没有任何关系了,str不再指向hello,改指向world了,但是这个过程中hello自身以及hello在内存中的地址没有任何变化,方法外部的string变量也一直指向hello这个字符串对象,没有任何变化


Java中除了String,其他的Object都是按引用传递。


//示例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  
    }  
}

我的一篇专栏文章的开头有讲到相关知识点,看你有点晕乎乎的,向你推荐一下。


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");
        }
    }
} 

String对象中包含的字符串是不能被修改的,当String类型的变量被重新赋值后,其引用也会发生变化,是一个新的对象了。

【热门文章】
【热门文章】