本文为翻译的文章,作者Rafael Chinelato Del Nero,原文:
https://www.javaworld.com/article/3276354/java-language/java-challengers-2-string-comparisons.html
在字符串池中,字符串方法、关键字和操作符如何来处理比较。
在Java语言中,String类封装了一个char类型的数组。简单来说,String是一个字符串数组,用来构成词语,语句,或者其他任何你想要的数据。
封装是面向对象编程中最强大的概念之一。因为有了封装,你不需要知道String类是如何工作的,你只需要知道使用接口中的哪个方法。
当你观察Java中的String类,你可以看到char数组是如何被封装的:
为了更好的理解封装,考虑一个实际的对象:汽车。为了驾驶它,你需要知道汽车内部是如何运转的吗?当然不,但你需要知道汽车提供了什么交互:比如象油门,刹车,方向盘这样的东西。每个交互都支持某种特定的操作:加速,刹车,向左转,向右转。面向对象编程也是一样的。
我在Java挑战者系列的第一篇博客中介绍了方法重载,String类广泛地使用了这种技术。重载能够让你的类变得真正的灵活,包括String:
这篇Java挑战者不是要尝试理解String类如何工作,而是帮助你理解它做了什么,以及如何在你的代码中来使用它。
什么是字符串池?
String可能是Java中用得最多的类了。如果每次我们使用一个String的时候,都在堆内存中创建一个新的对象,我们可能会浪费很多内存。对于每个字符串的值,字符串池都只保存了一个对象,通过这样的方式来解决上述问题,如下图所示:
图1 字符串池中的字符串
尽管我们创建了Duek和Juggy的字符串变量,但只有两个对象被创建并保存在堆内存中。为了证明这一点,看看下面的代码样例。(回忆一下,Java中“==”操作符被用来比较两个对象,并确定它们是不是一样的。)
这段代码会返回true,因为这两个字符串指向了字符串池中的同一个对象。它们的值是相同的。
一个例外:“new”操作符
现在看看这段代码----它看起来与前面的示例代码比较类似,但有一点差别。
基于前面的样例代码,你可能会认为这段代码会返回true,但它确实是fasle。添加了new操作符,强制在堆内存中创建一个新的String。因而,JVM创建两个不同的对象。
本地方法
Java中的本地方法将会使用C语言来进行编译,通常是为了操纵内存和优化性能的目的。
字符串池和intern()方法
我们使用一种被称为String驻留的技术,把字符串保存在字符串池中。Javadoc告诉我们关于intern()方法的说明:
intern()方法用来把字符串保存在字符串池中。首先,它校验你创建的字符串是池中是否已经存在。如果不存在,它在池中创建一个新的字符串。 在这背后,字符串池的逻辑是基于享元模式的。
现在,请留意当我们使用new关键字来强制创建两个字符串的时候,到底发生了什么:
与前面样例中的new关键字不同的是,这个案例返回了true。这是因为使用intern()方法确保字符串将会被保存在池中。
String类中的Equals方法
equals()方法被用来验证两个Java类的状态是否相同。 因为equals()方法来自Object类,每个Java类都继承它。 但是equals()方法必须要重写才能让它正确地工作。当然,String类重写了equals()
来看一下:
就像你看到的,equals方法是比较字符串的状态而不是对象引用。对象引用是否相同没有关系,字符串的状态将会被比较。
最常用的String方法
在接受字符串比较的挑战之前,最后还有一件你需要知道的事情。想想String类中的这些通用方法:
接受字符串比较的挑战!
让我们快速挑战一下你所学的关于String类的知识。在这个挑战中,你将会使用我们探讨的概念来比较很多字符串。看看下面的代码,你能够确定每个result变量的最终值吗?
下面哪个答案是result变量的最终结果:
A: 02468
B: 12469
C: 12579
D: 12568
刚才发生了什么?理解String的行为
代码的第一行我们看到:
尽管调用trim()方法后的字符串是相同的, 但" powerfulCode "字符串与开始不一样了。在这个案例中,比较返回了false,因为trim()方法移除了两边的空格,导致它使用new操作符强制创建了一个新的字符串。
接下来,我们看看:
这不神秘,在字符串池中,这些字符串是相同的。这个比较返回true。
再接着:
使用new保留字来强制创建两个新的字符串对象,不管它们是否相等。在这个案例中,比较返回了false,即使字符串的值是相同的。
接下来是:
因为我们使用equals()方法,字符串的值而不是对象实例将会被比较。在这个例子中,对象是否相同并没有关系,因为比较的是它们的值。这个比较返回true。
最后,我们有下面的代码:
就像你之前看到的那样,intern()方法把字符串放到了字符串池中。两个字符串都指向了同一个对象,所以这个例子中的比较返回了true。
字符串的常见错误:
想知道两个字符串是否指向同一个对象比较困难,特别是当两个字符串包含了相同的值的时候。记住,使用保留字new总会创建一个新的对象,即使值是相同的。
使用String的方法来比较Object引用也比较棘手。关键是, 如果方法改变了String中的某些东西,那个对象引用将会不同:
一些示例可以帮助澄清这个概念:
这个比较返回true,因为trim()方法并没有生成新的String。
在这个例子中,第一个trim()方法会生成一个新的String,因为方法会执行它的动作,所以引用不同。
最后,当trim()方法执行的时候,它创建了一个新的String:
关于字符串需要记住什么
String是不可变的,所以一个String的状态是不能被改变的
为了节省内存,JVM把String保留在字符池中。当一个新的String创建的时候,JVM检查它的值并指向一个已经存在的对象。如果池没有一个String包含这个值,JVM就创建一个新的String
使用==操作符来比较对象引用。使用equals()方法来比较String的值。 同样的规则适用于所有的对象。
当使用new操作符的时候,在字符池中将会创建一个新的String,即使有一个值相同的String。
原创文章,欢迎转载,但请注明出处。
欢迎大家关注本订阅号互联网全栈架构,长按下图即可。
领取专属 10元无门槛券
私享最新 技术干货