Java String引用传递问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java String引用传递问题相关的知识,希望对你有一定的参考价值。

String类在主方法定义无法被方法修改
但是如果实例化其他类并对对象修改String对象则可以修改,这其中原因是什么

了解数据在堆栈内存中的存储,这道问题就可以解了。
1.String对象传入方法后,str2是局部变量,方法结束之后,局部变量值会在缓冲区中。但是并没有修改成员变量str1的值,而输出的仍然是str1的值,"hello"。(局部变量和成员变量是不同的变量,即使名字相同)
2.至于第二个因为类的实例化,对象可以指向该类地址,可以访问该类的成员变量。对象指向成员变量temp,调用方法tell方法时,传入的是Ref2对象的地址。该地址可以找到Ref2的成员变量temp。然后把"xueyuan"赋给temp,由此temp的值已经改变,方法调用结束之后,输出的是Ref2类的已经改变值的成员变量追问

第一种情况是因为形参在堆中开辟了新的空间而没有修改成功
第二种情况是改变了引用的指向导致了修改成功?

追答

引用的指向没有变吧,指向的是temp,把值赋给temp,指向还是temp

参考技术A java只有值传递,所有的方法参数都是变量的值的拷贝,对于基本变量,改变变量的拷贝没有任何意义,而对于引用变量,变量的值为引用对象的地址,变量的拷贝与原变量指向同一个对象,所以在方法中对于对象的修改(set方法)是全局性的,而“变量的拷贝 = 新对象”仅仅是开辟新的空间储存“新对象”,然后将“新对象”的地址赋给“变量的拷贝”,所以原变量无变化。 参考技术B 你是C/C++或是VB转过来的吗?在Java中过多考虑引用(指针)和值之间的关系是步入歧途,这正是Java的设计者极力避免你考虑的问题。
你需要明白的是:
1、Java中所有方法的参数的传递都是“值传递”;
2、Java中所有对象类型的变量的“值”,本质上说,包含了Java堆中的实际对象的地址,你可以大体认为Java的变量对应了C/C++中的指针(其实这里面有更复杂的机制)。事实上,Java并不像C/C++一样明确的区分“值语义”与“引用语义”,Java栈中也不会存放任何对象的实体(这点与C/C++不同,C/C++栈中可以存放对象实体),所有的Java对象都是在堆中。

概念上的区别在于,我这里提到的“变量”是指Java栈中的内容,对应你说的“引用”;我提到的“对象”是指Java堆中的实体,对应你说的“值”。而一般Java教材中提到的“值传递”,是指这些“变量”的内容的传递,不是Java堆中的对象实体的传递。

你用字符串来做实验,并推广为所有Java对象的做法,并不是特别合适。Java的String类型有特殊的处理:所有编译期认识的字符串,都会被放到常量池,于是下面的语句:
a = "s";
b = "s";
a和b并不像其它对象一样有创建的动作,都是直接指向常量池中的"s",所以你可以得到a==b。而下面的语句:
a = new String("s");
b = new String("s");
是分别在Java堆中创建了2个对象,此时a!=b。

本质上说,对于基本数据类型(整数、字符等),Java的符号==,用于判断二者的值是否相等;对于对象类型,Java的符号==,用于判断两个变量是否是“同一个对象”,equals()方法才是用于判断两个对象是否相等。

你希望实现的swap逻辑,在Java中通常认为是无法实现的。拿你这个例子来说,swapValue()中的tmpValue无论怎么更改,只是改变tmpValue自己的内容(即不断指向不同的对象),并不会改变value中的内容(始终指向同一个对象)。这也是为什么Java最初说自己永远是值传递。你只有改变tmpValue指向的对象的值(通过调用这个对象的方法或是更改它的属性),使用value访问时,才能看到这些改变。

为了弥补这个缺陷,C#才加入了ref关键字,允许传入变量的引用(如果参考C/C++,C#传递的实际是二级指针,它的内容是栈中的变量的地址)。
参考技术C String对象无法被修改。你说的“修改”,修改的是引用的指向,也就是令本来指向String对象a的引用r,改而指向String对象b。String对象本身并没有改变,也不可变。追问

我理解第一种情况
但是第二种情况里形参为String开拓了新的内存空间并指向了它为什么会改变实参

追答

你传递的参数是一个指向Ref2类的引用r2,通过这个引用r2去修改这个Ref2对象中的一个属性的值,这个属性是一个指向String对象的引用。tell(r1)没有修改实参r1本身的指向,也无法修改。

追问

既然修改的是引用
为什么第一种情况没有修改
而第二种情况修改了引用
两者的区别在哪里

追答

实参是引用,在函数中不能通过形参直接修改实参引用的指向,但可以通过引用修改其指向的对象,分清引用和对象的区别。

追问

可以这么理解吗
第一种情况是因为形参在堆中开辟了新的空间而没有修改成功
第二种情况是改变了引用的指向导致了修改成功?

追答

