原文链接:https://www.javaworld.com/article/3276354/java-language/java-challengers-2-string-comparisons.html
在Java中,String类封装一个char数组。简单来说,String 是一个char数组用于组合文字,语句或者任何你想要的数据。
封装是面向对象编程最强大的概念之一。因为封装,你不用了解String类是如何运作的,你只需要知道如何从它的接口中使用对应的方法。
当你阅读String类的源码,你能了解char数组是如何被封装的:
public String(char value[]) { this(value, 0, value.length, null); }
为了更好地理解封装,我们可以以一辆车为例。为了能够驾驶车辆我们是否需要了解车辆在引擎盖下是如何运作的?当然不需要,但是你需要知道车辆的接口能够做什么比如油门,刹车以及方向盘。在这里,每一个接口都具有某个具体的功能比如加速,刹车,向左转,向右转。这些和面向对象编程是一样的。
什么是字符串常量池?
String可能是Java中最常用的一个类。如果我们每次都创建一个新的String对象,可能就会导致浪费跟多内存。字符串常量池能够通过存储每一个String值对应的对象,示例如下:
尽管我们创建了数个值为Duck和Juggy的String对象,实际上只有两个对象被创建并存储在堆上。为了证明以上结论,可以参考以下示例代码(这里重申下在Java中“==”运算符用于比较两个对象是否相等):
String juggy = "Juggy"; String anotherJuggy = "Juggy"; System.out.println(juggy == anotherJuggy);
这段代码会返回true,因为这两个对象在字符串常量池中指向的是同一个对象。它们的值是一样的。
一个例外:“new”运算符
现在我们来看下这段代码,它看起来和上面的代码很相似但是却仍有一点不同。
String duke = new String("duke"); String anotherDuke = new String("duke"); System.out.println(duke == anotherDuke);
基于之前的实例,你也许会认为这段代码会返回true但是实际上却是false。使用new运算符会强制在堆中创建一个新的String对象。因此,这里JVM会创建两个不同的对象。
字符串常量池与intern()方法
为了在字符串常量池中存储一个String,我们会用到一个称为String interning的技术。以下是从Javadoc中摘录的关于intern()方法的说明:
/** * Returns a canonical representation for the string object. * * A pool of strings, initially empty, is maintained privately by the * class {@code String}. * * When the intern method is invoked, if the pool already contains a * string equal to this {@code String} object as determined by * the {@link #equals(Object)} method, then the string from the pool is * returned. Otherwise, this {@code String} object is added to the * pool and a reference to this {@code String} object is returned. * * It follows that for any two strings {@code s} and {@code t}, * {@code s.intern() == t.intern()} is {@code true} * if and only if {@code s.equals(t)} is {@code true}. * * All literal strings and string-valued constant expressions are * interned. String literals are defined in section 3.10.5 of the * The Java™ Language Specification. * * @returns a string that has the same contents as this string, but is * guaranteed to be from a pool of unique strings. * @jls 3.10.5 String Literals */ public native String intern();intern()方法用于在字符串常量池中存储String。首先它会验证你所创建的String是否存在于常量池中。如果不存在,则在常量池中创建一个新的String。
现在我们来看下当我们使用new关键字强制创建两个String会发生什么,代码如下:
String duke = new String("duke"); String duke2 = new String("duke"); System.out.println(duke == duke2); // The result will be false here System.out.println(duke.intern() == duke2.intern()); // The result will be true here
不同域之前的示例,在这个例子中,这里的比较结果为true。这是因为这里使用了intern()方法来保证该字符串存储于常量池中。
String类的equals方法
在String类中,equals()方法用于验证两个Java类是否相同。因为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; }
正如所见,equals方法比较的是String类的值而不是对象的引用。它不关心对象的引用是否不同而是比较对象的值。
最常用的String方法
在你比较String之前还有最后一件事需要注意,观察以下String类的常用方法:
// Removes spaces from the borders trim() // Gets a substring by indexes substring(int beginIndex, int endIndex) // Returns the characters length of the String length() // Replaces String, regex can be used. replaceAll(String regex, String replacement) // Verifies if there is a specified CharSequence in the String contains(CharSequences)
挑战String比较!
让我们尝试以下挑战。
在这里,你将使用我们学到的String概念来比较多个String。观察以下代码,你能否得出每一个比较的结果?
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
B:12469
C:12579
D:12568
答案是D
上述代码发生了什么?理解String运行原理
在第一段代码中我们看到:
result += " powerfulCode ".trim() == "powerfulCode" ? "0" : "1";
尽管String经过trim()方法调用之后不会改变,String “ powerfulcode ”在这种情况下是不一样的。在这里比较结果是false,因为当trim()方法从边界移除空格,JVM将强制通过new运算符创建一个新的String对象。
我们再看接下来的代码:
result += "flexibleCode" == "flexibleCode" ? "2" : "3";
这里毫无疑问,两个string在String pool中是相同的,这里比较的结果是true。
接下来的代码如下:
result += new String("doYourBest") == new String("doYourBest") ? "4" : "5";
使用new保留关键字强制创建两个新的String对象,无论他们是否相等。在这种情况下比较的结果是false即使他们的值是相同的。
接下来的代码如下:
result += new String("noBugsProject") .equals("noBugsProject") ? "6" : "7";
因为我们使用了equals()方法,两个String的值会进行比较而不是对象的实例。在这个例子中,JVM不关心对象是否相同因为比较的是它们的值。这里比较的结果是true。
关于String一些常见的错误
确认两个String是否指向同一个object是困难的,特别对于包含相同值的String对象来说。需要记住的是使用new关键字创建对象,即使创建的对象值是相同的,JVM仍旧会创建一个新的对象。
关于String需要记住的项
a)String至不可变的因此String的状态不可变。
b)为了节约内存,JVM会在String pool中存储String对象。当一个信息String被创建,JVM会检查它的值并指向一个已经存在的对象。如果在pool中不存在对应值的String对象,JVM将会创建一个String。
c)使用“==”是比较对象的引用,使用equals()方法比较的是String的值。相同的规则适用于所有对象。
d)当使用new关键字时,JVM将会创建一个新的String即使已存在值相同的String对象。