关于Java中String的传递问题

本文详细探讨了Java中String类型的不可变性原理。通过分析String类的内部实现及JDK源码,揭示了String为何表现出类似基本数据类型的值传递特性。同时,通过实例对比了String与其他对象类型在方法调用时的行为差异。

关于Java中String的传递问题

  1. 今天在写java时想写一个方法public void Helper(TreeNode root,List<String> list, String path) 前面两者不必说,传递的是地址,叫 emm 叫值传递——java中只存在值传递,只存在值传递!!! (然而我们经常看到对于对象(数组,类,接口)的传递似乎有点像引用传递,可以改变对象中某个属性的值。但是不要被这个假象所蒙蔽,实际上这个传入函数的值是对象引用的拷贝,即传递的是引用的地址值,所以还是按值传递。) 再来看一看String的问题,String是对象,但是表现出像基本数据类型一样的值传递表现,比较一下下面两者:
public class Test3 {
    public static void change(int []a){
        a[0]=50;
    }
    public static void main(String[] args) {
        int []a={10,20};
        System.out.println(a[0]);
        change(a);
        System.out.println(a[0]);
    }
}

输出10,50

public class Test {
    public static void change(String s){
        s="zhangsan";
    }

    public static void main(String[] args) {
        String s=new String("lisi");
        System.out.println(s);
        change(s);
        System.out.println(s);
    }
}

输出lisi lisi
照理说String是对象,应该输出lisi zhangsan才对啊,怎么change方法里没把String改掉呢?这是因为String具有不变性,看下图 来源 这里写图片描述
String具有不可变性,String被重新赋值为abcdel时,是在堆内重新开辟空间放入abcdel,并且String指向它,相当于新建了一个String对象,但是原先的String s=abcd仍然存在。
同理对于上面change方法里重新建立了一个String zhangsan对象,回到main里,zhangsan对象地址由于是值传递,在main里当然就消失了,此时的s仍然是lisi这个对象.
2. 再从根本上来看下为什么String具有这种不变性
翻开JDK源码,java.lang.String类起手前三行,是这样写的:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    /** String本质是个char数组. 而且用final关键字修饰.*/
    private final char value[];
    ...
    ...
}

首先String类是用final关键字修饰,这说明String不可继承。
再看下面,String类的主力成员字段value是个char[ ]数组,而且是用final修饰的。final修饰的字段创建以后就不可改变。

有的人以为故事就这样完了,其实没有。因为虽然value是不可变,也只是value这个引用地址不可变。挡不住Array数组是可变的事实。Array的数据结构看下图,

这里写图片描述

也就是说Array变量只是stack上的一个引用,数组的本体结构在heap堆。String类里的value用final修饰,只是说stack里的这个叫value的引用地址不可变。没有说堆里array本身数据不可变。看下面这个例子,

final int[] value={1,2,3}
int[] another={4,5,6};
value=another;    //编译器报错,final不可变
value用final修饰,编译器不允许我把value指向堆区另一个地址。但如果我直接对数组元素动手,分分钟搞定。

final int[] value={1,2,3};
value[2]=100;  //这时候数组里已经是{1,2,100}

所以String是不可变,关键是因为SUN公司的工程师,在后面所有String的方法里很小心的没有去动Array里的元素,没有暴露内部成员字段。private final char value[]这一句里,private的私有访问权限的作用都比final大。而且设计师还很小心地把整个String设成final禁止继承,避免被其他人继承后破坏。所以String是不可变的关键都在底层的实现,而不是一个final。考验的是工程师构造数据类型,封装数据的功力。

参考文献

[1] 胖胖. 如何理解 String 类型值的不可变[EB/OL]. https://www.zhihu.com/question/20618891.

### Java 参数传递机制 在Java中,参数传递主要分为按值调用(Pass by Value)和按引用调用(Pass by Reference),但实际上Java只支持按值调用。对于基本数据类型的参数传递入的是实际数值的一个副本;而对于引用数据类型,则是传递对象引用的副本。 #### 基本数据类型传递 当函数接收一个基本数据类型作为参数时,会创建该原始值的一份拷贝并将其赋给形参。因此,在方法内部对该参数所做的任何修改都不会影响到外部的实际变量[^1]。 ```java public void changeValue(int value){ value = 10; } int num = 5; changeValue(num); System.out.println(num); // 输出仍然是5,因为只是改变了value这个局部变量 ``` #### 引用数据类型传递 对于像`StringBuffer`, `ArrayList`这样的复杂对象或是自定义类实例来说,情况有所不同。虽然形式上还是按照值来传递,但是这里所谓的“值”是指向堆内存中某个特定位置的对象引用地址。所以如果我们在方法体内通过此引用来改变对象的状态(比如增加列表项或更改字符串缓冲区的内容),这些变化是可以被外界感知到的。 然而需要注意的是,即使能够操作同一个对象状态,也无法让两个不同的引用指向完全新的不同对象: ```java public static void tryChangeObjectReference(StringBuilder sbuilder) { sbuilder.append(" world"); } StringBuilder strBldr = new StringBuilder("hello "); tryChangeObjectReference(strBldr); System.out.println(strBldr.toString()); // hello world // 下面的例子则不会成功替换strBldr所指代的对象本身 public static void failToReplaceObjectReference(StringBuilder paramSb) { paramSb = new StringBuilder("new content"); } failToReplaceObjectReference(strBldr); System.out.println(strBldr.toString()); // 还是原来的 "hello world" ``` ### String 类型详解 `String` 是一种特殊的引用类型,在Java里它实际上是不可变(immutable)的字符序列容器。一旦创建了一个`String`对象之后就不能再对其内容做任何形式上的修改了——每次涉及对原字符串的操作都会返回一个新的`String`对象而不是直接改动现有的那个。 这意味着当你试图在一个方法内修改入的`String`参数时,实际上是在构建另一个全新的字符串而非更新原有的那一个。这同样适用于其他所有尝试去变更已存在字符串的行为,无论是追加、截取还是大小写转换等等[^3]。 ```java public static void appendString(String s) { s += " extra"; } String originalStr = "initial "; appendString(originalStr ); System.out.println(originalStr ); // initial ,并没有受到影响 ``` 尽管如此,由于`String`常量池的存在使得相同字面量会被共享存储空间从而节省资源消耗,并且编译期优化也可能导致某些看似会产生新对象的地方其实并未真正发生分配动作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值