Java 不要在问String为什么是不可变的!
前言
String 是一个特殊的对象,属于引用类型,一经创建初始化后不能更改,由于String 对象的不可变,所以可以共享。
定义
从概念上讲,Java字符串就是Unicode字符序列。在标准Java类库中提供了一个预定义类String。String就是用双引号引起来的几个字符,每个用双引号括起来的字符串都是String类的一个实例:
String s = "abc";
String 对象是不可变的。查看 JDK 文档你就会发现,String 类中每一个看起来会修改 String 值的方法,实际上都是创建了一个全新的 String 对象,以包含修改后的字符串内容。而最初的 String 对象则丝毫未动。看看下面的代码:
public class User {public static String upcase(String s){
return s.toUpperCase();
}public static void main(String[] args) {
String name = "manoninsight";
String name1 = upcase(name);
System.out.println(name1);
System.out.println(name);
}
}
输出:
MANONINSIGHT
manoninsight
当把 name 传递给 upcase() 方法时,实际传递的是引用的一个拷贝。其实,每当把 String 对象作为方法的参数时,都会复制一份引用,而该引用所指向的对象其实一直待在单一的物理位置上,从未动过。
回到 upcase() 的定义,传入其中的引用有了名字 s,只有 upcase() 运行的时候,局部引用 s 才存在。一旦 upcase() 运行结束,s 就消失了。当然了,upcase() 的返回值,其实是最终结果的引用。这足以说明,upcase() 返回的引用已经指向了一个新的对象,而 name 仍然在原来的位置。
【Java 不要在问String为什么是不可变的!】对于一个方法而言,参数是为该方法提供信息的,而不是想让该方法改变自己的。在阅读这段代码时,读者自然会有这样的感觉。这一点很重要,正是有了这种保障,才使得代码易于编写和阅读。
- 小细节:
我们知道String是一个对象,然而我们前面说过对象的创建要通过new关键字创建,而我们在创建String时却写成:
String s = "abc";
而非
String s = new String(“abc”);
1,在字符串池中创建一个对象(此对象是不能重复的)
2,new 出一个对象。Java 运行环境有一个字符串池,由 String 类维护。执行语句 String s="abc"时,首先查看字符串池中是否存在字符串"abc",如果存在则直接将"abc"赋给 s,如果不存在则先在字符串池中新建一个字符串"abc",然后再将其赋给 s。执行语句 String s=new String("abc")时,不管字符串池中是否存在字符串"abc",直接新建一个字符串"abc"(注意:新建的字符串"abc"不是在字符串池中),然后将其付给 s。
重载 String不可变性会带来一定的效率问题。为 String 对象重载的 + 操作符就是一个例子。重载的意思是,一个操作符在用于特定的类时,被赋予了特殊的意义。操作符 + 可以用来连接 String:
public static void main(String[] args) {
String name = "码农洞见";
String age = 30;
String s = "namge:" + name + "age:" + age;
System.out.println(s);
}
可以想象一下,这段代码是这样工作的:String 可能有一个 append() 方法,它会生成一个新的 String 对象,以包含“namge:”与 name 连接后的字符串。该对象会再创建另一个新的 String 对象,然后与“age:”相连,生成另一个新的对象,依此类推。
这种方式当然是可行的,但是为了生成最终的 String 对象,会产生一大堆需要垃圾回收的中间对象。我猜想,Java 设计者一开始就是这么做的(这也是软件设计中的一个教训:除非你用代码将系统实现,并让它运行起来,否则你无法真正了解它会有什么问题),然后他们发现其性能相当糟糕。
为了解决这个问题,在JDK5.0中引入 StringBuilder类。在这之前用的是 StringBuffer。后者是线程安全的,因此开销也会大些。使用 StringBuilder 进行字符串操作更快一点。如果需要用许多小段的字符串构建一个字符串,那么应该按照下列步骤进行。首先,构建一个空的字符串构建器:
StringBuilder builder = new StringBuilder();
当每次需要添加一部分内容时,就调用append方法。
builder.append("abc");
builder.append("123");
...
在需要构建字符串时就调用toString方法,将可以得到一个String对象,其中包含了构建器中的字符序列。
String result = builder.toString();
StringBuilder 提供了丰富而全面的方法,包括 insert()、replace()、substring(),甚至还有reverse(),但是最常用的还是 append() 和 toString()。还有 delete()。
总结 String 是只读字符串,它是一个对象。每次+操作隐式在堆上new了一个跟原来字符串相同的StringBuilder对象,在调用append方法拼接+后面的字符串。在使用过程中在细节上要注意效率问题,例如恰当地使用 StringBuilder 等。
我们都知道Java源自于C++,Java设计者认为C++允许编程人员任意重载操作符是一个很复杂的过程,所以没有纳入到Java中。然而就像现在看到的Python和C#,它们都有垃圾回收机制,操作符重载也简单易懂。所以说在Java中使用操作符重载也并非想象中那么复杂。这也是软件设计中的一个教训:除非你用代码将系统实现,并让它运行起来,否则你无法真正了解它会有什么问题。
最后的最后 为初学者提供学习指南,为从业者提供参考价值。我坚信码农也具有产生洞见的能力。扫描下图二维码关注,学习和交流!
文章图片
推荐阅读
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 急于表达——往往欲速则不达
- 2018-02-06第三天|2018-02-06第三天 不能再了,反思到位就差改变
- 家乡的那条小河
- 一个人的碎碎念
- 赠己诗
- 这辈子我们都不要再联系了
- 死结。
- 我从来不做坏事
- 时间老了