java String 可变性的分析

2025-05-29 0 88

前言

这两天在看Java面试相关的一些问题,很偶然也很幸运的看到了下面这篇文章。

https://www.zzvips.com/article/92798.html

这篇文章的作者有一系列关于Java深入学习的文章,很值得一看,个人觉得非常好,很有收获。

起因

java String 可变性的分析

正如我们所理解的,通过

?

1
String hello = "Hello World!";

?

1
String xx = new String("Hello World!");

得到的字符串对象是不一样的,new方式是在堆空间中创建的,而直接的字符串则是先被放到常量池中。如果有新的与之一样的对象被创建,则直接让这个新对象引用常量池中的这个地址即可。

这样的好处就是可以最大限度的节省内存空间。

而使用new方式创建的则就不一样了,只要是用了new创建字符串,就会在堆空间中开辟出一块内存,然后返回这个内存地址的引用。所以这样创建的对象,即使内容一致,也不会是指向同一个内存地址。

下面用几个简单的代码做下测试。

?

1

2

3

4

5
/**

*字符串中对于内容和地址的判定可以用下面两种方式,但侧重点不一样。

*/

equals // 判断 两个字符串的内容是否一致

== // 判断两个字符串的内存地址是否一致

且看下面的代码:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19
public static void simple() {

String s1 = "Hello World!";

String s2 = "Hello World!";

String s3 = new String("Hello World!");

String s4 = new String("Hello World!");

// 下面开始比较引用和内容的比较

System.out.println("字符串赋值方式:");

System.out.println(s1==s2);

System.out.println(s1.equals(s2));

System.out.println("\\n字符串赋值方式和new方式:");

System.out.println(s1==s3);

System.out.println(s1.equals(s3));

System.out.println("\\nnew 方式:");

System.out.println(s3==s4);

System.out.println(s3.equals(s4));

}

得到的结果如下:

?

1

2

3

4

5

6

7

8

9

10

11
字符串赋值方式:

true

true

字符串赋值方式和new方式:

false

true

new 方式:

false

true

结果却是和我们所说的那样。

深入源码

不出所料,String确实是“不可变的”,每次改变底层其实都是创建了一个心的字符串对象,然后赋予了新值。

为什么会这样呢?我们也许可以在源码中找到真相。

java String 可变性的分析

哦,原来Java对于String类只是维护了一个final类型的字符数组啊。怪不得赋值之后就不能改变了呢。

但是也许你会有疑问,咦,不对啊,“我经常使用String的什么replace方法改变字符串的内容啊。你这则么解释呢?”

其实答案还是那样,它真的没变,我们并没有看到事情的真相,相信看完下面的源码,你就明白了。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55
/**

* Returns a string resulting from replacing all occurrences of

* {@code oldChar} in this string with {@code newChar}.

* <p>

* If the character {@code oldChar} does not occur in the

* character sequence represented by this {@code String} object,

* then a reference to this {@code String} object is returned.

* Otherwise, a {@code String} object is returned that

* represents a character sequence identical to the character sequence

* represented by this {@code String} object, except that every

* occurrence of {@code oldChar} is replaced by an occurrence

* of {@code newChar}.

* <p>

* Examples:

* <blockquote><pre>

* "mesquite in your cellar".replace('e', 'o')

* returns "mosquito in your collar"

* "the war of baronets".replace('r', 'y')

* returns "the way of bayonets"

* "sparring with a purple porpoise".replace('p', 't')

* returns "starring with a turtle tortoise"

* "JonL".replace('q', 'x') returns "JonL" (no change)

* </pre></blockquote>

*

* @param oldChar the old character.

* @param newChar the new character.

* @return a string derived from this string by replacing every

* occurrence of {@code oldChar} with {@code newChar}.

*/

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;

}

源码中很明确的使用了

?

1
new String(buf, true);

的方式返回给调用者新对象了。

真的不可变吗?

读到上面的内容,其实基本上已经够了。但是了解一下更深层次的内容,相信对我们以后编程来说会更好。

源码中清楚的使用char[] value来盛装外界的字符串数据。也就是说字符串对象的不可变的特性,其实是源自value数组的final特性。

那么我们可以这么想,我们不改变String的内容,而是转过头来改变value数组的内容(可以通过反射的方式来修改String对象中的private属性的value),结果会怎样呢?

答案是真的会变哦。

可以先看下下面的代码

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43
private static void deep() throws NoSuchFieldException, IllegalAccessException {

String hello = "Hello World!";

String xx = new String("Hello World!");

String yy = "Hello World!";

/**

* 判断字符串是否相等,默认以内存引用为标准

*/

System.out.println(hello == xx);

System.out.println(hello == yy);

System.out.println(xx == yy);

// 查看hello, xx, yy 三者所指向的value数组的真实位置

Field hello_field = hello.getClass().getDeclaredField("value");

hello_field.setAccessible(true);

char[] hello_value = (char[]) hello_field.get(hello);

System.out.println( hello_field.get(hello));

Field xx_field = xx.getClass().getDeclaredField("value");

xx_field.setAccessible(true);

char[] xx_value = (char[]) xx_field.get(xx);

System.out.println(xx_field.get(xx));

Field yy_field = yy.getClass().getDeclaredField("value");

yy_field.setAccessible(true);

char[] yy_value = (char[]) yy_field.get(yy);

System.out.println(yy_field.get(yy));

/**

* 经过反射获取到这三个字符串对象的最底层的引用数组value,发现如果一开始内容一致的话,java底层会将创建的字符串对象指向同一个字符数组

*

*/

// 通过反射修改字符串引用的value数组

Field field = hello.getClass().getDeclaredField("value");

field.setAccessible(true);

char[] value = (char[]) field.get(hello);

System.out.println(value);

value[5] = '^';

System.out.println(value);

// 验证xx是否被改变

System.out.println(xx);

}

结果呢?

?

1

2

3

4

5

6

7

8

9
false

true

false

[C@6d06d69c

[C@6d06d69c

[C@6d06d69c

Hello World!

Hello^World!

Hello^World!

真的改变了。

而我们也可以发现,hello,xx, yy最终都指向了内存中的同一个value字符数组。这也说明了Java在底层做了足够强的优化处理。

当创建了一个字符串对象时,底层会对应一个盛装了相应内容的字符数组;此时如果又来了一个同样的字符串,对于value数组直接获取刚才的那个引用即可。(相信我们都知道,在Java中数组其实也是一个对象类型的数据,这样既不难理解了)。

不管是字符串直接引用方式,还是new一个新的字符串的方式,结果都是一样的。它们内部的字符数组都会指向内存中同一个“对象”(value字符数组)。

总结

稍微有点乱,但是从这点我们也可以看出String的不可变性其实仍旧是对外界而言的。在最底层,Java把这一切都给透明化了。我们只需要知道String对象有这点特性,就够了。

其他的,日常应用来说,还是按照String对象不可变来使用即可。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

原文链接:http://blog.csdn.net/marksinoberg/article/details/60873982

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 java String 可变性的分析 https://www.kuaiidc.com/118109.html

相关文章

发表评论
暂无评论