在java中String是一个比较特殊的类,String有一个自己的常量池,存放了初始化的字符串,这一个特性经常能导致一些坑,下面简单的介绍一点。
一,用常量赋值的字符串
String a=”abc”;
当执行这条语句时,先在常量池中查找是否有”abc”对象,如果有则创建对象a并指向这个对象,如果没有则先在常量池中创建”abc”对象,然后创建对象a并指向这个对象。
最终a是指向常量池的。
所以:
String a="abc";
String b="abc";
System.out.println(a==b);
输出的是true
二,new的字符串
String a=new String("abc");
先在常量池中查找是否有”abc”对象,如果有则把常量池中的对象复制一份,然后创建对象a并指向复制出来的这个对象,如果没有则先在常量池中创建”abc”对象,然后复制一份,然后创建对象a并指向复制出来的这个对象。
最终a不是指向常量池的。而且在第一次使用该字符串时,内存里存了两个这个字符串的对象,一个在堆里一个在常量池里。
所以:
String a=new String("abc");
String b="abc";
System.out.println(a==b);
输出的是false
再一个例子:
String a=new String("abc");
String b= new String("abc");
System.out.println(a==b);
输出的也是false
三,String的intern()方法
在这里要特别提到String的intern()方法,根据jdk对这个方法的描述,翻译一下大概是这样的:
当这个方法被调用时,如果常量池中有同值的字符串,则返回常量池中的字符串。如果常量池中没有这个字符串,则在常量池中创建这个字符串并返回。所谓同值,指的是equals()方法返回值为true。
所以,以下代码输出的是true
String a="abc";
String b=new String("abc").intern();
System.out.println(a==b);
四,System的identityHashCode()方法
另外提一下System.identityHashCode()方法,这个方法可以返回对象所属类的原有的HashCode,这个方法适合应用于hashCode()方法被重写的类。
比如本文中提到的String类的hashCode方法就被重写过,相同字符串的hashCode往往是一样的,而这个方法返回重写前的那个hashCode()方法的返回值,比如:
String a="abc";
String b="abc";
String c=new String("abc");
String d=new String("abc");
System.out.println(System.identityHashCode(a));
System.out.println(System.identityHashCode(b));
System.out.println(System.identityHashCode(c));
System.out.println(System.identityHashCode(d));
输出的内容是:
1502913001
1502913001
756151793
1982445652
可以看到,a和b确实是指向一个对象,而c和d都是堆中单独的对象。
五,synchronized关键字和String
java的synchronized关键字在生效时就和内存地址有关,特别是当我们使用类似
synchronized(某个变量){}
这种格式时,参数对象的内存地址只要相同,synchronized关键字的同步功能就会生效,所以我们可以写以下例子:
package test;
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) { //新建10个线程一起抢锁
String key = new String("abc");
TestThread t = new TestThread(key);
t.start();
}
}
/**
* 使用构造时传入的参数String作为锁,观察线程获得锁的情况
*/
public static class TestThread extends Thread {
public String key;
public TestThread(String key) {
this.key = key;
}
@Override
public void run() {
synchronized (key) {
System.out.println(Thread.currentThread().getName() + " begin " + key);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " end " + key);
}
}
}
}
这个例子中的同步语句是失效的,每个线程都拿到了锁并且并行执行了,因为每次定义一个线程时使用的String对象是用new的方式获得的,在内存中是不同的地址。
然后改一下key的定义方式,改成以下的例子:
package test;
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) { //新建10个线程一起抢锁
String key = "abc";
TestThread t = new TestThread(key);
t.start();
}
}
/**
* 使用构造时传入的参数String作为锁,观察线程获得锁的情况
*/
public static class TestThread extends Thread {
public String key;
public TestThread(String key) {
this.key = key;
}
@Override
public void run() {
synchronized (key) {
System.out.println(Thread.currentThread().getName() + " begin " + key);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " end " + key);
}
}
}
}
这个例子中的同步语句是生效的,每个线程争抢同一把锁,虽然key是在每次循环中定义的,但是其实使用的都是常量池中的对象,synchronized认为这都是一个对象,所以同步生效。
以上。