Java Challengers#2:字符串比较
与往常一样,我们在课程开始时还有很多延迟,所以就在昨天,在新线程“ Java Developer”中举行了第二课。 但这就是生活中的小事,但是现在我们继续发布一系列Java Challengers文章,为您准备了这些文章的翻译。
在Java中, String
类封装了一个char
数组( 译者注-在Java 9中,它已经是byte
数组,请参见Java 9中的紧凑字符串 )。 简单来说, String
是用于组成单词,句子或其他结构的字符数组。
封装是面向对象编程中最强大的概念之一。 借助封装,您无需了解String
类的工作原理。 您只需要知道其接口的方法。

当您查看Java中的String
类时,可以看到char
数组的封装方式:
public String(char value[]) { this(value, 0, value.length, null); }
为了更好地理解封装,可以想象一个物理对象:一台机器。 您是否需要知道汽车在引擎盖下的运转方式? 当然不是,但是您应该知道汽车的界面在做什么:油门踏板,制动器和方向盘。 这些接口均支持某些操作:加速,制动,左转,右转。 面向对象编程中也是如此。
Java Challengers系列的第一篇文章是关于方法重载的,该方法重载在String
类中。 重载可以使您的类真正灵活:
public String(String original) {} public String(char value[], int offset, int count) {} public String(int[] codePoints, int offset, int count) {} public String(byte bytes[], int offset, int length, String charsetName) {}
本文将帮助您理解它的作用以及如何在代码中使用它,而不是试图理解String
类的工作原理。
什么是字符串池?
可以说String
类是Java中最常用的类。 如果每次使用String
都在动态内存(内存堆)中创建一个新对象,那么我们将浪费大量内存。 字符串池通过为每个行值仅存储一个对象来解决此问题。

行池中的行
尽管我们使用值Duke
和Juggy
创建了几个String
变量,但只有两个对象被创建并存储在动态内存(堆)中。 请参见以下代码示例以获取证明。 (回想一下,在Java中,“ ==
”运算符用于比较两个对象并确定同一对象是否相同。)
String juggy = "Juggy"; String anotherJuggy = "Juggy"; System.out.println(juggy == anotherJuggy);
该代码将返回true
因为两个String
变量指向字符串池中的同一对象。 它们的含义是相同的。
new
操作员是一个例外。
现在看一下这段代码-它看起来与前面的示例相似,但是有所不同。
String duke = new String("duke"); String anotherDuke = new String("duke"); System.out.println(duke == anotherDuke);
根据前面的示例,您可能会认为此代码将返回true
,但不是。 添加new
运算符会在内存中创建一个新的String
对象。 因此,JVM将创建两个不同的对象。
本机方法
Java中的本机方法是将使用C语言编译的方法,通常目的是管理内存和优化性能。
字符串池和intern()
方法
要将字符串存储在池中,使用了一种称为String interning的方法。
以下是Javadoc告诉我们的intern()
方法:
public native String intern();
intern()
方法用于将字符串存储在字符串池中。 首先,它检查池中是否存在已创建的行。 如果没有,它将在池中创建一个新行。 行池逻辑基于Flyweight模式。
现在,请注意当我们使用new
创建两行时会发生什么:
String duke = new String("duke"); String duke2 = new String("duke"); System.out.println(duke == duke2);
与前面带有new
关键字的示例不同,在这种情况下,比较将返回true
。 这是因为使用intern()
方法可确保字符串在池中。
String
类中的equals
方法
equals()
方法用于检查两个类是否相同。 由于equals()
位于Object
类中,因此每个Java类都会继承它。 但是必须重写equals()
方法才能使其正常工作。 当然, String
会覆盖equals()
。
看一下:
public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String aString = (String)anObject; if (coder() == aString.coder()) { return isLatin1() ? StringLatin1.equals(value, aString.value) : StringUTF16.equals(value, aString.value); } } return false; }
如您所见, String
类的值是通过equals()
而不是通过对象引用进行比较的。 对对象的引用是否不同并不重要; 条件将进行比较。
常见的String
方法
解决字符串比较问题之前,您还需要了解一件事。
考虑String
类的最常见方法:
解决字符串比较问题
让我们通过解决一个小难题来检查您对String
类的了解。
在此任务中,您将使用所学的概念比较几行。 查看下面的代码,您可以确定每个变量result
的值吗?
public class ComparisonStringChallenge { public static void main(String... doYourBest) { String result = ""; result += " powerfulCode ".trim() == "powerfulCode" ? "0" : "1"; result += "flexibleCode" == "flexibleCode" ? "2" : "3"; result += new String("doYourBest") == new String("doYourBest") ? "4" : "5"; result += new String("noBugsProject") .equals("noBugsProject") ? "6" : "7"; result += new String("breakYourLimits").intern() == new String("breakYourLimits").intern() ? "8" : "9"; System.out.println(result); } }
结论是什么?
- A:02468
- 乙:12469
- C:12579
- D:12568
文章结尾给出了正确的答案。
现在发生了什么? 了解String
行为
在第一行中,我们看到:
result += " powerfulCode ".trim() == "powerfulCode" ? "0" : "1";
在这种情况下,结果为false
,因为当trim()
方法删除空格时,它将使用new
运算符创建一个新的String
。
接下来我们看到:
result += "flexibleCode" == "flexibleCode" ? "2" : "3";
这里没有秘密;行池中的行相同。 此比较返回true
。
然后,我们有:
result += new String("doYourBest") == new String("doYourBest") ? "4" : "5";
使用new
会导致创建两个新行,它们是否相等并不重要。 在这种情况下,即使值相同,比较也将为false
。
下一个:
result += new String("noBugsProject") .equals("noBugsProject") ? "6" : "7";
由于我们使用了equals()
方法,因此将比较字符串的值,而不是对象的实例。
在这种情况下,是否比较不同的对象无关紧要,因为会比较该值。 结果是true
。
最后,我们有:
result += new String("breakYourLimits").intern() == new String("breakYourLimits").intern() ? "8" : "9";
如您先前所见, intern()
方法将字符串放入字符串池中。 两条线都指向同一个对象,因此在这种情况下为true
。
常见的字符串错误
很难确定两条线是否指向相同的对象,尤其是当两条线包含相同的值时。 请记住,即使字符串值相同,使用new
总是会导致在内存中创建新对象,这很有用。
使用String
方法比较对象引用也很棘手。 特殊之处在于,如果该方法更改了行中的某些内容,则对对象的引用将有所不同。
一些示例有助于阐明:
System.out.println("duke".trim() == "duke".trim());
这种比较将是正确的,因为trim()
方法不会创建新行。
System.out.println(" duke".trim() == "duke".trim());
在这种情况下,第一个trim()
方法将生成新行,因为该方法将完成其工作,因此链接将有所不同。
最后,当trim()
完成其工作时,它将创建新行:
关于字符串要记住什么
行是不可变的,因此无法更改行状态。
为了节省内存,JVM将字符串存储在字符串池中。 创建新行时,JVM会检查其值并指向现有对象。 如果池中没有具有该值的行,那么JVM将创建一个新行。
运算符“ ==
”比较对象引用。 equals()
方法比较字符串值。 相同的规则将应用于所有对象。
使用new
运算符时,即使有一行具有相同的值,也会在堆中创建新行(翻译器的注释-它以原始形式写入它在池中,但事实并非如此, 谢谢 zagayevskiy )。
答案
这个问题的答案是D。结论将是12568。
待续...