Java 字符串常量 Java 字符串在面试、笔试中都是常考知识点,下面进行相关知识点讲解。
例子 1
2
3
4
5
6
7
8
9
String strOne = "testString" ;
String strTwo = "testString" ;
System.out.println(strOne.equals(strTwo));
System.out.println(strOne == strTwo);
true
true
*/
结果分析 String的equals()是比较字符串的内容。故equals得到true很容易理解。
==是比较内存地址的,第一个true说明strOne 和strTwo的地址相同。
为什么呢?
java有常量池,存储所有的字符串常量。
String strOne = “testString” java首先会在常量池查找是否有 “testString”这个常量,发现没有,于是创建一个 “testString”,然后将其赋给strOne。
String strTwo= “String”; java同样会在常量池中查找 “testString”,这次常量池中已经有strOne 创建的 “testString”,故不创建新的常量,将其地址赋给strTwo。
如此,strOne和strTwo便有了相同的地址。
new建立String对象 Java 字符串对象创建有两种形式:
上文的字符串常量,如String str = "testString"
使用用new这种标准的构造对象的方法,如String str = new String("testString")
举例说明 1 1
2
3
4
5
6
7
8
9
String strOne = "testString" ;
String strThree = new String("testString" );
System.out.println(strOne.equals(strThree));
System.out.println(strOne==strThree);
true
false
*/
举例说明 2 1
2
3
4
5
6
7
8
9
String strOne = new String("testString" );
String strThree = new String("testString" );
System.out.println(strOne.equals(strThree ));
System.out.println(strOne==strThree);
true
false
*/
分析原因 而用new String(“testString”)创建的两个字符串,用equals()比较相等,用==比较则不相等。
为什么呢?
new String()每次会在堆中创建一个对象,每次创建的对象内存地址都不同,故==不相等。但字符串内容是相同的,故equals()相等。
String intern() 方法 看下官方文档的说明,大致意思是:
如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回。
1
2
3
4
5
6
7
8
9
10
11
12
public String intern ()
Returns a canonical representation for the string object.
A pool of strings, initially empty, is maintained privately by the class String.
When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals (Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.
It follows that for any two strings s and t, s.intern () == t.intern() is true if and only if s.equals(t) is true .
All literal strings and string-valued constant expressions are interned. String literals are defined in section 3.10 .5 of the The Java™ Language Specification.
Returns:
a string that has the same contents as this string, but is guaranteed to be from a pool of unique strings.
举例说明 1 1
2
3
4
5
6
7
String strOne = "testString" ;
String strThree = new String("testString" );
System.out.println(strOne==strThree.intern());
true
*/
String strOne = "testString"
这行代码使得常量池中已经存在了"testString"
,intern() 从字符串常量池中查询到当前字符串已经存在,返回常量池中的字符串
举例说明 2 1
2
3
4
5
6
7
String strOne = new String("testString" );
String strThree = new String("testString" );
System.out.println(strOne.intern()==strThree.intern());
true
*/
如文档里所说 s.intern() == t.intern() is true if and only if s.equals(t) is true
。
strOne.intern()
先查找常量池中是否存在当前字符串,发现不存在,于是会把字符串放入常量池中。strThree.intern()
也先从常量池中查找,找到了刚才放入的字符串,所以两者相等。
底层实现 new 创建的String对象使用intern方法,intern方法,若不存在就会将当前字符串放入常量池中。
intern() 底层实现也是维护了一个hash表,保持字符串。
1
2
3
4
5
6
7
8
9
10
11
oop StringTable::intern(Handle string_or_null, jchar* name,
int len, TRAPS) {
unsigned int hashValue = java_lang_String::hash_string(name, len);
int index = the_table()->hash_to_index(hashValue);
oop string = the_table()->lookup(index, name, len, hashValue);
if (string != NULL ) return string ;
return the_table()->basic_add(index, string_or_null, name, len,
hashValue, CHECK_NULL);
}
查看源码可知,intern() 是使用 jni 调用c++实现的StringTable的intern方法, StringTable的intern方法跟Java中的HashMap的实现是差不多的, 只是不能自动扩容。默认大小是1009。
注意,数据量规模很大时,如果程序将很多字符串常量(类名、方法名、key值等)存入intern()的常量池,当池大小超过了默认值后,性能可能会急剧下降。
String 真的不可变么 不可继承 源码可以看到 public final class String
,所以 String 也不允许继承。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public final class String
implements java .io .Serializable , Comparable <String >, CharSequence {
private final char value[];
private int hash;
* Initializes a newly created {@code String} object so that it represents
* an empty character sequence. Note that use of this constructor is
* unnecessary since Strings are immutable.
*/
public String () {
this .value = "" .value;
}
* Initializes a newly created {@code String} object so that it represents
* the same sequence of characters as the argument; in other words, the
* newly created string is a copy of the argument string. Unless an
* explicit copy of {@code original} is needed, use of this constructor is
* unnecessary since Strings are immutable.
*
* @param original
* A {@code String}
*/
public String (String original) {
this .value = original.value;
this .hash = original.hash;
}
不可改变 如果对一个已有字符串进行修改,不会在原来内存地址上修改数据,而是重新指向一个新的对象。
1
2
3
4
5
6
7
String x = "123456" ;
x.substring(0 , 3 );
System.out.println(x);
123456
*/
使用 substring 改变字符串,也是返回一个新的字符串,而不是原地修改。
利用反射改变字符串 String 内部使用 private final char value[];
来保存值,而且也没有暴露关于value的引用。虽然数组value
一旦赋值后,不允许修改,但是这仅仅是不能指向新的数组,数组的内容其实可以修改。
我们可以利用反射得到 value ,从而改变 value 的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
String x = "123456" ;
Field f = String.class.getDeclaredField("value" );
f.setAccessible(true );
f.set(x, "helloworld" .toCharArray());
System.out.println(x);
char [] value = (char []) f.get(x);
value[0 ] = 'H' ;
value[1 ] = 'E' ;
System.out.println(x);
helloworld
HElloworld
*/
看下面这段代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
String x = "123456" ;
String y = "123456" ;
Field f = String.class.getDeclaredField("value" );
f.setAccessible(true );
f.set(x, "helloworld" .toCharArray());
System.out.println(x);
System.out.println(y);
char [] value = (char []) f.get(x);
value[0 ] = 'H' ;
value[1 ] = 'E' ;
System.out.println(x);
System.out.println(y);
helloworld
helloworld
HElloworld
HElloworld
*/
x
和 y
本来都是"123456"
,我们只利用反射改变了x
的值,却发现y
的值也跟着变了,因为这两者都是指向常量值中的字符串,所以修改一个,另外一个也变了。
不推荐实际生产环境中使用反射改变字符串,违背String不可变行
参考文章