你可以把引用想象成写着地址的纸条,把对象想象成地址所在处的房子。
现在有纸条a(实参),你照着纸条a抄了一份纸条b(形参),无论你怎么修改纸条b上的内容,都不可能影响到纸条a上写着的地址,这就叫改变形参不影响实参的值。这是情况一;
如果你照着抄来的纸条b上的地址找到了那栋房子,然后在门上刻了到此一游几个字,那么等到拿着纸条a的人顺着地址找到那栋房子的时候,他也会在门上看到那几个字。通过纸条b(引用)找到对象(房子)然后在门上刻字(改变对象的状态),其他人再通过纸条a(其他指向同一个对象的引用)找到那栋房子的时候,也会发现它的门已经被刻过字。这是情况二。

本回答被提问者和网友采纳

java 的String和StringBuffer引用传递问题

public class StrngBuffer_1

public static void main(String args[])

String a=new String("java");
StringBuffer b=new StringBuffer("java");
mb_method(a,b);
System.out.println(a+b);

public static void mb_method(String s,StringBuffer t)

s=s.replace("j","i");
t=t.append("C");


为什么输出为javajavaC而不是 iavajavaC,感觉是引用传递问题,但不知道怎么解释,请指点。谢谢。

java中不能对参数做出任何赋值操作,即=操作不行
s=s.replace("j","i");想要改变参数s的物理地址,即改变实参这是不被允许的
就好像如果参数类型是int,你对形参进行++或--以及=操作都是无效的,虽然不会报错
这里应该修改一下
public class StrngBuffer_1

public static void main(String args[])

String a=new String("java");
StringBuffer b=new StringBuffer("java");
a=mb_method(a,b);
System.out.println(a+b);

public static String mb_method(String s,StringBuffer t)


t.append("C");
return s.replace("j","i");
//你不能对参数做出=操作,但是可以靠返回值将处理后的结果返回程序


这跟final什么的没关系,java里就是这样,你用你自己写的类也是一样的结果,我直接给你举例说明吧
public class Main
public static void main(String[] args)
ObjectOne o=new ObjectOne(4,3);
fun(o);
System.out.println(o);

public static void fun(ObjectOne n)
ObjectOne s=new ObjectOne();
n=s;


class ObjectOne
private int x = 0;
private int y = 0;
public ObjectOne()
this.x=0;
this.y=0;

public ObjectOne(int x,int y)
this.x=x;
this.y=y;

public String toString()
return x+" "+y;


我们来看看这程序的输出结果:
4 3
结果是4 3,而不是0 0,这样你懂了吧
t=t.append("C");的这个操作并不是t=这里起作用了,而是t.append("C");这里起作用了,通过调用t的append()方法,修改了t的属性,而不是t=t.append("C")产生新对象后赋回实参地址了
你可以自己在ObjectOne中添加
public void setX(int x)
this.x=x;

并在主函数中比较一下
o=o.setX();和o.setX()有什么区别
参考技术A String是一个特殊的对象,例如String s = "a"; s = s + "b"; Java不会在s的原有对象中附加一个b。而是重新建立一个String对象“ab”,然后将s指向新的对象,而之前的"a”会继续呆在内存中。如果没有其他对象引用a。这在一段时间后,他会被垃圾收集机制收集处理。
而此例中,原本a和s指向的是同一个对象,因为你进行的replace操作,s就指向了新的String对象。而a指向的对象还是没有变化。

而StringBuffer是正常的传引用。

所以在很多的频繁变更String的场合下,不建议使用String,浪费内存。大家都用StringBuffer
参考技术B

    String 不是简单类型,而是一个类,它被用来表示字符序列。字符本身符合 Unicode 标准,其初始化方式有两种。
    如:String greeting=“Good Morning! \\n”;
    String greeting=new String(=“Good Morning! \\n”);
    String的特点是一旦赋值,便不能更改其指向的字符对象,如果更改,则会指向一个新的字符对象 。

    StringBuffer是一个具有对象引用传递特点的字符串对象。
    StringBuffer对象可以调用其方法动态的进行增加、插入、修改和删除操作,且不用像数组那样事先指定大小,从而实现多次插入字符,一次整体取出的效果,因而操作字符串非常灵活方便。
    一旦通过StringBuffer生成最终想要的字符串,就可调用它的toString方法将其转换为一个String对象。

参考技术C 这个不会涉及到引用传递建议你去看看《effective java》里面对于字符串的操作有详解,而且能解决你这个问题。在JDK里面string被定义为final的即a是不可改变的,你将a的引用改变了,于是重新生成了一个string对象,即s,而你又没有把s赋给a(虽然a是不可变的但是可以重新复值得,只是赋值又会重新生成一个对象而已,原来还是没变,符合final的语意); 参考技术D String 字符串常量,简单来说String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象。在你这段代码中用到的还是原来的对象,是没有改变的,你只是生成了一个新的对象"iava",但是没有引用。

以上是关于Java String引用传递问题的主要内容,如果未能解决你的问题,请参考以下文章

Java String 值传递 VS 引用传递

String 不变性以及Java 值传递和引用传递

java中String包装类枚举类的引用传递

JAVA 参数的 值传递和引用传递(特殊的String 和 StringBuild)

JAVA 参数的 值传递和引用传递(特殊的String 和 StringBuild)

SWIG:如何包装 std::string&(通过引用传递的 std::string)