Java初窥门径
- Java万物皆对象,带你进入编程之旅!
- Java运算符,操纵数据全靠它!
- Java控制流程,保持身材尤为重要!
- Java初始化和清理,控制安全是首要!
- Java封装是为了更好的重构!
- JavaOOP优雅总是会有回报!
- Java接口和抽象类如何选择!
- Java集合,日常开发掌握这些就够了
- Java不要再问String为什么是不可变的!
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 仍然在原来的位置。
对于一个方法而言,参数是为该方法提供信息的,而不是想让该方法改变自己的。在阅读这段代码时,读者自然会有这样的感觉。这一点很重要,正是有了这种保障,才使得代码易于编写和阅读。
- 小细节: 我们知道String是一个对象,然而我们前面说过对象的创建要通过new关键字创建,而我们在创建String时却写成:
String s = “abc”;
而非
String s = new String(“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中使用操作符重载也并非想象中那么复杂。这也是软件设计中的一个教训:除非你用代码将系统实现,并让它运行起来,否则你无法真正了解它会有什么问题。