String类型的不可变
众所周知,String类型是不可变的,一旦String对象被创建好了则这个字符串中的序列就不可改变。
为什么String类型是不可变?
根据阅读源码可知,String类是一个final类,但是String不可变并不是因为它是一个final类,final修饰的类只是不能被继承,因为有字符串常量池这个东西,如果String类能被继承就会出现安全问题。关于String不可变的原因是因为下面这句代码:
private final char value[];
value负责存储String的值,因为value是用final修饰的,所以value的地址不能被改变(但是数组的值可以被改变),又因为在String类中并没有任何方法能够修改value的元素,所以String是不可变的。但是在有的时候我们认为字符串改变了啊,例如String s="hi"; s+=" world";s输出就会变成"hi world",这是因为重新给了s一个字符串对象,相加后的对象和以前的对象不一样,后面将会有代码演示。
String类型真的不可变吗?
因为value是一个char数组,他只是地址不可变,其中的值是可变的,String不可变是因为它没有提供给我们可以修改value的方法,但是我们可以通过反射获取到value数组,然后修改它的值,这样可以实现“String的可变”。如下代码所示:
String s1= "hello world";
String s2 ="hello world";
System.out.println(s1==s2);
System.out.println(s1);
System.out.println(s2);
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
char[] value = (char[])field.get(s1);
value[1]='i';
value[2]='i';
value[3]='i';
value[4]='i';
System.out.println(s1==s2);
System.out.println(s1);
System.out.println(s2);
输出结果
true
hello world
hello world
true
hiiii world
hiiii world
通过上面可以看出当我们改变s1的值时s2被改变了。
关于String的一些常识
如下代码
String s1 = "hello world";
String s2 = "hello world";
String s3 = "hello "+"world";
String s4 = new String("hello world");
String s5 = "hello" + new String("world");
String s6 = new String("hello ");
String s7="world";
String s8 ="hello ";
String s9=s6+s7;
String s10=s8+s7;
System.out.println(s1==s2);
System.out.println(s1==s3);
System.out.println(s1==s4);
System.out.println(s1==s5);
System.out.println(s4==s5);
System.out.println(s1==s9);
System.out.println(s1==s10);
System.out.println(s9==s10);
s1 = s1+"!";
System.out.println(s1);
System.out.println(s2);
System.out.println(s1==s2);
输出结果
true
true
false
false
false
false
false
false
hello world!
hello world
false
在String中使用new就新创建一个对象,不论字符串常量池是否有它,而s3和s10的区别在于,在编译阶段就能确定s3,因为s3指定了是"hello "和"world"相结合,而s10在编译阶段程序并不能确定s8和s7的值。
String一些方法可能产生新的对象
subString
当subString截取的字符串就是本身的时候,返回本身,否则返回一个新声明的字符串。
replace
如果新字符和要替换的一样的话,返回本身,否则返回一个新声明的字符串。
toCharArray
返回一个新的字符数组,对改数组进行修改不会对String产生任何影响。
toString
返回的是字符串本身
trim
trim方法是返回String对象的一个副本,该副本去除了原来字符串中的首部和尾部空白。但如果String对象首部和尾部没有空白的话,则返回自身。否则调用subString方法返回一个崭新的String对象(真子串,subString方法中说过)。
关于String传参
在开始String传参的时候先看看参数传递的两大类型:
值传递:在我们进行参数传递的时候将实参拷贝一份传递过去,所以在方法里面对其更改不会对原本的造成影响。
引用传递:在java中引用传递是拷贝该对象的地址传递过去,如果在传递过去的地址对应的对象上进行修改就会影响原本的值。
具体例子如下:
//自己声明的一个类
class Ref {
private String name;
public void setName(String name) {
this.name = name;
}
public String getName(){
return name;
}
@Override
public String toString() {
return "Ref{" +
"name='" + name + '\'' +
'}';
}
}
public static void main(String[] args) {
Ref ref = new Ref();
ref.setName("张三");
int a=5;
String name = "张三";
change(ref);
change(a);
change(name);
System.out.println(ref);
System.out.println(a);
System.out.println(name);
}
public static void change(Ref ref) {
ref.setName("李四");
}
public static void change(int num) {
num = 18;
}
public static void change(String name) {
name = "李四";
}
//上面输出
/*
Ref{name='李四'}
5
张三
*/
//将ref 的change函数改变如下
public static void change(Ref ref) {
Ref ref1 = new Ref();
ref = ref1;
ref.setName("李四");
}
/*运行
Ref{name='张三'}
5
张三
*/
通过上面的例子我们发现对象的传递如果在方法里面进行了修改将会改变原本的值。但是!!!要注意我们传递过去的只是地址,如下图所示,我们进行传递的时候将ref对象的地址拷贝一份传递过去(注意是拷贝一份),当我们没有对传递过去的形参地址进行修改的情况下,对该形参进行修改都会影响原本的实参,例如上面前面的change方法,如果进行对形参的地址进行修改了的话将会和原本的实参断绝关系,例如上面修改后的change方法。而上面的String的change就不用说了 name= "李四"相当于将name的地址修改为了字符串常量池中的"李四"对象。
ref的存储结构.png
通过上面的我们明白了什么是引用传递和值传递。关于String传参可能大家也有了答案。
String传参的时候传递String对象所指向的地址,但是由于String中没有方法能够对value数组进行修改,所以我们在对String参数进行操作的时候都不会改变他的值,很多情况下我们认为String的值变化了都是一种错觉。比如下面的replace方法
public static void change(String name) {
name.replace('张','李');
}
上面的代码很容易让我们产生name中的"张"换成了"李"的错觉,但是其实并没有改变,我们可以看一看replace的具体是怎么实现的
replace.png
我们可以看出repalce中并没有对原本value数组进行更改,而是将value中的数组拷贝到buf中(需要替换的替换),然后返回一个new String。具体其他你认为String内容改变了的地方你可以阅读源码进行观察,你就会发现其实都没有更改。
关于String的其他内容可以点击查看