对于String类
1、String类对象内容的不可变性
String类的对象的内容是不能被改变的。即String对象一旦被创建,其组成这个String类对象的字符数组(或者说是字符数列)是无法被修改的。String类里面的方法,例如subString,replace和trim方法,都是不能修改本身的字符串,只能创建改字符串对象的一个副本然后才对其进行相关操作并返回这个被操作后的副本。
例如:
public class Main { public static void main(String[] args) { String str1 = "hello world!"; str1.replace("l","L"); String str2 = " hello java! "; str2.trim(); System.out.println(str1); System.out.println(str2); } }
运行结果如下:
创建两个字符串 str1 = "hello world!"; str2 = " hello java! ";
然后对str1进行replace操作,对str2进行trim去除首尾空格操作,然后打印str1和str2操作,
从其结果可以看出来:String类里面的方法并不能修改String对象的内容。
2、从源代码观察这种机制
在idea里面查看源代码,如下:
可以发现,其实String类的对象的字符存储是以 字符数组的形式存放的,并且这个数组被private关键字和final 关键字修饰,被final关键字修饰的变量就变成了常量,是无法被修改的,但是这并不是其根本原因,其根本所在是因为被final修饰的变量无法被其子类继承,和private修饰,这个value变量其实是一种引用,final修饰这个value表明,value所指向的对象的引用地址不能修改,但是可以修改value数组中的内容,例如,有如下代码:
public class Main { public static void main(String[] args) { final char[] arr = {'a','b','c'}; arr = new char[]{'c','c','c'}; } }
因为arr被final修饰,所以无法将arr 本身指向另外一个字符串数组对象。
结果如下:
public class Main { public static void main(String[] args) { final char[] arr = {'a','b','c'}; for (int i = 0; i < arr.length; i++) { arr[i] = 'x'; } System.out.println(arr); } }
代码结果如下:
可以看到,其实arr的值,也就是value的值其实是可以被修改的。
而实际上不能被修改的原因是:
为什么String类的方法不会修改字符串本身?从源代码的层次看,以下是String类的一些方法:
substring();
public String substring(int beginIndex, int endIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } if (endIndex > value.length) { throw new StringIndexOutOfBoundsException(endIndex); } int subLen = endIndex - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } return ((beginIndex == 0) && (endIndex == value.length)) ? this : new String(value, beginIndex, subLen); }
replace();
public String replace(char oldChar, char newChar) { if (oldChar != newChar) { int len = value.length; int i = -1; char[] val = value; /* avoid getfield opcode */ while (++i < len) { if (val[i] == oldChar) { break; } } if (i < len) { char buf[] = new char[len]; for (int j = 0; j < i; j++) { buf[j] = val[j]; } while (i < len) { char c = val[i]; buf[i] = (c == oldChar) ? newChar : c; i++; } return new String(buf, true); } } return this; }
可以看出,他们最终都是return 返回一个new 关键字新建的字符串对象,而不是在原来的字符串的基础上进行修改。
3、如何理解这种不可变机制?
这种无法被修改的机制可以完美地体现出java语言的安全性。虽然字符串对象的内容无法被修改,但可以修改String 变量所指向的对象:
上面我们所说,关于String类的一些方法都无法修改原字符串,而是新建一个副本,那么如果有一种要求,需要我们不断地去拼接字符串,那么就会存在一个问题,他会在连接的过程中,不断地创建字符串对象,而创建字符串对象是对性能有消耗的,运行效率非常低下,不推荐。
为了解决这个问题
StringBuffer和StringBuilder前导:
String对象一段创建就无法修改,但是如果想创建一个可变的字符串该怎么办?
除了String类可以用来存储字符串,java提供了StringBuffer和StringBuilder类来存储,StringBuffffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作,在性能下,StringBuilder 的性能是要高于StringBuffer的。
StringBuilder
StringBuilder类也可以用来高效地处理字符串,尤其是在字符串连接的时候。因为StringBuilder是java的JDK 5.0版本新增的类,他是一个可变的字符序列:
1、 声明
public class Main { public static void main(String[] args) { StringBuilder tem = new StringBuilder("hello world!"); System.out.println(tem); } }
2、构造方法
①无参构造
无传入值,调用无参构造方法:
StringBuilder继承AbstractStringBuilder,AbstractStringBuilder中有属性:
char[ ] value;
int count;
构造StringBuilder对象前,先对父类AbstractStringBuilder进行构造方法:
最终生成一个StringBuilder类的实例对象,如下:
可以看出来,无参构造方法生成的StringBuilder实例对象的value字符数组默认大小为16.
② 传入字符串
调用构造方法StringBuilder,首先调用父类构造方法:
构造StringBuilder:value数组的长度为原字符串长度 + 16
然后追加append 字符串:
首先调用父类append方法:
将传入的str对象拷贝到value数组中:
最终数组的大小为 传入的字符串的长度 + 16,但是不能超过Integer.MAX_VALUE - 8。
③ 传入int 类型的值
创建一个长度为传入值大小的容量空字符数组char[ ] arr, 所对应的字符串为空字符串。
假设传入int值为8,其结果如下:
StringBuilder类中的方法
这两个类大 部分功能是相同的,这里介绍 StringBuilder 常用的一些方法:
这里面很多方法都和String类的用法相类似,这里不再做过多的代码案例介绍。
其中append方法就类似与 += 操作,但是StringBuilder的 += 和append不会新重新创建并返回一个对象,而是在原来的已经存在的字符数组中做修:
String类和StringBuilder类实例之间的转化
1、String → StringBuilder
使用构造方法:
String str = "hello world!";
StringBuilder stringBuilder = new StringBuilder(str);
使用append方法:
String str = "hello world!";
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(str);
2、Stringbuilder → String
使用StringBuilder 的 toString()方法
StringBuilder stringBuilder = new StringBuilder("hello world!");
String str = stringBuilder.toString();
String,StringBuilder,StringBuffer比较
1、String
String是不可变的对象,一旦创建,无法修改其value数组的值,每次对String进行 += 或者与其他类型的数据连接的时候,都会新建一个对象,每次创建一个对象都有性能的消耗,频繁地拼接字符串对系统的性能会产生一定的的影响。
2、StringBuffer
StringBuffer是可变字符串,每次对字符串的修改都是对其对象本身操作,不会新建对象,再改变对象的引用。因此,在频繁的使用字符串拼接的场景下,建议使用StringBuffer,
3、StringBuilder
StringBuilder是java的JDK 5.0版本之后提供的类,它同StringBuffer类等价,只不过区别在于:StringBuffer是线程安全的,StringBuilder类是单线程,不提供同步,线程安全问题,会在后面的线程这一部分知识点中讲解。
面试题
下面这条语句一共创建了多少个对象:String s= "welcome'+"to"+360;
A. 1
B. 2
C. 3
D. 4
正确答案:A
解析:
对于字符串常量的拼接,实在java编译器编译期间执行的,在编译期间对这三个字面量进行+操作,最终生成一个"welcometo360"字符串常量,然后编译器会在常量池中寻找是否有和这个
"welcometo360"内容相同的字符串,如果有,则直接让s指向这个已经在常量池存在的字符串,如果没有,则重新在常量池中生成一个字符串对象。这就是java的优化机制。
所以这题题意并不算严谨。而对于字符串实例的拼接,实在java运行期间执行的,会在堆中重新生成一个对象。