Java Challengers#2:字符串比较

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都在动态内存(内存堆)中创建一个新对象,那么我们将浪费大量内存。 字符串池通过为每个行值仅存储一个对象来解决此问题。


弦池中的弦


行池中的行


尽管我们使用值DukeJuggy创建了几个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()方法:


  /** *      . * *   ( )   {@code String}. * *    intern,     , *    {@code String},   *  {@link #equals(Object)},     . * ,   {@code String}   *        {@code String}. * *   ,      {@code s}  {@code t}, * {@code s.intern() == t.intern()}  {@code true} *    ,  {@code s.equals(t)}  {@code true}. * *       . *      3.10.5 The Java™ Language Specification. * * @returns ,         , * , ,       . * * @jls 3.10.5 String Literals */ public native String intern(); 

intern()方法用于将字符串存储在字符串池中。 首先,它检查池中是否存在已创建的行。 如果没有,它将在池中创建一个新行。 行池逻辑基于Flyweight模式。


现在,请注意当我们使用new创建两行时会发生什么:


 String duke = new String("duke"); String duke2 = new String("duke"); System.out.println(duke == duke2); //    false System.out.println(duke.intern() == duke2.intern()); //    true 

与前面带有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类的最常见方法:


 //         trim() //     substring(int beginIndex, int endIndex) //    length() //  ,     replaceAll(String regex, String replacement) // ,     CharSequence   contains(CharSequences) 

解决字符串比较问题


让我们通过解决一个小难题来检查您对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()完成其工作时,它将创建新行:


 //   trim   String new String(Arrays.copyOfRange(val, index, index + len), LATIN1); 

关于字符串要记住什么


  • 行是不可变的,因此无法更改行状态。


  • 为了节省内存,JVM将字符串存储在字符串池中。 创建新行时,JVM会检查其值并指向现有对象。 如果池中没有具有该值的行,那么JVM将创建一个新行。


  • 运算符“ == ”比较对象引用。 equals()方法比较字符串值。 相同的规则将应用于所有对象。


  • 使用new运算符时,即使有一行具有相同的值,也会在堆中创建新行(翻译器的注释-它以原始形式写入它在池中,但事实并非如此, 谢谢 zagayevskiy )。



答案


这个问题的答案是D。结论将是12568。


待续...

Source: https://habr.com/ru/post/zh-CN428449/


All Articles