引言

在java中每天都会使用String。对于String的特性和用法需要进行深入的了解

immutable(不可变)

  1. 安全性:final修饰的类意味着不能被继承,所有的属性方法不能被修改,安全。
    不可变也意味着可以被共享
  2. 效率:因为都是final修饰编译器可以做一定的优化。
    可以被共享不需要进行多线程之间的同步。

String 加号的本质

1
2
3
String str="hello";
str = str+"world";

由于我们不熟悉Class文件结构,
而且字节码非常不容易看懂,在这里不直接查看编译生成的StringTest.class文件的内容,
而是通过jad工具反编译字节码查看结果。
在cmd下执行jad命令jad -o -a -sjava StringTest.class成功执行上述命令后,
会发现StringTest.class文件所在目录下会多出源文件StringTest.java,

内容如下:

1
2
3
String s = "Hello";
s = (new StringBuilder(String.valueOf(s))).append(" world!").toString();
所以应该避免使用加号,直接使用StringBuffer(),
因为都是方法内应用不存在线程安全问题。StringBuffer更快

相等判断的说明

String中==判断结果解释

1
2
3
4
String a = "a1";
String b = "a" + 1;
System.out.println(a == b);

结果为true

说明在编译期间生成的字节码命令已经决定这些对象指向哪些地址,
存在什么位置


实际上在java编译器的编译期,已经进行了字面量的拼接。
也就是说编译生成的Class文件中并不存在String b = “a” + 1对应的字节码指令,
已经被优化为String b = “a1”对应的字节码指令。

在编译期间能够确定结果并进行计算,
(也就是生成class文件时)就能有效减少Class文件中的字节码指令,
即减少了程序运行时需要执行的指令,提高了程序效率
1
2
3
4
5
String hw = "Hello world!";
String h = "Hello";
h = h + " world!";
System.out.println(h == hw);

结果为false

存在new StringBulider(String.valueOf(h))
所以前面的h指向的是java的堆,而”world”指向的是常量池很显然这两个不是一个地址。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static final String h2 = "Hello";
public static final String h4 = getH();
private static String getH() {
return "Hello";
}
public static void main(String[] args) {
String hw = "Hello world!";
final String h1 = "Hello";
final String h3 = getH();
String hw1 = h1 + " world!";
String hw2 = h2 + " world!";
String hw3 = h3 + " world!";
String hw4 = h4 + " world!";
System.out.println(hw == hw1);
System.out.println(hw == hw2);
System.out.println(hw == hw3);
System.out.println(hw == hw4);
}

true、true、false、false

局部变量h1被final修饰,意味着h1是常量。
常量和常量组合在编译器就能计算出结果。

h2同理,是静态常量

h3也是常量但是h3值得确定需要通过方法调用进行赋值。在编译期间无法确定所以。
所以h3也是指向>java堆

h4同理
1
2
3
4
String str="abc";
String strObj = new String("abc");
System.out.println(str==strObj);

结果为false

一个是对象存放在java堆中一个事常量存放在常量池中

面两种给String类型变量赋值的方式,除了它们指向不同的String对象外,其它并没有什么区别。从程序效率的角度看,推荐使用方式一给String类型变量赋值,因为方式二多了一次java堆的String对象分配。

前面说过,字符串字面量直接被看作String类的一个实例,实际是其在编译期就存放在Class文件的常量池中,当Class文件被jvm加载时,其就进入到方法区的运行时常量池中。如果想在运行期间将新的常量加入常量池中,可调用String的intern()方法。 当调用 intern方法时,如果常量池已经包含一个等于此String对象的字符串(用equals(Object)方法确定),则返回常池中的字符串。否则,将此String 对象添加到池中,并返回此String对象的引用。