前言

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

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

起因

正如我们所理解的,通过

string hello = "hello world!";

string xx = new string("hello world!");

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

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

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

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

/**

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

*/

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

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

且看下面的代码:

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));

}

得到的结果如下:

字符串赋值方式:

true

true

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

false

true

new 方式:

false

true

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

深入源码

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

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

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

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

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

/**

* returns a string resulting from replacing all occurrences of

* {@code oldchar} in this string with {@code newchar}.

*

* 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}.

*

* examples:

*

* "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)

*

*

* @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;

}

源码中很明确的使用了

new string(buf, true);

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

真的不可变吗?

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

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

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

答案是真的会变哦。

可以先看下下面的代码

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);

}

结果呢?

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对象不可变来使用即可。

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

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

java string 分析_java String 可变性的分析相关推荐

  1. java AST 表达式_java AST JCTree简要分析

    JCTree简要分析 [toc] JCAnnotatedType 被注解的泛型:(注解的Target为ElementType.TYPE_USE时可注解泛型) public static class A ...

  2. java转型 内存_java 对象转型内存分析

    对象转型: 一个基类的引用类型变量可以指向其子类的对象(要求传个动物,传给狗是可以的,狗是动物) 一个基类的引用不可以访问其子类对象的新增成员(狗会游泳不代表所有的动物都会游泳,把狗当作动物来看时不可 ...

  3. java自制缓冲池_java String 缓冲池概念的举例说明

    口试中常会碰到String对象构造的题目.例如String s= new String("a");创建了几个对象. 下面根据代码具体解释一下各种String构造情况. 代码如下: ...

  4. java string范围_java,String

    很多的编程语言都会强调利用字符数组来描述字符串,实际上在Java里面也存在有类似的概念,在String类中也提供有一系列与字符操作有关的方法.No.方法名称类型描述 1public String(ch ...

  5. java equals 字符串_Java String 字符串 比较 == equals

    1.==该运算符表示指向字符串的引用是否相同,如t1==t2这种情况, 是因为在java中字符串的值是不可改变的,相同的字符串在内存中只会存一份, 所以t1和t2指向的是同一个对象,而t1==t4返回 ...

  6. java 不可变_java - String是不可变的。 究竟是什么意思?

    java - String是不可变的. 究竟是什么意思? 这个问题在这里已有答案: Java中字符串的不变性                                     24个答案 我在不 ...

  7. java replace无效_Java String.replace()方法无效的原因及解决方式

    首先我们来看个例子 public class Demo1 { public static void main(String[] args) { String aa="abcd"; ...

  8. java isempty用法_Java String 的 isEmpty() 与 null 与 区别解析

    问:简单说说 String 的 isEmpty() 与 null 与 "" 的区别? 答: public class Test { public static void main( ...

  9. java contains 效率_java String contains逻辑的优化

    有一个"AAA,BBB"格式的字符串组成的List,有一个字符串SSS 现在想遍历List,如果字符串SSS中包含了AAA,并且包含了BBB,则返回ture.否则将List全部遍历 ...

最新文章

  1. js进阶 13-5 jquery队列动画如何实现
  2. iOS 打电话回到当前应用
  3. 【NIO】通道Channel
  4. python画直方图成绩分析-Python数据分析:直方图及子图的绘制
  5. Spring-AOP @AspectJ进阶之切点复合运算
  6. 辽宁科技大学计算机好调剂吗,2020年辽宁科技大学硕士研究生调剂办法
  7. 1 游戏逻辑架构,Cocos2d-x游戏项目创建,HelloWorld项目创建,HelloWorld程序分析,(CCApplicationProtocol,CCApplication,AppDeleg
  8. django mysql 表单_Python Django 表单提交数据到mysql并展示
  9. 蓝鸽英语学习平台_蓝鸽集团携手英特尔,共筑智慧校园新生态——蓝鸽amp;英特尔智慧校园建设高峰论坛顺利举办...
  10. win11 P85主板能正常安装吗 windows11使用p85主板的安装的步骤方法
  11. 监控某个dll被修改_浅谈动力环境监控系统技术标准
  12. python Udp与Tcp
  13. c++小游戏代码(5个) 免费
  14. 前端H5企业微信第三方应用开发浅谈(一)
  15. H3CNE中单臂路由实验
  16. 冰冻效果Shader案例
  17. tds与oracle,oracle和sqlserver找不到字段的错误
  18. 对ActiveX控件进行注册
  19. python控制台程序学生管理系统实验总结_管理信息系统实验总结报告
  20. visio画图-----如何克服两箭头交叉变形 及 箭头自动重绘?

热门文章

  1. 关于Aspose对于Word操作的一些扩展及思考
  2. 面对Mission Impossible,你会怎么做?
  3. 第二阶段冲刺—第一天
  4. luogu P1586 四方定理(背包)
  5. 克鲁斯卡尔重构树小结
  6. 自定义控件-----输入框
  7. Maven 打包过滤掉jar包、class文件和指定jsp文件
  8. PHP生成zip压缩包
  9. nginx 开启gzip压缩--字符串压缩比率很牛叉
  10. ListView上拉加载,下拉刷新 PullToRefresh的使用