String s1 = "hello";
String s2 = new String("hello");
这两种方式有什么区别呢?
推荐:https://www.zhihu.com/question/22739143
String中,native方法Intern()是关键
对于String的Intern()的分析:https://www.cnblogs.com/wxgblogs/p/5635099.html
https://www.cnblogs.com/guozhenqiang/p/5633269.html
可以发现java7后,Interned String从PermGen挪到Heap堆。
关于JVM内存的分区情况推荐大家查阅《深入理解Java虚拟机[JVM高级特性与最佳实践](周志明)》的第2.2章节。
相信大家已经基本清楚区别啦,现在知道
String s2 = new String("hello");
会创建几个对象了吗?如果常量池中没有“hello”,会创建两个,是字符串常量池中的“hello 和 JAVA Heap中的 s2引用指向的对象
如何使s3与s1、s2是同一个"hello"的引用?
public class Main {
public static void main(String[] args) throws InterruptedException {
String s3 = new String("hel") + "lo" ;
s3.intern();
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2);
System.out.println(s1 == s3);
}
}
执行结果:
true
true
public class Main {
public static void main(String[] args) throws InterruptedException {
String s1 = "hello";
String s3 = new String("hello") ;
s3.intern();
System.out.println(s1 == s3);
s3 = s3.intern();
String s2 = "hello";
System.out.println(s1 == s2);
System.out.println(s1 == s3);
}
}
运行结果:
false
true
true
总结一下Stirng的对象创建:
1.String s1 = "hello"这种赋值,会在常量池去寻找引用
需要知道:因为String类为final的,“hello”的引用会在类被加载的时候被放到字符串常量池中,“hello”被放在了堆中的Eden区,在执行前,s1还未“连接”上“hello”。当要运行创建s1时,先查看字符串常量池里面有没有对象“hello”的引用,加载的时候被放进来了,找到后,把这个引用赋值给s1完成创建
2.String s3 = new String("hello") 会在java堆上会创建一个对象,其次如果常量池中没有“hello“,会在常量池中创建"hello"的引用, 在堆上放上“hello”字符串
如果把常量池中的引用的String对象用作了同步对象,一定要小心
class B extends Thread{
public void run(){
synchronized ("hello"){
System.out.println("进入B,持有字符串hello锁");
while(true);
}
}
}
class C extends Thread{
public void run(){
System.out.println("C试图去获得字符串hello锁");
//与B中获取的是常量池中的同一个hello,B未释放,一直不能获取到阻塞
synchronized ("hello"){
System.out.println("进入C,持有字符串hello锁");
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
B b = new B();
C c = new C();
b.start();
Thread.sleep(1000);
c.start();
}
}
运行结果:
进入B,持有字符串hello锁
C试图去获得字符串hello锁
B和C中的“hello”字符串都是常量池中的同一个
String类因为是final修饰的,所以不能被继承。String对象被创建后是不可变的,String类中每一个看起来会修改String值的方法,实际上都是创建了一个全新的String对象,最初的String对象丝毫不动
public class Main {
public static void main(String[] args) throws InterruptedException {
String s = "hello";
String s2 = s;
System.out.println(s == s2);
s2 = "world";
System.out.println(s == s2);
}
}
运行结果:
true
false
String的连接可以通过“+”“+=”实现,而编译器自动为这项工作引用了StringBuilder类。StringBuilder可以通过append方法连接,最后通过toString()方法最终结果,所以如果有循环去连接一个长字符串,应该这样写:
public String add(String[] fields){
StringBuilder result = new StringBuilder();
for(int i = 0; i < fields.length; i++){
result.append(fields[i]);
}
return result.toString();
}
而不应该这样写:
public String add(String[] fields){
String result = "";
for(int i = 0; i < fields.length; i++){
//因为使用了‘+=’去连接,每一次循环编译器都会创建新的StringBuilder对象
result += fields[i];
}
return result;
}
避免无意识的递归
所有类都继承于Object类,现在我们想打印对象的地址,如下:
class D{
public String toString(){
return "address" + this ;
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
D d = new D();
System.out.println(d);
}
}
运行后,会出现java.lang.StackOverflowError的异常,这是无限递归造成的。在D类中,重写了toString方法,想要通过this来返回对象的地址,但当"address"去连接this时,this发现是字符串,所以要将自己装换成Stirng,所以又调用了自己的toString,造成了无限递归。应该这样来做:
class D{
public String toString(){
return "address" + super.toString() ; ;
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
D d = new D();
System.out.println(d);
}
}