String 不可变的好处和坏处

String 不可变的好处和坏处

String 为什么不可变,有什么好处?

字符串,顾名思义就是一串连续的字符,也就是一串连续的 char

C 语言中就是用 char 数组来表示字符串,

在java的一切皆对象的世界中,将char数组进行了封装,

用String 类型来表达字符串,点开 String 的源码,就会发现char数组的身影。

// Java 8 源码

public final class string{

private final char value[];

}

char 数组被 final 关键字修饰,并且是私有成员变量,为什么 java 要这样设计呢?

这就要说到 String 的不可变性。

// Person 是可变的

Person p = new Person(18);

p.setAge(20);

// String 是不可变的

String s = "abc";

一个对象创建后,如果可以修改对象的属性,我们会说这个对象是可变的,反之则是不可变的。

为什么 String 不可变呢?

首先,它是被 fianl 修饰,代表它不可指向新数组,(但不代表数组本身的数据不能被改变)并且不能被继承。

真正不可变的原因是 它还被 private 修饰了,并且 String 提供和暴露任何修改字符数组的方法,都是返回新的 String 对象。

获取其底层字符数组时,都是获取一个新的数组进行返回,原数组,不会收到影响。

这样设计有什么好处呢?

首先,只有String 不可变了,字符串常量池才能发挥作用,

用字面量创建字符串时,字符串常量池会返回,已有对象的引用,

如果字符串可变,引用的值就可以被修改,这样还谈何复用呢?如何节省资源呢?

String 不可变,可以保证它的哈希码也不会被改变。

public final class String{

private int hash; // Default to 0

private int hash;

public int hashCode() {

int h = hash;

if(h = 0;&& value.length > 0){

char val[] = value;

for(int i = 0;i < value.length; i++){

h = 31 * h + val[i];

}

// 计算一次后即可将哈希码存储

hash = h;

}

return h;

}

}

就可以将其缓存,再用到时,就不用在计算了。

也正是因为 哈希码 不会变,可以放心的去使用,和哈希计算相关的对象

比如 hashMap 、HashSet,如果 哈希码会改变,那就会影响这些对象的哈希计算,从而导致预期之外的结果。

比如你之前明明存储了一个String 对象。到后面你会发现找不到了。

最后,不可变对象都是线程安全的。

不必担心,当前线程使用的对象,会被其它线程修改。

String 不可变的弊端

​ 正是因为String不可变,所以导致添加新对象时,会创建一个新对象,从而导致性能低下。

for(int i = 0;i<9999;++i){

string+=i; // 大量创建新对象

}

System.out.print(string);

​ 为了解决这一问题,java 提供了StringBuilder 这个可变的字符串类型,我们简称它 为 sb ,小 sb继承了一个 老 sb (AbstractStringBuilder) 其底层和String一样都是用的 char 数组来表示字符串,不过老sb的 char 数组,没有加 private final 修饰符,代表这个数组可被访问且可变。

abstract class AbstractStringBuilder{

char[] value;

}

public final class StringBuilder extends AbstractStringBuilder{

}

​ 而且 sb父子俩还提供了许多方法供我们修改字符串,比如append方法就可以原字符串后面添加字符,这些方法修改的都是自身数据,返回的 sb 对象,

​ 也就是它自己本身,不像String一样返回的都是新对象。

​ 这样一来我们频繁修改字符串就方便了许多。

所以在拼接字符串的时候,谨慎使用 String,优先选择 sb

sb 的缺点:它是一个可变对象,那它就是线程不安全的。

为了解决这个问题 java 还推出了 sb 的兄弟,StringBuffer,

它和 sb 不一样的是,它内部使用了 synchronized 关键字,修饰了字符串操作方法,从而保证了线程安全。

有得必有舍,正是由于它每次操作字符串时都会加锁,这回导致它性能没有 sb 高,但是比String 还是要高的。

类型

特点

适用场景

String

不可变,线程安全

操作少量数据,或不需要操作数据

StringBuilder

可变,线程不安全

需要频繁操作数据,但是不用考虑线程安全

StringBuffer

可变,线程安全,性能较低

需要频繁操作数据,要考虑线程安全

StringJoiner 拼接字符串

String[] names = {"A","B","C","D"};

StringJoiner sj = new StringJoiner(",","[","]"); // 设置分隔符,要想设置开头和结尾,在构造方法后面添加两个参数即可

for(String name:names){

sj.add(name);

}

System.out.println(sj); // [A,B,C,D]

java 标准库中的一些拼接操作,其实就用到了,StringJoiner

比如 String 的静态方法, join,以及Stream 流中常用的 joining

String[] names = {"A","B","C","D"};

String.join(",",names);

Arrays.stream(names).collect(Collectors.joining(","));

相关推荐

SpringBoot 文件上传超时解决
365bet足球即时比分

SpringBoot 文件上传超时解决

📅 08-25 👁️ 1633
Win10/Win11电脑版微信双开或多开的两种方法
365bet足球即时比分

Win10/Win11电脑版微信双开或多开的两种方法

📅 07-16 👁️ 8629
囡囡是代表什么意思 囡囡是代表的意思
365bet足球即时比分

囡囡是代表什么意思 囡囡是代表的意思

📅 08-22 👁️ 1210
kfc取消订单一般多久能返款
365bet客户端

kfc取消订单一般多久能返款

📅 09-01 👁️ 6957
ip67是什么防水等级 ip67级防水有多强
365bet足球即时比分

ip67是什么防水等级 ip67级防水有多强

📅 08-30 👁️ 2731
小米手机换外屏:价格、因素及省钱攻略
365bet足球即时比分

小米手机换外屏:价格、因素及省钱攻略

📅 07-03 👁️ 7622
果酱直播,也是秀场,但主打女性消费男色
365bet客户端

果酱直播,也是秀场,但主打女性消费男色

📅 02-01 👁️ 4753
为什么Beats连不上蓝牙?常见问题与解决方案
365bet足球即时比分

为什么Beats连不上蓝牙?常见问题与解决方案

📅 09-22 👁️ 4643
英尺 自 米
365bet客户端

英尺 自 米

📅 10-04 👁️ 8734