下面的代码运行结果是什么? 解释一下为什么会有这些差异.
- String s1 = "hello";
- String s2 = s1 + ",world";
- String s3 = "hello" + ",world";
- String s4 = "hello,world";
- String s5 = new String("hello,world");
- System.out.println(s2.equals(s4)); // true
- System.out.println(s2==s4); // false
- System.out.println(s3==s4); // true
- System.out.println(s4==s5); // false
看似简单的代码, 却有很多学问在里面. 在此之前先非常简单地说一下 JVM 中的栈和堆, 栈一般存放的是局部变量, 对象的引用等; 堆一般放对象, 常量等, 在 jdk1.7 以后的版本, 字符串常量池也在堆中.
字符串常量池
一般情况下, 创建字符串对象有两种方式, 一种是字面值创建, 一种是 new 创建, 这两者是不一样的.
如果是字面值创建的方式, 如 String s4="hello,world",JVM 会先去字符串常量池中寻找有没有 "hello,world" 这个字符串, 若有, 则将其地址给 s4; 若没有, 则先在常量池里创建 "hello,world", 然后再把地址给 s4; 而通过 new 的方式创建对象, 则是在堆中创建 "hello,world" 对象, s5 指向这个对象. 还是看图吧, 比较直观:
equals() 和 ==
这是 Java 面试常考点, 有人可能会说 equals() 比较的是值, 而 == 比较的是对象的引用 (没错是我), 直到被面试官吊打, 才会老老实实去看 Object 类中 equals() 的源码:
可以看到, equals() 方法体中, 返回的是 两个对象的引用的比较结果 . 但是, 两个 String 类型 == 和 equals() 有时候结果却不一样. 那是因为在 String 类中, equals() 被重写了:
可以看到, 它先对比两个引用; 若一样则直接返回 true, 不一样就对它们的值进行比较. 因此, 可以对 == 和 equals() 更好的区别了:
字符串的拼接
String 类被 final 修饰, 因此字符串不能修改, 当两个字符串相加时, 是先生成 StringBuilder 对象, 然后通过 append() 的方式将两个字符串拼接, 再调用 toString() 方法生成新的字符串对象. 所以只考虑 s1 和 s2, 他们在 JVM 中是这样的:
但是像 String s3 = "hello" + ",world" 这样直接两个字面值相加的, java 文件在编译期间就已经将这条语句做了优化, 将其直接变成 "hello,world", 等到运行的时候就查找字符串常量池, 因此 s3 == s4 返回的结果就为 true.
String,StringBuilder,StringBuffer
既然上面说到了 StringBuilder, 那就顺便简单说一下这三者之间的区别.
String 是常量, 且每次需要在原来字符串基础上扩展都需要新建对象, 导致速度会稍微慢一点, 而且占内存, 因此对于需要经常扩展的字符串, 可以使用 StringBuilder 和 StringBuffer. 但是 StringBuilder 和 StringBuffer 又有区别, 即时两者很像, 在速度上 StringBuilder 会快一些, 因为 StringBuffer 的操作加了 synchronized , 即加了锁, 使得操作相对比较慢, 就举 append() 为例子, StringBuffer 中此方法的源码如下:
所以, 一般在单线程的情况下, 可以选择使用 StringBuilder; 在多线程的情况下可以选择 StringBuffer .
总结
回到一开始的程序, 我们可以大致地画出在 JVM 中的存储:
来源: https://www.cnblogs.com/lyuzt/p/12075568.html