前言
有人说看了你的基本类型篇,那我期望有 java 语言表达一句话还不成了是吧,我是不是得一字一顿呢?当然不是啦,Java中存在着 String ,只要使用 双引号 概括起来的那么就是一个String( 字符串 )。
设计
字符串操作那可是计算机程序设计中最常见的行为了,使用频率高的因素决定了其定位与设计,设计者将其定位为不可变,不可变的表现如下:
- 在设计上 String类 通过final修饰了类,同时将存放内容的char数组也使用final修饰。final关键字修饰的属性为不可修改的,修饰的类是不可继承的,如此就限制了使用者对其修改。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc {
@Stable
private final byte [] value;
//...
}
- 我们可以通过String类的源码先从表象看一看,每一个看起来会修改其值的方法最终都是创建了一个新的String对象。如下图字符串的截取方法,指定起止下标,但返回时如果截取长度与字符串长度一致返回当前对象,否则就创建一个新的对象返回。
public String substring(int beginIndex, int endIndex) {
int length = length();
checkBoundsBeginEnd(beginIndex, endIndex, length);
int subLen = endIndex - beginIndex;
if (beginIndex == 0 && endIndex == length) {
return this;
}
return isLatin1() ? StringLatin1.newString(value, beginIndex, subLen)
: StringUTF16.newString(value, beginIndex, subLen);
}
位置
- 使用常量方式创建的字符串存放在常量池里
- 使用new方式创建的字符串存放在堆里
说到这里就顺便了一下String提供的方法intern(),这个方法就有点意思了,根据下图源码表示,这个方法是返回一个标准化的表示方式,通过字符串的常量池进行返回,也就是说针对字符串调用此方法就会统一表示,主要是针对通过new创建在堆上的字符串,调用此方法,它会先去常量池里发现,如果存在返回它,如果不存在就会放入常量池并返回,简单理解就是将存在于堆的字符串放入常量池。
/**
* Returns a canonical representation for the string object.
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
* <p>
* It follows that for any two strings {@code s} and {@code t},
* {@code s.intern() == t.intern()} is {@code true}
* if and only if {@code s.equals(t)} is {@code true}.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* <cite>The Java™ Language Specification</cite>.
*
* @return a string that has the same contents as this string, but is
* guaranteed to be from a pool of unique strings.
* @jls 3.10.5 String Literals
*/
public native String intern();
重载“+”
重载就是”+”操作符在应用于对应类时具有特殊意义,这里对于String而言“+”即为连接的作用,以下通过javap命令对于源码进行反编译,进行更直观的确认:
java反编译流程(参数很多,请根据需要进行添加)
1、 javac XXX.java
2、javap -v XXX
public class Test {
public static void main(String[] args) {
String a = "a";
String s = "b" + "c" + "d" + a;
System.out.println(s);
}
}
0: ldc #2 // String a
2: astore_1
3: new #3 // class java/lang/ StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: ldc #5 // String bcd
12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: aload_1
16: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: astore_2
23: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_2
27: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: return
通过将源代码javap反译后可以看到指令码中,Java在处理”+”拼接的字符串时借助了StringBuilder类, append 方法即追加内容,调用 toString 方法new出来最终结果。
有人会问拼接时有四个元素进行拼接,为什么只调用了2次append方法呢,因为前3个元素均在常量池优化合并。
为了直观地看出,下面的例子,请参照如下,共计调用5次append方法:
public class Test {
public static void main(String[] args) {
String a = "a";
String s = "b" + a + "c" + a + "d";
System.out.println(s);
}
}
0: ldc #2 // String a
2: astore_1
3: new #3 // class java/lang/StringBuilder
6: dup
7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
10: ldc #5 // String b
12: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: aload_1
16: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc #7 // String c
21: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: aload_1
25: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: ldc #8 // String d
30: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
33: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
36: astore_2
37: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
40: aload_2
41: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
44: return
弯弯绕绕面试
一、请写出以下程序的输出结果
public class Test {
public static void main(String[] args) {
String a = "a";
String b = "b";
String ab = "ab";
String sum = "a" + "b";
String sum_a = a + "b";
String sum_ab = a + b;
System.out.println(ab == sum); // true
System.out.println(ab == sum_a); // false
System.out.println(ab == sum_ab); // false
System.out.println(sum == sum_a); // false
System.out.println(sum == sum_ab); // false
System.out.println(sum_a == sum_ab);// false
}
}
根据前面我们通过反编译得出来的结论:
1、常量拼接仍在常量池;
2、有变量参与拼接, JDK 就会估化操作:
String a = "a";
String sum_a = a + "b";
StringBuilder temp = new StringBuilder();
temp.append(a);
temp.append(b);
temp.toString();// StringBuilder的toString()方法为new String()
二、问以下语句共创建了几个对象
String s = new String("abc");
以上语句等价于:
String temp = "123"; // 0~1 常量池存在,则将堆内对象引用赋值,否则在堆内创建对象,并加入常量池
String s = new String(temp); //1
所以上述语句共创建1~2个对象
三、请问以下代码是否存在问题
String Lock = Thread.currentThread().getName();
synchronized ( lock ) {
// DO SOMETHING
}
Lock取值为当前 线程 的名称,但线程名称非常量,存在于堆内,因此在这种情况下会导致锁失效。
需使用intern()方法将lock加入常量池。