String
String是不可变类型。这里的不可变是指,当编译器开辟堆内存来存储String内容后,这个堆内存无法再被外界修改。
但是,程序员可以创建一个String类型变量,通过赋值的方式,使之指向不同的堆内存,从而产生String字符串可变的假象。
由于String字符串的不可变性,就导致拼接、提取字符串的过程中,需要创建新的堆内存来存放结果,这和直接修改堆内存相比要低效的多。
但是Java语言的设计者为什么要这么做呢?
- 不可变字符串有一个优点:编译器可以让字符串共享。也就是说相同内容的字符串,其实是共享同一个堆内存的,不管你创建多少个,只要字符串内容一样,其幕后都指向一个堆内存。
// 不信,你试试
String s1 = "Hello World!";
String s2 = "Hello World!";
//注意:判断两个字符串是否指向同一个内存地址,需要使用‘==’,使用equal是判断两个字符串内容是否相等,注意这两个概念的区别!
System.out.println(s2 == s1);
//你会发现打印结果是:true
字符串共享机制,会使得字符串比较变得非常简单,因为指向同一个堆内存地址的字符串肯定是相等的,而不需要挨个遍历字符。(P.S. 当然了,如果两个字符串并非指向同一个地址,则还是需要比对字符串内容)
注意!!!判断两个字符串是否相等,一定要使用 s1.equals.(s2),而不能直接使用s1 == s2,否则你会遇到很奇怪的调试现象,别怪我没提醒你 -_-||
- 字符串其实很少被修改。翻翻代码你就会发现这个结论。不过,话说回来,如果我真想就像频繁改动字符串,该怎么办呢?Java推荐你使用StringBuilder。
这就是为什么,简单拼接字符串时,你可以优雅地使用字符串拼接符+;当频繁构建字符串时,拼接符+的低效性就暴露出来,StringBuilder虽然难看,但很高效。优雅和高效,二者难以兼得。
final
final关键字是用来控制变量初始化后的对象引用不会再指向其他对象。
String本身是一个不可变的类,指的是不可修改String对象的内容。
如果要确保String类型变量定义之后,永不可变(不可再指向其他堆内存),可以使用final修饰之。
//我们既没办法修改s指向的堆内存的内容,也无法将新的String对象赋值给s.
//因此s永不可变。
private final String s = "Hello World!";
final也可以用来修饰可变类,例如StringBuilder:
private final StringBuilder sb;
sb = new StringBuilder();
//虽然使用final修饰StringBuilder,由于StringBuilder属于可变类,
//因此我们依然可以通过append方法修改sb对象所指向的堆内存的内容。
//但是我们无法重新让sb指向一个新的StringBuilder对象引用。
sb.append("abcd");//正常
sb.append("1234");//正常
sb = new StringBuilder();//无法执行