String的“==”比较

JVM

基本

==

  • 基本类型,值相等,即返回true
  • 引用类型,对象的地址相等,即返回true

equals

  • 比较对象的内容(说法,并不是十分准确,但易理解)相同,即返回true

例子:

1
2
3
4
5
String a = new String("abc");
String b = new String("abc");

System.out.println(a==b);//a与b是两个对象,地址不同,所以是false
System.out.println(a.equals(b));//a和b虽然不是同一个对象,但是内容相同,都是“abc”,所以是true

Object中默认的equals()方法也是通过比较两个对象的地址,来判断两个对象是否相等的,所以默认的equals()方法没有什么意义,我们需要重写自定义类的equals()方法。

String类重写了equals()方法,使得String对象的equals()方法,是按照String的内容进行比较的。

深入

例子一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
String a = new String("ab");//运行时确定,常量池中存在了“ab”,堆中存在了对象a
String b = new String("ab");//运行时确定,用常量池管理"ab",调用构造器创造新的对象b,
String c = "ab";//编译时确定,直接引用了常量池中的"ab"
String d = "a" + "b";//编译时确定,直接引用了常量中的"ab"
String e = "b";
String f = "a" + e;//运行时确定,不能直接引用常量池中的"ab"

//b.intern()返回的是常量池中"ab"的地址,a是堆上a的地址 false
System.out.println(b.intern() == a);
//b.intern()返回的是常量池中"ab"的地址,c是常量池中"ab"的地址的地址 true
System.out.println(b.intern() == c);
//b.intern()返回的是常量池中"ab"的地址,d是常量池中"ab"的地址的地址 true
System.out.println(b.intern() == d);
//b.intern()返回的是常量池中"ab"的地址,f返回的是堆上f的地址 false
System.out.println(b.intern() == f);
//b.intern()返回的是常量池中"ab"的地址,a.intern()返回的是常量池中"ab"的地址的地址 true
System.out.println(b.intern() == a.intern());

“ab”直接量和new String(“ab”)的区别:
当Java程序直接使用形如“ab”的字符串直接量的时候(包括可以在编译时期就计算出来的字符串值)时,JVM会使用常量池来管理这些字符串;当使用new String(“ab”)时候,JVM会先使用常量池来管理“ab”直接量,再调用String类的构造器来创建一个新的String对象,新创建的String对象被保存在堆中。换句话说,new String(“ab”)一共产生了两个对象。

String.intern()方法是一个Native方法,它的作用是:
如果字符串常量池中已经存在了一个等于此String对象的字符串,则返回常量池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。
注意,在不同的JDK版本上有区别,在JDK 1.6中,intern()方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久代中这个字符串实例的引用,在JDK 1.7中intern()的实现,不会再复制实例,只是在常量池中记录首次出现的实例的引用,并且返回这个引用。

JVM常量池保证了相同的字符串直接量只有一个,不会产生多个副本。a,b所引用的字符串在编译时候就已经确定下来了,因此他们都将引用常量池中的同一个对象。

使用new String()创建的字符串是运行时创建出来的,他被保存在运行时数据,(即堆内存上),不会被放入运行时常量池。

例子二:

1
2
3
4
5
6
7
pulic static void main(String[] args){
List<String> list = new ArrayList<>();
int i=0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}

上面这个例子,如果运行在JDK 1.6上,会出现运行时常量池溢出的异常,因为intern()方法会一直把新出现的字符串实例所包含的字符串添加到永久代中。而在JDK 1.7中这段代码,会一直运行下去,因为在JDK 1.7中,不会再复制实力例的内容,而只是在常量池中记录首次出现的实例的引用。

例子三:

1
2
3
4
5
6
7
String abc = new StringBuilder().append("a").append("b").append("c").toString();
System.out.println(abc.intern()==abc);

String abda = "abd";
String abdb = new StringBuilder().append("a").append("b").append("d").toString();

System.out.println(abdb.intern()==abdb);

在JDK 1.6中,返回两个false,而在JDK1.7中返回一个true,一个false。

因为在JDK 1.6中abc.intern()会将abc对象的内容“abc”复制到永久代,然后返回这个永久代中字符串“abc”的引用,所以false,abdb.intern()之前,“abc”已经存在于字符串常量池中了,所以intern()返回的是常量池中“abc”的地址,所以false。
而在JDK 1.7中,abc.intern()只是在常量池中记录了这个abc这个对象的地址,并且返回了这个地址,所以true,而abdb.intern()之前,“abd”已经存在于字符串常量池中了,所以intern()返回的是常量池中“abd”的地址,所以false。

例子二和例子三,体现了不同的字符串常量池的实现。

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器