当前位置: 首页 > news >正文

Java中的String对象是不可变的吗

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

有个仁兄在 StackOverflow 上发起了一个问题,是这么问的:

众所周知Java中的String对象是不可变的,但我们来看下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
String s1 ="Hello World";
String s2 ="Hello World";
String s3 = s1.substring(6);
System.out.println(s1);// Hello World
System.out.println(s2);// Hello World
System.out.println(s3);// World
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
char[] value = (char[])field.get(s1);
value[6] ='J';
value[7] ='a';
value[8] ='v';
value[9] ='a';
value[10] ='!';
System.out.println(s1);// Hello Java!
System.out.println(s2);// Hello Java!
System.out.println(s3);// World

为什么这段代码会是这样的运行结果?为什么s1和s2的值被改变了,但是s3的值却没有?

回答#1:

String对象是不可变的,但这仅意味着你无法通过调用它的公有方法来改变它的值。

上面的代码通过反射机制绕过了正常的API。采用这种方式,你还可以改变枚举的值,甚至可以改变Integer类型自动装箱时使用的查找表。

在这里,由于s1和s2指向同一个内部的字符串对象,因此他们的值都被改变了。正如其他回答所述,这是由编译器实现的。

s3没有被改变的原因确实令我诧异,我过去一直认为s3和s1共享同一个value数组(在Java 7u6之前的版本中的确是这样)。然而,通过查看String类的源码,我们可以看出子串对象的value数组是从原字符串对象中拷贝得到的(通过使用Arrays.copyOfRange(..)方法)。这是s3没有被改变的原因。

你可以安装一个SecurityManager来防止恶意代码执行这一类的操作。但需要注意的是,有些库的实现依赖于这种反射技巧(例如ORM工具,AOP库等)。

我在回复的开头写道String对象并不是真的不可变,只是“看上去不可变”。这可能会误导读者以为String类的当前版本在访问限制方面有所疏忽,但事实上value数组使用了private和final修饰符。因此开发者需要注意:在Java中无法将数组声明为不可变的,即使使用了正确的访问修饰符,也不能将它暴露在类的外面。

鉴于这个话题如此火热,向各位推荐一些进阶读物:2009年JavaZone会议上Heinz Kabutz发表的关于反射技术的疯狂演说,本文涵盖了反射操作中的常见问题,以及其他一些有关反射技术的内容。本文非常好,也非常疯狂。

本文揭示了为什么反射技术在某些场景下非常实用,但在大多数情况下,你应该避免使用它。

回答#2:

在Java中,如果两个String类型的变量被初始化为同一个字符串,那么这两个变量将被赋值以相同的对象引用。这就是表达式“Test1==Test2”返回值为true的原因。

1
2
3
StringTest1="Hello World";
StringTest2="Hello World";
System.out.println(test1==test2);// true

Test3是由substring()方法创建的一个新的String对象,它并没有和Test1共享同一个value数组。(注:由于原作者笔误,下图中变量test1和test3的首字母并未大写,望读者注意)

我们可以通过反射技术访问String对象,获取到value数组的指针:

1
2
Field field =String.class.getDeclaredField("value");
field.setAccessible(true);

改变这个value数组的值便能够改变所有持有该数组指针的String对象的值,因此Test1和Test2的值都发生了改变。但由于Test3是由substring()方法创建的一个新的String对象,它的值并没有被改变。

转载于:https://my.oschina.net/Chaos777/blog/212279

相关文章:

  • 9款极具创意的HTML5/CSS3进度条动画
  • 一、NGINX简介
  • 页面工具集
  • 大型网站性能监测、分析与优化常见问题QA
  • 导数组合的极限
  • JVM的粗略简述
  • jquery 重置表单
  • MyBatis 通过包含的jdbcType类型
  • nginx+tomcat+memcached 集群环境搭建
  • 【ISC安全训练营】挑战价格极限第三天!!![北京]
  • rownum order by
  • UVa712 S-Trees
  • 线程池
  • TYVJ P1020 寻找质因数
  • 疯狂ios讲义之使用CoreLocation定位(4)
  • git 常用命令
  • JS变量作用域
  • js算法-归并排序(merge_sort)
  • spring学习第二天
  • Web标准制定过程
  • 关于for循环的简单归纳
  • 前端每日实战:61# 视频演示如何用纯 CSS 创作一只咖啡壶
  • 数据可视化之 Sankey 桑基图的实现
  • 数据库写操作弃用“SELECT ... FOR UPDATE”解决方案
  • 主流的CSS水平和垂直居中技术大全
  • Nginx实现动静分离
  • 移动端高清、多屏适配方案
  • #include
  • #单片机(TB6600驱动42步进电机)
  • $.ajax,axios,fetch三种ajax请求的区别
  • (32位汇编 五)mov/add/sub/and/or/xor/not
  • (ctrl.obj) : error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MDd_DynamicDebug”不匹配值“
  • (floyd+补集) poj 3275
  • (JS基础)String 类型
  • (Python第六天)文件处理
  • (分布式缓存)Redis持久化
  • (附源码)计算机毕业设计ssm基于B_S的汽车售后服务管理系统
  • (深度全面解析)ChatGPT的重大更新给创业者带来了哪些红利机会
  • (生成器)yield与(迭代器)generator
  • (算法)Travel Information Center
  • (一)硬件制作--从零开始自制linux掌上电脑(F1C200S) <嵌入式项目>
  • .bat批处理(五):遍历指定目录下资源文件并更新
  • .NET CLR Hosting 简介
  • .NET Standard 的管理策略
  • .net 使用$.ajax实现从前台调用后台方法(包含静态方法和非静态方法调用)
  • .NET 依赖注入和配置系统
  • .NET框架
  • /bin/bash^M: bad interpreter: No such file ordirectory
  • /etc/sudoers (root权限管理)
  • ::前边啥也没有
  • @EnableAsync和@Async开始异步任务支持
  • @FeignClient注解,fallback和fallbackFactory
  • @TableLogic注解说明,以及对增删改查的影响
  • [ 云计算 | AWS ] AI 编程助手新势力 Amazon CodeWhisperer:优势功能及实用技巧
  • [<死锁专题>]