一.String不可变
String对象在创建后不能被修改。这种不可变性可以带来一些性能优势和安全性。那么为什么String类是不可变的呢?下面是从源码角度解释原因:
String类的成员变量是一个char数组,它被声明为
private final char[] value
从源代码来看, 这个char数组存储了String对象的字符内容,因为它被声明为final,所以一旦被初始化,就不能再被修改。而且,String类中没有任何公共方法可以修改该数组。当需要对String对象进行修改时,String类的实现不是修改原始对象,而是创建一个新的String对象,并将修改后的结果存储在其中。例如,当我们调用String类的replace()方法来替换字符串中的某些字符时,它并不会修改原始字符串,而是创建一个新的字符串对象来存储替换后的结果,从而保证了String对象的不可变性。
二.为什么String要被设计成不可变呢?
安全性:由于字符串是Java中最广泛使用的对象之一,String对象被广泛用于很多重要的任务,比如密码验证等。如果String是可变的,那么在多线程环境下,如果有多个线程同时修改同一个String对象,就会造成不可预料的结果,这对程序的安全性是非常不利的。
性能:在Java中,String对象经常被用作哈希表中的键,如果String是可变的,那么每次修改String对象都需要重新计算哈希值,这会带来很大的性能损失。此外,由于String不可变,Java编译器可以在编译时进行优化,例如字符串常量的拼接,编译器可以在编译时直接将多个字符串常量拼接成一个字符串常量,从而避免在运行时进行字符串拼接操作,提高程序的性能。
简单性:String类的不可变性使得程序的设计更加简单,因为String对象可以被自由地传递和共享,而不必担心它们会被修改。
三.不可变带来的问题
虽然String类的不可变性在很多情况下都是非常有益的,但是也会带来一些问题,具体包括以下几个方面:
内存浪费:由于String对象是不可变的,每次修改String对象都需要创建一个新的String对象,这就会带来很多的内存开销。例如,在进行字符串拼接操作时,每个中间结果都需要创建一个新的String对象,这会导致大量的内存浪费。
性能问题:虽然String的不可变性有助于提高程序的性能,但在某些情况下,它也可能会导致性能问题。例如,在进行字符串拼接操作时,由于每个中间结果都需要创建一个新的String对象,这会导致大量的对象创建和垃圾回收操作,从而降低程序的性能。
安全问题:虽然String对象的不可变性有助于提高程序的安全性,但在某些情况下,它也可能会导致安全问题。例如,在使用String对象作为密码时,由于String对象是不可变的,密码可能会在内存中被暴露,从而被其他人获取。为了解决这个问题,可以使用char数组来代替String对象。
难以扩展:由于String对象是不可变的,它的值无法被修改,这也意味着String对象的功能也无法被扩展。例如,在String对象中增加一个新的方法是不可能的,这限制了String对象的功能扩展能力。
四.解决思路:StringBuffer和StringBuilder
StringBuffer和StringBuilder是Java中两个用于字符串操作的类,它们都比String类更加灵活和高效。它们的主要区别在于线程安全性和性能。
StringBuffer源代码
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
private static final long serialVersionUID = 3388685877147921107L;
public synchronized StringBuffer append(Object obj) {
super.append(String.valueOf(obj));
return this;
}
public synchronized StringBuffer append(CharSequence s) {
super.append(s);
return this;
}
public synchronized StringBuffer append(CharSequence s, int start, int end) {
super.append(s, start, end);
return this;
}
public synchronized StringBuffer append(char[] str) {
super.append(str);
return this;
}
public synchronized StringBuffer append(char[] str, int offset, int len) {
super.append(str, offset, len);
return this;
}
public synchronized StringBuffer append(boolean b) {
super.append(b);
return this;
}
public synchronized StringBuffer append(char c) {
super.append(c);
return this;
}
public synchronized StringBuffer append(int i) {
super.append(i);
return this;
}
public synchronized StringBuffer append(long lng) {
super.append(lng);
return this;
}
public synchronized StringBuffer append(float f) {
super.append(f);
return this;
}
public synchronized StringBuffer append(double d) {
super.append(d);
return this;
}
public synchronized StringBuffer appendCodePoint(int codePoint) {
super.appendCodePoint(codePoint);
return this;
}
// 其他方法省略
}
StringBuilder源代码
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
private static final long serialVersionUID = 1L;
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
public StringBuilder append(String str) {
super.append(str);
return this;
}
public StringBuilder append(StringBuilder sb) {
super.append(sb);
return this;
}
public StringBuilder append(CharSequence s) {
if (s == null)
s = "null";
return append(s, 0, s.length());
}
public StringBuilder append(CharSequence s, int start, int end) {
super.append(s, start, end);
return this;
}
}
StringBuffer是一个线程安全的可变字符串类,它的方法都是使用synchronized关键字来保证线程安全的。这也就意味着,虽然StringBuffer可以在多线程环境中使用,但是它的性能会受到一定的影响。在Java 5之前,StringBuffer是Java中操作字符串的主要工具。
StringBuilder是一个非线程安全的可变字符串类,它的方法没有使用synchronized关键字来保证线程安全。这也就意味着,在单线程环境中使用StringBuilder比使用StringBuffer更加高效。在Java 5之后,StringBuilder成为Java中操作字符串的首选工具。
以上是两者的源代码实现,可以看出它们的代码实现基本相同,只是StringBuffer中所有的公共方法都添加了synchronized关键字。不考虑线程安全的情况下,StringBuilder效率最高,实际开发中也最常用。在单线程环境中,建议使用StringBuilder;在多线程环境中,建议使StringBuffer。
五:StringJoiner
Java 8中新增的一个字符串拼接工具类,它提供了一种简单的方式来拼接字符串,并且比较灵活。使用StringJoiner可以将多个字符串拼接成一个字符串,并且可以指定拼接字符串的分隔符、前缀和后缀等等。StringJoiner的主要方法包括:
//创建一个使用指定分隔符的StringJoiner对象。
StringJoiner(CharSequence delimiter);
//创建一个使用指定分隔符、前缀和后缀的StringJoiner对象。
StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix);
//将指定的字符串添加到StringJoiner对象中。
StringJoiner add(CharSequence newElement);
//将另一个StringJoiner对象中的字符串合并到当前对象中。
StringJoiner merge(StringJoiner other);
下面是一个简单的示例:
StringJoiner joiner = new StringJoiner(",", "[", "]");
joiner.add("apple");
joiner.add("banana");
joiner.add("orange");
String result = joiner.toString();
System.out.println(result);
//输出结果为:[apple,banana,orange]
上面的示例中,我们首先创建了一个使用逗号作为分隔符、方括号作为前缀和后缀的StringJoiner对象。然后,我们将三个字符串(apple、banana和orange)添加到StringJoiner对象中,最后使用toString()方法将其转换成一个字符串。
本文讨论了String类的不可变以及为什么要设计成不可变和这样所带来的问题,StringBuffer和StringBuilder的对应解决,以及平常很少用的StringJoiner,希望对大家有用。